teol

Steam API class

Jul 26th, 2011
884
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 14.61 KB | None | 0 0
  1. <?php
  2. Class SteamAPI
  3. {
  4.     //- Time out in seconds
  5.     public $timeout = 3;
  6.  
  7.     //- API hostname
  8.     private $hostname = "steamcommunity.com";
  9.     private $socket, $ip_address, $service_port;
  10.  
  11.     /*
  12.     -- Function:    constructor
  13.     -- Purpose:     Called when class is created. Used to set some default values.
  14.     -- Arguments:   none
  15.     -- Returns:     void
  16.     */
  17.     public function __construct()
  18.     {
  19.         //- Get the IP address for steamcommunity.com
  20.         $this->ip_address = gethostbyname($this->hostname);
  21.         //- Get the port for the WWW service
  22.         $this->service_port = getservbyname('www', 'tcp');
  23.     }
  24.  
  25.     /*
  26.     -- Function:    get_profile_data
  27.     -- Purpose:     Retrieves profile data from the Steam API for the specified steam ID.
  28.     -- Arguments:   $steam_id - A steam ID to perform the API call for
  29.     -- Returns:     An array filled with the profile data, or null if the profile does not exist
  30.     */
  31.     public function get_profile_data($steam_id)
  32.     {
  33.         //- Calculate the 64 bit steam ID
  34.         $steam_id64 = $this->get_steamid64($steam_id);
  35.         if (!$steam_id64)
  36.         {
  37.             throw New Exception('Invalid steam ID provided');
  38.         }
  39.  
  40.         //- Construct HTTP request
  41.         $request = "GET /profiles/" . $steam_id64 . "?xml=1 HTTP/1.1\r\n";
  42.         $request .= "Host: " . $this->hostname . "\r\n";
  43.         $request .= "Connection: Close\r\n\r\n";
  44.  
  45.         //- Do HTTP request
  46.         $this->steam_connect();
  47.         $response = $this->steam_request($request);
  48.         $this->steam_disconnect();
  49.  
  50.         //- Check if a 302 response is given. This happens if the user has a 'Custom URL' set in their profile.
  51.         //- If this is the case, make another request to the custom URL
  52.         if (substr($response,0,12) == "HTTP/1.1 302")
  53.         {
  54.             //- Get the custom URL from the response
  55.             if (preg_match( '@Location: http://steamcommunity.com/id/([a-zA-Z0-9_-]+)/@', $response, $location))
  56.             {
  57.                 $custom_url = $location[1];
  58.             }
  59.             else
  60.             {
  61.                 throw New Exception('Custom URL redirection, but no location found.');
  62.             }
  63.  
  64.             //- Construct HTTP request
  65.             $request = "GET /id/" . $custom_url . "?xml=1 HTTP/1.1\r\n";
  66.             $request .= "Host: " . $this->hostname . "\r\n";
  67.             $request .= "Connection: Close\r\n\r\n";
  68.  
  69.             //- Do HTTP request
  70.             $this->steam_connect();
  71.             $response = $this->steam_request($request);
  72.             $this->steam_disconnect();
  73.         }
  74.  
  75.         $this->steam_validate($response);
  76.  
  77.         //- If there is no profile XML element, something is not right
  78.         if (!preg_match( '@<profile>(.+)</profile>@s', $response, $xmlArray))
  79.         {
  80.             throw New Exception('Could not parse returned steam community data. XML Root node missing.');
  81.         }
  82.  
  83.         //- Store XML without headers
  84.         $xml_data = $xmlArray[0];
  85.  
  86.         //- Parse XML. An array of data is returned
  87.         $data_array = $this->parse_xml($xml_data);
  88.  
  89.         //- Return the data
  90.         return $data_array;
  91.     }
  92.  
  93.     /*
  94.     -- Function:    parse_xml
  95.     -- Purpose: Populate an array with the data found in the supplied XML
  96.     -- Arguments:   $xml_data - The XML data to parse
  97.     -- Returns:     An array of data from the XML
  98.     */
  99.     private function parse_xml($xml_data)
  100.     {
  101.         //- Create an XMLReader object to parse the XML with
  102.         $reader = new XMLReader();
  103.  
  104.         //- Load the XML into it
  105.         try {
  106.             $reader->XML($xml_data);
  107.         }
  108.         catch (Exception $e)
  109.         {
  110.             throw New Exception('Could not parse returned XML: ' . $e->getMessage());
  111.         }
  112.  
  113.         //- Initialize an array for the profile data
  114.         $data_array = array();
  115.  
  116.         //- Start reading through the XML
  117.         while ($reader->read())
  118.         {
  119.             //- First encounter the root element
  120.             if ($reader->nodeType == XMLReader::ELEMENT)
  121.             {
  122.                 $rootnode_name = $reader->name;
  123.  
  124.                 //- Read through its subnodes
  125.                 while ($reader->read())
  126.                 {
  127.                     if ($reader->nodeType == XMLReader::ELEMENT)
  128.                     {
  129.                         //- Store the name of the subnode
  130.                         $elementName = $reader->name;
  131.  
  132.                         //- Check for sections that will result in a recursive multi dimensional array
  133.                         if (    $elementName == "friends" || $elementName == "weblinks" ||
  134.                             $elementName == "groups" || $elementName == "mostPlayedGames" )
  135.                         {
  136.                             //- Initialize an array for the data
  137.                             $sub_array = null;
  138.                             $sub_array = array();
  139.  
  140.                             $sub_counter = 0;
  141.  
  142.                             while ($reader->read())
  143.                             {
  144.                                 //- Check for subnodes
  145.                                 if ($reader->nodeType == XMLReader::ELEMENT &&
  146.                                     ($reader->name == "friend" ||
  147.                                     $reader->name == "weblink" ||
  148.                                     $reader->name == "group" ||
  149.                                     $reader->name == "mostPlayedGame"))
  150.                                 {
  151.                                     while ($reader->read())
  152.                                     {
  153.                                         //- Get the elements from the subnodes
  154.                                         if ($reader->nodeType == XMLReader::ELEMENT)
  155.                                         {
  156.                                             $subElementName = $reader->name;
  157.  
  158.                                             //- The node is not empty so an end node is required
  159.                                             //- Consider the data invalid until it is found
  160.                                             $validDatafield = false;
  161.  
  162.                                             while ($reader->read())
  163.                                             {
  164.                                                 //- Check for the subnode value
  165.                                                 if ($reader->nodeType == XMLReader::TEXT
  166.                                                     || $reader->nodeType == XMLReader::CDATA
  167.                                                     || $reader->nodeType == XMLReader::WHITESPACE
  168.                                                     || $reader->nodeType ==
  169.                                                         XMLReader::SIGNIFICANT_WHITESPACE)
  170.                                                 {
  171.                                                     //- Store the value
  172.                                                     $elementValue = $reader->value;
  173.                                                 }
  174.                                                 //- Check if this is the subnode end element
  175.                                                 else if ($reader->nodeType == XMLReader::END_ELEMENT
  176.                                                     && $reader->name == $subElementName)
  177.                                                 {
  178.                                                     //- If so the subnode is complete
  179.                                                     $validDatafield = true;
  180.                                                     break;
  181.                                                 }
  182.                                             }
  183.  
  184.                                             //- If this is a valid subnode, store it in the data array
  185.                                             if ($validDatafield)
  186.                                             {
  187.                                                 $sub_array[$sub_counter][$subElementName] =
  188.                                                     $elementValue;
  189.  
  190.                                                 $elementValue = '';
  191.                                             }
  192.                                         }                              
  193.                                         else if ($reader->nodeType == XMLReader::END_ELEMENT &&
  194.                                                 ($reader->name == "friend" ||
  195.                                                 $reader->name == "weblink" ||
  196.                                                 $reader->name == "group" ||
  197.                                                 $reader->name == "mostPlayedGame" ))
  198.                                         {
  199.                                             $sub_counter++;
  200.                                             break;
  201.                                         }
  202.                                     }
  203.                                 }
  204.                                 else if ($reader->nodeType == XMLReader::END_ELEMENT
  205.                                     && $reader->name == $elementName)
  206.                                 {
  207.                                     $validDatafield = false;
  208.                                     $data_array[$elementName] = $sub_array;
  209.                                     break;
  210.                                 }
  211.                             }
  212.                         }
  213.                         //- Check for sections that will result in a recursive single dimensional array
  214.                         else if ( $elementName == "inGameInfo" || $elementName == "favoriteGame" )
  215.                         {
  216.                             //- Initialize an array for the data
  217.                             $sub_array = null;
  218.                             $sub_array = array();
  219.  
  220.                             while ($reader->read())
  221.                             {
  222.                                 //- Get the elements from the subnodes
  223.                                 if ($reader->nodeType == XMLReader::ELEMENT)
  224.                                 {
  225.                                     $subElementName = $reader->name;
  226.  
  227.                                     //- The node is not empty so an end node is required
  228.                                     //- Consider the data invalid until it is found
  229.                                     $validDatafield = false;
  230.  
  231.                                     while ($reader->read())
  232.                                     {
  233.                                         //- Check for the subnode value
  234.                                         if ($reader->nodeType == XMLReader::TEXT
  235.                                             || $reader->nodeType == XMLReader::CDATA
  236.                                             || $reader->nodeType == XMLReader::WHITESPACE
  237.                                             || $reader->nodeType ==
  238.                                               XMLReader::SIGNIFICANT_WHITESPACE)
  239.                                         {
  240.                                             //- Store the value
  241.                                             $elementValue = $reader->value;
  242.                                         }
  243.                                         //- Check if this is the subnode end element
  244.                                         else if ($reader->nodeType == XMLReader::END_ELEMENT
  245.                                             && $reader->name == $subElementName)
  246.                                         {
  247.                                             //- If so the subnode is complete
  248.                                             $validDatafield = true;
  249.                                             break;
  250.                                         }
  251.                                     }
  252.  
  253.                                     //- If this is a valid subnode, store it in the data array
  254.                                     if ($validDatafield)
  255.                                     {
  256.                                         $sub_array[$subElementName] = $elementValue;
  257.                                         $elementValue = '';
  258.                                     }
  259.                                  }
  260.                                  else if ($reader->nodeType == XMLReader::END_ELEMENT &&
  261.                                  (  $reader->name == "inGameInfo" ||
  262.                                     $reader->name == "favoriteGame" ))
  263.                                  {
  264.                                     $validDatafield = false;
  265.                                     $data_array[$elementName] = $sub_array;
  266.                                     break;
  267.                                  }
  268.                              }
  269.                         }
  270.                         //- Check for single nodes that do not require a recursive array to be used
  271.                         else
  272.                         {
  273.                             //- An end node is required
  274.                             //- Consider the data invalid until it is found
  275.                             $validDatafield = false;
  276.  
  277.                             while ($reader->read())
  278.                             {
  279.                                 //- Check for the subnode value
  280.                                 if ($reader->nodeType == XMLReader::TEXT
  281.                                     || $reader->nodeType == XMLReader::CDATA
  282.                                     || $reader->nodeType == XMLReader::WHITESPACE
  283.                                     || $reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE)
  284.                                 {
  285.                                     //- Store the value
  286.                                     $elementValue = $reader->value;
  287.                                 }
  288.                                 //- Check if this is the subnode end element
  289.                                 else if ($reader->nodeType == XMLReader::END_ELEMENT
  290.                                     && $reader->name == $elementName)
  291.                                 {
  292.                                     //- If so the subnode is complete
  293.                                     $validDatafield = true;
  294.                                     break;
  295.                                 }
  296.                             }
  297.  
  298.                             //- If this is a valid subnode, store it in the data array
  299.                             if ($validDatafield)
  300.                             {
  301.                                 $data_array[$elementName] = $elementValue;
  302.                                 $elementValue = '';
  303.                             }
  304.                         }
  305.                     }
  306.                     //- Check for the final rootnode end element
  307.                     else if ($reader->nodeType == XMLReader::END_ELEMENT && $reader->name == $rootnode_name)
  308.                     {
  309.                         break;
  310.                     }
  311.                 }
  312.             }
  313.         }
  314.  
  315.         //- Close the XMLReader
  316.         $reader->close();
  317.        
  318.         //- Add the regular steam ID to the array as well
  319.         $data_array['steamID32'] = $this->calculate_steamid($data_array['steamID64']);
  320.  
  321.         return $data_array;
  322.     }
  323.  
  324.     /*
  325.     -- Function:    get_steamid64
  326.     -- Purpose: Check whether we have a regular steam ID, or a 64 bit steam ID and return a 64 bit steam ID either way
  327.     -- Arguments:   $steam_arg - Either a regular, or a 64 bit steam ID
  328.     -- Returns: The 64 bit steam ID, or false if an invalid argument is provided
  329.     */
  330.     private function get_steamid64($steam_arg)
  331.     {
  332.         if (preg_match('/^STEAM_[0-9]:[0-9]:[0-9]{1,}/i', $steam_arg))
  333.         {
  334.             return $this->calculate_steamid64($steam_arg);
  335.         }
  336.         else if (preg_match('/^76561197960[0-9]{6}$/i', $steam_arg))
  337.         {
  338.             return $steam_arg;
  339.         }
  340.         else
  341.         {
  342.             return false;
  343.         }
  344.     }
  345.  
  346.     /*
  347.     -- Function:    calculate_steamid64
  348.     -- Purpose: Translate a steam ID to a 64 bit steam id as used by Valve
  349.     -- Arguments:   $steam_id - The steam ID to translate
  350.     -- Returns: The 64 bit steam ID, or false if an invalid steam ID is provided
  351.     */
  352.     public function calculate_steamid64($steam_id)
  353.     {
  354.         if (preg_match('/^STEAM_[0-9]:[0-9]:[0-9]{1,}/i', $steam_id))
  355.         {
  356.             $steam_id = str_replace("_", ":", $steam_id);
  357.             list($part_one, $part_two, $part_three, $part_four) = explode(':', $steam_id);
  358.             $result = bcadd('76561197960265728', $part_four * 2);
  359.             $result = bcadd($result, $part_two);
  360.             return bcadd($result, $part_three);
  361.         }
  362.         else
  363.         {
  364.             return false;
  365.         }
  366.     }
  367.  
  368.     /*
  369.     -- Function:    calculate_steamid
  370.     -- Purpose: Translate a 64 bit steam ID to a steam id as used by Valve
  371.     -- Arguments:   $steam_id64 - The 64 bit steam ID to translate
  372.     -- Returns: The steam ID, or false if an invalid 64 bit steam ID is provided
  373.     */
  374.     public function calculate_steamid($steam_id64)
  375.     {
  376.         if (preg_match('/^76561197960[0-9]{6}$/i', $steam_id64))
  377.         {
  378.             $part_one = substr( $steam_id64, -1) % 2 == 0 ? 0 : 1;
  379.             $part_two = bcsub( $steam_id64, '76561197960265728' );
  380.             $part_two = bcsub( $part_two, $part_one );
  381.             $part_two = bcdiv( $part_two, 2 );
  382.             return "STEAM_0:" . $part_one . ':' . $part_two;
  383.         }
  384.         else
  385.         {
  386.             return false;
  387.         }
  388.     }
  389.    
  390.     /*
  391.     -- Function:    steam_connect
  392.     -- Purpose:     Initialize a connection to the Steam API
  393.     -- Arguments:   none
  394.     -- Returns:     void
  395.     */
  396.     private function steam_connect()
  397.     {
  398.         //- Get the TCP protocol (can also use SOL_TCP instead)
  399.         $protocol = getprotobyname('tcp');
  400.  
  401.         //- Create a socket
  402.         $this->socket = socket_create(AF_INET, SOCK_STREAM, $protocol);
  403.         if ($this->socket === false)
  404.             throw New Exception('Could not create socket. Reason: ' . socket_strerror(socket_last_error()));
  405.  
  406.         //- The socket should time out after 3 seconds, so make it non blocking for now
  407.         socket_set_nonblock($this->socket);
  408.  
  409.         $error = NULL;
  410.         $attempts = 0;
  411.         //- The time out value has to be in milliseconds
  412.         $timeout_ms = $this->timeout * 1000;
  413.         $connected;
  414.  
  415.         //- Connect the socket to the steam server
  416.         while (!($connected = @socket_connect($this->socket, $this->ip_address, $this->service_port)) && $attempts++ < $timeout_ms)
  417.         {
  418.             $error = socket_last_error();
  419.  
  420.             //- If the error is different from the below, there is a problem
  421.             if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY)
  422.             {
  423.                 socket_close($this->socket);
  424.                 throw New Exception('Could not connect to steam community: ' . socket_strerror($error));
  425.                 exit;
  426.             }
  427.  
  428.             //- Wait 1 second between attempts
  429.             usleep(1000);
  430.         }
  431.  
  432.         //- If it's still not connected then consider the connection timed out
  433.         if (!$connected)
  434.         {
  435.             socket_close($this->socket);
  436.             throw New Exception('Connection to steam community timed out.');
  437.             exit;
  438.         }
  439.  
  440.         //- Make the socket blocking again
  441.         socket_set_block($this->socket);
  442.     }
  443.  
  444.     /*
  445.     -- Function:    steam_request
  446.     -- Purpose:     Sends a HTTP request to the Steam API
  447.     -- Arguments:   $request_date - The HTTP request to be sent
  448.     -- Returns:     The 'raw' response data received from the Steam API
  449.     */
  450.     private function steam_request($request_data)
  451.     {
  452.         //- Write the request to the socket
  453.         socket_write($this->socket, $request_data, strlen($request_data));
  454.  
  455.         //- Store the response
  456.         $buffer = '';
  457.         $response = '';
  458.         while ($buffer = socket_read($this->socket, 2048))
  459.         {
  460.             $response .= $buffer;
  461.         }
  462.  
  463.         return $response;
  464.     }
  465.  
  466.     /*
  467.     -- Function:    steam_validate
  468.     -- Purpose:     Checks the HTTP response code to see if the API request was succesful
  469.     -- Arguments:   $response_data - The response to be validated
  470.     -- Returns:     void
  471.     */
  472.     private function steam_validate($response_data)
  473.     {
  474.         //- Check if the HTTP response code is 2xx. If so the request was a success
  475.         if (!preg_match('@^HTTP/1.1 2[0-9]{2}@s', $response_data))
  476.         {
  477.             throw New Exception('Request made to steam community failed (HTTP response code ' .
  478.                 substr($this->response, 9, 3) . ')');
  479.         }
  480.     }
  481.  
  482.     /*
  483.     -- Function:    steam_disconnect
  484.     -- Purpose:     Disconnect from the Steam API
  485.     -- Arguments:   none
  486.     -- Returns:     void
  487.     */
  488.     private function steam_disconnect()
  489.     {
  490.         //- Close the socket
  491.         socket_close($this->socket);
  492.     }
  493. }
  494.  
  495. ?>
Advertisement
Add Comment
Please, Sign In to add comment