Related
When a client installs the app, they have the option to click on the app name in the list of apps on the /admin/apps page.
When they click that page, my PHP index file for my app receives these $_GET vars:
hmac = some_long_alphanumaeric_hmac
locale = en
protocol = https://
shop = example-shop.myshopify.com
timestamp = 1535609063
To verify a webhook from Shopify, I successfully use this:
function verify_webhook($data, $hmac_header, $app_api_secret) {
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, $app_api_secret, true));
return ($hmac_header == $calculated_hmac);
}
// Set vars for Shopify webhook verification
$hmac_header = $_SERVER['HTTP_X_SHOPIFY_HMAC_SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header, MY_APP_API_SECRET);
Is it possible to verify an app admin page visit is from a Shopify client that has the app installed?
PS: I've looked through both, the Embedded Apps API (but I can't figure out if that's even the right documentation or if I'm doing something wrong), as well as the GitHub example provided (which has no instructions on how to verify an Embedded App admin page visit).
UPDATE:
I've tried various other ways, discovering some ridiculous problems along the way, but still no luck.
The method I understand should be used to verify a Shopify HMAC is something akin to this:
function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
$params_array = array();
$hmac = $hmac ? $hmac : $_GET['hmac'];
unset($_GET['hmac']);
foreach($_GET as $key => $value){
$key = str_replace("%","%25",$key);
$key = str_replace("&","%26",$key);
$key = str_replace("=","%3D",$key);
$value = str_replace("%","%25",$value);
$value = str_replace("&","%26",$value);
$params_array[] = $key . "=" . $value;
}
$params_string = join('&', $params_array);
$computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret);
return hash_equals($hmac, $computed_hmac);
}
But the line $params_string = join('&', $params_array); causes an annoying problem by encoding ×tamp as xtamp ... Using http_build_query($params_array) results in the same ridiculous thing. Found others having this same problem here. Basically resolved by encoding the & as &, to arrive at $params_string = join('&', $params_array);.
My final version is like this, but still doesn't work (all the commented code is what else I've tried to no avail):
function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
$params_array = array();
$hmac = $hmac ? $hmac : $_GET['hmac'];
unset($_GET['hmac']);
// unset($_GET['protocol']);
// unset($_GET['locale']);
foreach($_GET as $key => $value){
$key = str_replace("%","%25",$key);
$key = str_replace("&","%26",$key);
$key = str_replace("=","%3D",$key);
$value = str_replace("%","%25",$value);
$value = str_replace("&","%26",$value);
$params_array[] = $key . "=" . $value;
// This commented out method below was an attempt to see if
// the imporperly encoded query param characters were causing issues
/*
if (!isset($params_string) || empty($params_string)) {
$params_string = $key . "=" . $value;
}
else {
$params_string = $params_string . "&" . $key . "=" . $value;
}
*/
}
// $params_string = join('&', $params_array);
// echo $params_string;
// $computed_hmac = base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, true));
// $computed_hmac = base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, false));
// $computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret, false);
// $computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret, true);
$computed_hmac = hash_hmac('sha256', http_build_query($params_array), $shopify_app_api_secret);
return hash_equals($hmac, $computed_hmac);
}
If you get a hit from Shopify, the first thing you do is check in your persistence layer if you have the shop registered. If you do, and you have a session of some kind setup, you are free to render your App to that shop. If you do not have the shop persisted, you go through the oAuth cycle to get an authentication token to use on the shop, which you persist along with the shop and new session.
For any routes or end points in your shop where you are receiving webhooks, of course those requests have no session, so you use the HMAC security approach to figure out what to do. So your question is clearly straddling two different concepts, each handled differently. The documentation is pretty clear on the differences.
Here is the relevant documentation: https://shopify.dev/tutorials/authenticate-with-oauth#verification. This info by Sandeep was also very helpful too: https://community.shopify.com/c/Shopify-APIs-SDKs/HMAC-verify-app-install-request-using-php/m-p/140097#comment-253000.
Here is what worked for me:
function verify_visiter() // returns true or false
{
// check that timestamp is recent to ensure that this is not a 'replay' of a request that has been intercepted previously (man in the middle attack)
if (!isset($_GET['timestamp'])) return false;
$seconds_in_a_day = 24 * 60 * 60;
$older_than_a_day = $_GET['timestamp'] < (time() - $seconds_in_a_day);
if ($older_than_a_day) return false;
$shared_secret = Your_Shopify_app_shared_secret;
$hmac_header = $_GET['hmac'];
unset($_GET['hmac']);
$data = urldecode(http_build_query($_GET));
$calculated_hmac = hash_hmac('sha256', $data, $shared_secret, false);
return hash_equals($hmac_header, $calculated_hmac);
}
$verified = verify_visiter();
if (!$verified) {
exit('User verification failed.');
}
// ... everything else...
public function authenticateCalls($data = NULL, $bypassTimeCheck = FALSE)
{
$da = array();
foreach($data as $key => $val)
{
$da[$key] = $val;
}
if(isset($da['hmac']))
{
unset($da['hmac']);
}
ksort($da);
// Timestamp check; 1 hour tolerance
if (!$bypassTimeCheck)
{
if (($da['timestamp'] - time() > 3600))
{
return false;
}
}
// HMAC Validation
$queryString = http_build_query($da);
$match = $data['hmac'];
$calculated = hash_hmac('sha256', $queryString, $this->_API['API_SECRET']);
return $calculated === $match;
}
Before everything , I know that my question is already posted thousand times but none of them concern me since i cant identify my problem.
So ,I am building a PHP rest api to get a put request from the client (angular), this last can update a file inside the form as well as some text inputs , all of this is sent as a form-data in this code :
var fileFormData = new FormData();
$http({
method: 'PUT',
url: host + '/produit/' + $scope.product.id,
data: fileFormData,
transformRequest: angular.identity,
headers: {
'Content-Type': undefined
}
}).then(function successCallback(response) {}, function errorCallback(response) {});
On the server-side I have to get the response type and then parse the data here's the full code with help from here
$method = $_SERVER['REQUEST_METHOD'];
if ($method=="PUT"){
global $_PUT;
/* PUT data comes in on the stdin stream */
$putdata = fopen("php://input", "r");
/* Open a file for writing */
// $fp = fopen("myputfile.ext", "w");
$raw_data = '';
/* Read the data 1 KB at a time
and write to the file */
while ($chunk = fread($putdata, 1024))
$raw_data .= $chunk;
/* Close the streams */
fclose($putdata);
// Fetch content and determine boundary
$boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));
if(empty($boundary)){
parse_str($raw_data,$data);
$GLOBALS[ '_PUT' ] = $data;
return;
}
// Fetch each part
$parts = array_slice(explode($boundary, $raw_data), 1);
$data = array();
foreach ($parts as $part) {
// If this is the last part, break
if ($part == "--\r\n") break;
// Separate content from headers
$part = ltrim($part, "\r\n");
list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);
// Parse the headers list
$raw_headers = explode("\r\n", $raw_headers);
$headers = array();
foreach ($raw_headers as $header) {
list($name, $value) = explode(':', $header);
$headers[strtolower($name)] = ltrim($value, ' ');
}
// Parse the Content-Disposition to get the field name, etc.
if (isset($headers['content-disposition'])) {
$filename = null;
$tmp_name = null;
preg_match(
'/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/',
$headers['content-disposition'],
$matches
);
list(, $type, $name) = $matches;
//Parse File
if( isset($matches[4]) )
{
//if labeled the same as previous, skip
if( isset( $_FILES[ $matches[ 2 ] ] ) )
{
continue;
}
//get filename
$filename = $matches[4];
//get tmp name
$filename_parts = pathinfo( $filename );
$tmp_name = tempnam( ini_get('upload_tmp_dir'), $filename_parts['filename']);
//populate $_FILES with information, size may be off in multibyte situation
$_FILES[ $matches[ 2 ] ] = array(
'error'=>0,
'name'=>$filename,
'tmp_name'=>$tmp_name,
'size'=>strlen( $body ),
'type'=>$value
);
//place in temporary directory
file_put_contents($tmp_name, $body);
}
//Parse Field
else
{
$data[$name] = substr($body, 0, strlen($body) - 2);
}
}
}
$GLOBALS[ '_PUT' ] = $data;
$input= array_merge($_PUT, $_FILES);
}
and then I have to execute the query and move the file form the temp folder
$sql = "update `$table` set $set where id=$key"; //works
if(isset($name)){ //to check if file exist
chmod($tmpname,0666); //since that's the answer for most of question that are similar to mine
echo substr(sprintf('%o', fileperms($tmpname)), -4); //returns 0666
echo $tmpname." ".$name; //works too
if(move_uploaded_file($tmpname, "/home/****/****/uploads/produits/".$name))
echo "moved";
else
echo "not moved"; // and I always get this
Can you explain to me please whats wrong ?
PS:
I checked the /tmp folder the files I uploaded are still there.
The log is clean and I used this in the top of my code
ini_set('display_errors', true);
error_reporting(E_ALL);
Update :
I use this to get file params from the $input var
$values = array_map(function ($value) use ($link) {
global $method;
if ($value === null)
return null;
if (gettype($value) === "array"){
if ($method=="PUT" || $method=="POST"){
global $tmpname,$name;
$tmpname=$value['tmp_name'];
$name=uniqid().$value['name'];
$value=$name;
}
}
return mysqli_real_escape_string($link, (string) $value);
}, array_values($input));
I am trying to parse some data from external site to local script. Local script cals remote page with some get data to initiate response.
Returning the required data in the headers of the remote script, in this case data is "key" string.
Now the remote page also outputs a load of other headers, the one we are after is called LicenceID
unfourtunalty the array crerated by get_headers seems to include the header title in the value, not the key. So am trying some regex to match the header value, which would look somthing like this:
licenceID: dfkfdlsdlghgsldghsld
function check_path($path)
{
$headers = #get_headers($path);
var_dump($headers);
//while (list($key, $value) = each($headers))
foreach($headers as $key => $value)
{
echo "$key - $value<br />";
//if(preg_match('/^.*licenceID:*$/',$value))
if (preg_match('/^.*licenceID.*$/i', $dictionary, $matches))
{
echo "found";
echo "$key - $value<br />";
$position = strpos($value, ':');
$clean_value = substr($value, $position);
$clean_up = trim_string($clean_value,":");
return $clean_up;
}
else
{
return false;
}
}
}
Having trouble with the regex in the line if (preg_match('/^.licenceID.$/i', $dictionary, $matches))
cant seem to get it to match, also needs to strip out the licenceID: from the value and only return the data after the :
Suggestions welcome!
You could just use substr():
function check_path($path)
{
$headers = #get_headers($path);
var_dump($headers);
foreach($headers as $key => $value)
{
echo "$key - $value<br />";
if ('licenceID:' == substr($value, 0, 10))
{
echo "found";
echo "$key - $value<br />";
$clean_value = substr($value, 10);
$clean_up = trim($clean_value);
return $clean_up;
}
else
{
return false;
}
}
}
I think you're having trouble because you are returning false inside the foreach loop, unless licenceID is the first header it will always return false. Here are some simplified solutions:
Using preg_grep()
$headers = #get_headers($path);
$licenseID = preg_grep('~licenceID~i', $headers);
$licenseID = preg_replace('~licenceID:\s+~i', '', $licenseID); // clean-up
Or, like #Glass Robot suggested:
$headers = #get_headers($path, 1);
$licenseID = $headers['licenceID'];
I haven't tested this, but it should work.
so final function looks like this:
function check_path($path)
{
$headers = #get_headers($path);
$licenseID = preg_grep('~licenceID~i', $headers);
$licenseID = preg_replace('~licenceID:\s+~i', '', $licenseID); // clean-up
if($licenseID)
{
return $licenseID['7'];
}
else
{
return false;
}
}
thanks to Alix Axel for the preg_grep suggestion and spotting the mistake in the return false else statement. of course it was always returning false! doh.
Is it possible to pull track info from an audio stream using PHP? I've done some digging and the closest function I can find is stream_get_transports but my host doesn't support http transports via fsockopen() so I'll have to do some more tinkering to see what else that function returns.
Currently, I'm trying to pull artist and track metadata from an AOL stream.
This is a SHOUTcast stream, and yes it is possible. It has absolutely nothing to do with ID3 tags. I wrote a script awhile ago to do this, but can't find it anymore. Just last week I helped another guy who had a fairly complete script to do the same thing, but I can't just post the source to it, as it isn't mine. I will however get you in touch with him, if you e-mail me at brad#musatcha.com.
Anyway, here's how to do it yourself:
The first thing you need to do is connect to the server directly. Don't use HTTP. Well, you could probably use cURL, but it will likely be much more hassle than its worth. You connect to it with fsockopen() (doc). Make sure to use the correct port. Also note that many web hosts will block a lot of ports, but you can usually use port 80. Fortunately, all of the AOL-hosted SHOUTcast streams use port 80.
Now, make your request just like your client would.
GET /whatever HTTP/1.0
But, before sending <CrLf><CrLf>, include this next header!
Icy-MetaData:1
That tells the server that you want metadata. Now, send your pair of <CrLf>.
Ok, the server will respond with a bunch of headers and then start sending you data. In those headers will be an icy-metaint:8192 or similar. That 8192 is the meta interval. This is important, and really the only value you need. It is usually 8192, but not always, so make sure to actually read this value!
Basically it means, you will get 8192 bytes of MP3 data and then a chunk of meta, followed by 8192 bytes of MP3 data, followed by a chunk of meta.
Read 8192 bytes of data (make sure you are not including the header in this count), discard them, and then read the next byte. This byte is the first byte of meta data, and indicates how long the meta data is. Take the value of this byte (the actual byte with ord() (doc)), and multiply it by 16. The result is the number of bytes to read for metadata. Read those number of bytes into a string variable for you to work with.
Next, trim the value of this variable. Why? Because the string is padded with 0x0 at the end (to make it fit evenly into a multiple of 16 bytes), and trim() (doc) takes care of that for us.
You will be left with something like this:
StreamTitle='Awesome Trance Mix - DI.fm';StreamUrl=''
I'll let you pick your method of choice for parsing this. Personally I'd probably just split with a limit of 2 on ;, but beware of titles that contain ;. I'm not sure what the escape character method is. A little experimentation should help you.
Don't forget to disconnect from the server when you're done with it!
There are lots of SHOUTcast MetaData references out there. This is a good one: http://www.smackfu.com/stuff/programming/shoutcast.html
Check this out: https://gist.github.com/fracasula/5781710
It's a little gist with a PHP function that lets you extract MP3 metadata (StreamTitle) from a streaming URL.
Usually the streaming server puts an icy-metaint header in the response which tells us how often the metadata is sent in the stream. The function checks for that response header and, if present, it replaces the interval parameter with it.
Otherwise the function calls the streaming URL respecting your interval and, if any metadata isn't present, then it tries again through recursion starting from the offset parameter.
<?php
/**
* Please be aware. This gist requires at least PHP 5.4 to run correctly.
* Otherwise consider downgrading the $opts array code to the classic "array" syntax.
*/
function getMp3StreamTitle($streamingUrl, $interval, $offset = 0, $headers = true)
{
$needle = 'StreamTitle=';
$ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';
$opts = [
'http' => [
'method' => 'GET',
'header' => 'Icy-MetaData: 1',
'user_agent' => $ua
]
];
if (($headers = get_headers($streamingUrl))) {
foreach ($headers as $h) {
if (strpos(strtolower($h), 'icy-metaint') !== false && ($interval = explode(':', $h)[1])) {
break;
}
}
}
$context = stream_context_create($opts);
if ($stream = fopen($streamingUrl, 'r', false, $context)) {
$buffer = stream_get_contents($stream, $interval, $offset);
fclose($stream);
if (strpos($buffer, $needle) !== false) {
$title = explode($needle, $buffer)[1];
return substr($title, 1, strpos($title, ';') - 2);
} else {
return getMp3StreamTitle($streamingUrl, $interval, $offset + $interval, false);
}
} else {
throw new Exception("Unable to open stream [{$streamingUrl}]");
}
}
var_dump(getMp3StreamTitle('http://str30.creacast.com/r101_thema6', 19200));
I hope this helps!
Thanks a lot for the code fra_casula. Here is a slightly simplified version running on PHP <= 5.3 (the original is targeted at 5.4). It also reuses the same connection resource.
I removed the exception because of my own needs, returning false if nothing is found instead.
private function getMp3StreamTitle($steam_url)
{
$result = false;
$icy_metaint = -1;
$needle = 'StreamTitle=';
$ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';
$opts = array(
'http' => array(
'method' => 'GET',
'header' => 'Icy-MetaData: 1',
'user_agent' => $ua
)
);
$default = stream_context_set_default($opts);
$stream = fopen($steam_url, 'r');
if($stream && ($meta_data = stream_get_meta_data($stream)) && isset($meta_data['wrapper_data'])){
foreach ($meta_data['wrapper_data'] as $header){
if (strpos(strtolower($header), 'icy-metaint') !== false){
$tmp = explode(":", $header);
$icy_metaint = trim($tmp[1]);
break;
}
}
}
if($icy_metaint != -1)
{
$buffer = stream_get_contents($stream, 300, $icy_metaint);
if(strpos($buffer, $needle) !== false)
{
$title = explode($needle, $buffer);
$title = trim($title[1]);
$result = substr($title, 1, strpos($title, ';') - 2);
}
}
if($stream)
fclose($stream);
return $result;
}
This is the C# code for getting the metadata using HttpClient:
public async Task<string> GetMetaDataFromIceCastStream(string url)
{
m_httpClient.DefaultRequestHeaders.Add("Icy-MetaData", "1");
var response = await m_httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
m_httpClient.DefaultRequestHeaders.Remove("Icy-MetaData");
if (response.IsSuccessStatusCode)
{
IEnumerable<string> headerValues;
if (response.Headers.TryGetValues("icy-metaint", out headerValues))
{
string metaIntString = headerValues.First();
if (!string.IsNullOrEmpty(metaIntString))
{
int metadataInterval = int.Parse(metaIntString);
byte[] buffer = new byte[metadataInterval];
using (var stream = await response.Content.ReadAsStreamAsync())
{
int numBytesRead = 0;
int numBytesToRead = metadataInterval;
do
{
int n = stream.Read(buffer, numBytesRead, 10);
numBytesRead += n;
numBytesToRead -= n;
} while (numBytesToRead > 0);
int lengthOfMetaData = stream.ReadByte();
int metaBytesToRead = lengthOfMetaData * 16;
byte[] metadataBytes = new byte[metaBytesToRead];
var bytesRead = await stream.ReadAsync(metadataBytes, 0, metaBytesToRead);
var metaDataString = System.Text.Encoding.UTF8.GetString(metadataBytes);
return metaDataString;
}
}
}
}
return null;
}
UPDATE:
This is an update with a more appropriate solution to the question. The original post is also provided below for information.
The script in this post, after some error correction, works and extracts the stream title using PHP:
PHP script to extract artist & title from Shoutcast/Icecast stream.
I had to make a couple of changes, because the echo statements at the end were throwing an error. I added two print_r() statements after the function, and $argv[1] in the call so you can pass the URL to it from the command line.
<?php
define('CRLF', "\r\n");
class streaminfo{
public $valid = false;
public $useragent = 'Winamp 2.81';
protected $headers = array();
protected $metadata = array();
public function __construct($location){
$errno = $errstr = '';
$t = parse_url($location);
$sock = fsockopen($t['host'], $t['port'], $errno, $errstr, 5);
$path = isset($t['path'])?$t['path']:'/';
if ($sock){
$request = 'GET '.$path.' HTTP/1.0' . CRLF .
'Host: ' . $t['host'] . CRLF .
'Connection: Close' . CRLF .
'User-Agent: ' . $this->useragent . CRLF .
'Accept: */*' . CRLF .
'icy-metadata: 1'.CRLF.
'icy-prebuffer: 65536'.CRLF.
(isset($t['user'])?'Authorization: Basic '.base64_encode($t['user'].':'.$t['pass']).CRLF:'').
'X-TipOfTheDay: Winamp "Classic" rulez all of them.' . CRLF . CRLF;
if (fwrite($sock, $request)){
$theaders = $line = '';
while (!feof($sock)){
$line = fgets($sock, 4096);
if('' == trim($line)){
break;
}
$theaders .= $line;
}
$theaders = explode(CRLF, $theaders);
foreach ($theaders as $header){
$t = explode(':', $header);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode(':', $t));
if ($value != ''){
if (is_numeric($value)){
$this->headers[$name] = (int)$value;
}else{
$this->headers[$name] = $value;
}
}
}
}
if (!isset($this->headers['icymetaint'])){
$data = ''; $metainterval = 512;
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
$this->print_data($data);
$matches = array();
preg_match_all('/([\x00-\xff]{2})\x0\x0([a-z]+)=/i', $data, $matches, PREG_OFFSET_CAPTURE);
preg_match_all('/([a-z]+)=([a-z0-9\(\)\[\]., ]+)/i', $data, $matches, PREG_SPLIT_NO_EMPTY);
echo '<pre>';var_dump($matches);echo '</pre>';
$title = $artist = '';
foreach ($matches[0] as $nr => $values){
$offset = $values[1];
$length = ord($values[0]{0}) +
(ord($values[0]{1}) * 256)+
(ord($values[0]{2}) * 256*256)+
(ord($values[0]{3}) * 256*256*256);
$info = substr($data, $offset + 4, $length);
$seperator = strpos($info, '=');
$this->metadata[substr($info, 0, $seperator)] = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'title') $title = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'artist') $artist = substr($info, $seperator + 1);
}
$this->metadata['streamtitle'] = $artist . ' - ' . $title;
}else{
$metainterval = $this->headers['icymetaint'];
$intervals = 0;
$metadata = '';
while(1){
$data = '';
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
//$this->print_data($data);
$len = join(unpack('c', fgetc($sock))) * 16;
if ($len > 0){
$metadata = str_replace("\0", '', fread($sock, $len));
break;
}else{
$intervals++;
if ($intervals > 100) break;
}
}
$metarr = explode(';', $metadata);
foreach ($metarr as $meta){
$t = explode('=', $meta);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode('=', $t));
if (substr($value, 0, 1) == '"' || substr($value, 0, 1) == "'"){
$value = substr($value, 1);
}
if (substr($value, -1) == '"' || substr($value, -1) == "'"){
$value = substr($value, 0, -1);
}
if ($value != ''){
$this->metadata[$name] = $value;
}
}
}
}
fclose($sock);
$this->valid = true;
}else echo 'unable to write.';
}else echo 'no socket '.$errno.' - '.$errstr.'.';
print_r($theaders);
print_r($metadata);
}
public function print_data($data){
$data = str_split($data);
$c = 0;
$string = '';
echo "<pre>\n000000 ";
foreach ($data as $char){
$string .= addcslashes($char, "\n\r\0\t");
$hex = dechex(join(unpack('C', $char)));
if ($c % 4 == 0) echo ' ';
if ($c % (4*4) == 0 && $c != 0){
foreach (str_split($string) as $s){
//echo " $string\n";
if (ord($s) < 32 || ord($s) > 126){
echo '\\'.ord($s);
}else{
echo $s;
}
}
echo "\n";
$string = '';
echo str_pad($c, 6, '0', STR_PAD_LEFT).' ';
}
if (strlen($hex) < 1) $hex = '00';
if (strlen($hex) < 2) $hex = '0'.$hex;
echo $hex.' ';
$c++;
}
echo " $string\n</pre>";
}
public function __get($name){
if (isset($this->metadata[$name])){
return $this->metadata[$name];
}
if (isset($this->headers[$name])){
return $this->headers[$name];
}
return null;
}
}
$t = new streaminfo($argv[1]); // get metadata
/*
echo "Meta Interval: ".$t->icymetaint;
echo "\n";
echo 'Current Track: '.$t->streamtitle;
*/
?>
With the updated code, it prints the arrays of header and streamtitle info. If you only want the now_playing track, then comment out the two print_r() statements, and uncomment the echo statements at the end.
#Example: run this command:
php getstreamtitle.php http://162.244.80.118:3066
#and the result is...
Array
(
[0] => HTTP/1.0 200 OK
[1] => icy-notice1:<BR>This stream requires Winamp<BR>
[2] => icy-notice2:SHOUTcast DNAS/posix(linux x64) v2.6.0.750<BR>
[3] => Accept-Ranges:none
[4] => Access-Control-Allow-Origin:*
[5] => Cache-Control:no-cache,no-store,must-revalidate,max-age=0
[6] => Connection:close
[7] => icy-name:
[8] => icy-genre:Old Time Radio
[9] => icy-br:24
[10] => icy-sr:22050
[11] => icy-url:http://horror-theatre.com
[12] => icy-pub:1
[13] => content-type:audio/mpeg
[14] => icy-metaint:8192
[15] => X-Clacks-Overhead:GNU Terry Pratchett
[16] =>
)
StreamTitle='501026TooHotToLive';
Here is the original post using python and vlc
The PHP solution kept searching but never returned a response for me.
This is not PHP as requested, but may help others looking for a way to extract the 'now_playing' info from live streams.
If you only want the 'now_playing' info, you can edit the script to return that.
The python script extracts the metadata (including the 'now_playing' track) using VLC. You need VLC and the python libraries: sys, telnetlib, os, time and socket.
#!/usr/bin/python
# coding: utf-8
import sys, telnetlib, os, time, socket
HOST = "localhost"
password = "admin"
port = "4212"
def check_port():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = sock.connect_ex((HOST, int(port)))
sock.close()
return res == 0
def checkstat():
if not check_port():
os.popen('vlc --no-audio --intf telnet --telnet-password admin --quiet 2>/dev/null &')
while not check_port():
time.sleep(.1)
def docmd(cmd):
tn = telnetlib.Telnet(HOST, port)
tn.read_until(b"Password: ")
tn.write(password.encode('utf-8') + b"\n")
tn.read_until(b"> ")
tn.write(cmd.encode('utf-8') + b"\n")
ans=tn.read_until(">".encode("utf-8"))[0:-3]
return(ans)
tn.close()
def nowplaying(playing):
npstart=playing.find('now_playing')
mystr=playing[npstart:]
npend=mystr.find('\n')
return mystr[:npend]
def metadata(playing):
fstr='+----'
mstart=playing.find(fstr)
mend=playing.find(fstr,mstart+len(fstr))
return playing[mstart:mend+len(fstr)]
checkstat()
docmd('add '+sys.argv[1])
playing=""
count=0
while not 'now_playing:' in playing:
time.sleep(.5)
playing=docmd('info')
count+=1
if count>9:
break
if playing == "":
print("--Timeout--")
else:
print(metadata(playing))
docmd('shutdown')
Example, extract metadata from Crypt Theater Station:
./radiometatdata.py http://107.181.227.250:8026
Response:
+----[ Meta data ]
|
| title: *CRYPT THEATER*
| filename: 107.181.227.250:8026
| genre: Old Time Radio
| now_playing: CBS Radio Mystery Theatre - A Ghostly Game of Death
|
+----
Can i parse a plist file with php and kind of get it into an array, like the $_POST[''] so i could call $_POST['body'] and get the string that has the <key> body ?
CFPropertyList - A PHP Implementation Of Apple's plist (PropertyList)
Googling for "php plist parser" turned up this blog post that seems to be able to do what you are asking for.
Took a look at some of the libraries out there but they have external requirements and seem overkill. Here's a function that simply puts the data in to associative arrays. This worked on a couple of exported itunes plist files I tried.
// pass in the full plist file contents
function parse_plist($plist) {
$result = false;
$depth = [];
$key = false;
$lines = explode("\n", $plist);
foreach ($lines as $line) {
$line = trim($line);
if ($line) {
if ($line == '<dict>') {
if ($result) {
if ($key) {
// adding a new dictionary, the line above this one should've had the key
$depth[count($depth) - 1][$key] = [];
$depth[] =& $depth[count($depth) - 1][$key];
$key = false;
} else {
// adding a dictionary to an array
$depth[] = [];
}
} else {
// starting the first dictionary which doesn't have a key
$result = [];
$depth[] =& $result;
}
} else if ($line == '</dict>' || $line == '</array>') {
array_pop($depth);
} else if ($line == '<array>') {
$depth[] = [];
} else if (preg_match('/^\<key\>(.+)\<\/key\>\<.+\>(.+)\<\/.+\>$/', $line, $matches)) {
// <key>Major Version</key><integer>1</integer>
$depth[count($depth) - 1][$matches[1]] = $matches[2];
} else if (preg_match('/^\<key\>(.+)\<\/key\>\<(true|false)\/\>$/', $line, $matches)) {
// <key>Show Content Ratings</key><true/>
$depth[count($depth) - 1][$matches[1]] = ($matches[2] == 'true' ? 1 : 0);
} else if (preg_match('/^\<key\>(.+)\<\/key\>$/', $line, $matches)) {
// <key>1917</key>
$key = $matches[1];
}
}
}
return $result;
}