LCTVAPI.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. /**
  3. * Livecoding.tv API.
  4. *
  5. * @package LCTVBadges\LCTVAPI
  6. * @since 0.0.3
  7. */
  8. /** Check if script is accessed directly. */
  9. if ( basename( __FILE__ ) == basename( $_SERVER['SCRIPT_FILENAME'] ) ) {
  10. exit();
  11. }
  12. /** Set path for flat file data store if not set. */
  13. if ( ! defined( 'LCTVAPI_DATA_PATH' ) ) {
  14. define( 'LCTVAPI_DATA_PATH', __DIR__ );
  15. }
  16. /** Set cache expire time if not set. */
  17. if ( ! defined( 'LCTVAPI_CACHE_EXPIRES_IN' ) ) {
  18. define( 'LCTVAPI_CACHE_EXPIRES_IN', 300 );
  19. }
  20. /**
  21. * Livecoding.tv API class.
  22. *
  23. * @since 0.0.3
  24. */
  25. class LCTVAPI {
  26. /**
  27. * App client id.
  28. *
  29. * Used for authorization and token retrieval.
  30. *
  31. * @var string
  32. */
  33. public $client_id = '';
  34. /**
  35. * App client secret.
  36. *
  37. * Used for authorization and token retrieval.
  38. *
  39. * @var string
  40. */
  41. public $client_secret = '';
  42. /**
  43. * Redirect url after livecoding.tv authorization.
  44. *
  45. * Used for authorization code return from API.
  46. *
  47. * @var string URL.
  48. */
  49. public $redirect_url = '';
  50. /**
  51. * Token.
  52. *
  53. * Used to make API calls.
  54. *
  55. * @var stdClass|boolean
  56. */
  57. public $token = false;
  58. /**
  59. * Scope.
  60. *
  61. * Used to define authorization scope.
  62. *
  63. * @var string
  64. */
  65. public $scope = 'read read:user read:channel';
  66. /**
  67. * User name/slug.
  68. *
  69. * Used to identify the user token for API calls.
  70. *
  71. * @var string
  72. */
  73. public $user = '';
  74. /**
  75. * Data store.
  76. *
  77. * Used to store, recall and delete data.
  78. *
  79. * @var string|data store object
  80. */
  81. private $data_store = 'LCTVAPIDataStoreFlatFiles';
  82. /**
  83. * Constructor.
  84. *
  85. * Supplied $args override class property defaults.
  86. *
  87. * @since 0.0.3
  88. *
  89. * @param array $args Arguments to override class property defaults.
  90. */
  91. function __construct( $args = array() ) {
  92. /** Check that curl is available. */
  93. if ( ! function_exists( 'curl_version' ) ) {
  94. echo 'Curl required.';
  95. exit();
  96. }
  97. /** Override class property defaults. */
  98. $keys = array_keys( get_object_vars( $this ) );
  99. foreach ( $keys as $key ) {
  100. if ( isset( $args[$key] ) ) {
  101. $this->$key = $args[$key];
  102. }
  103. }
  104. /** Instatiate data store. */
  105. $this->data_store = new $this->data_store();
  106. /** Delete user token and all user data. */
  107. if ( isset( $_GET['delete'] ) && isset( $_GET['user'] ) ) {
  108. $token_del = $this->data_store->get_data( $_GET['user'], 'token' );
  109. if ( $_GET['delete'] === $token_del->delete_id ) {
  110. $this->data_store->delete_data( $_GET['user'], '*' );
  111. }
  112. }
  113. /** Attempt to get token data. */
  114. $this->token = $this->data_store->get_data( $this->user, 'token' );
  115. /** If the API isn't authorized start a PHP session. */
  116. if ( ! $this->is_authorized() ) {
  117. session_start();
  118. }
  119. /** Received authorization redirect from API. */
  120. if ( isset( $_GET['state'] ) && isset( $_GET['code'] ) && session_status() === PHP_SESSION_ACTIVE ) {
  121. if ( $_GET['state'] === session_id() ) {
  122. $this->token = new stdClass();
  123. $this->token->code = $_GET['code'];
  124. }
  125. }
  126. /** Check the token. */
  127. $this->check_token();
  128. }
  129. /**
  130. * Check the token and get/refresh it if necessary.
  131. *
  132. * @since 0.0.3
  133. * @access public
  134. */
  135. public function check_token() {
  136. /** No token to check or errored token. */
  137. if ( $this->token === false || isset( $this->token->error ) ) {
  138. return;
  139. }
  140. /** Token hasn't expired yet. */
  141. if ( isset( $this->token->access_token ) && ( time() - $this->token->created_at ) < $this->token->expires_in ) {
  142. return;
  143. }
  144. /** POST parameters for token request. */
  145. $token_params = array(
  146. 'grant_type' => 'authorization_code',
  147. 'code' => $this->token->code,
  148. 'redirect_uri' => $this->redirect_url,
  149. );
  150. /** Add refresh POST parameters if available. */
  151. if ( isset( $this->token->refresh_token ) ) {
  152. $token_params['grant_type'] = 'refresh_token';
  153. $token_params['refresh_token'] = $this->token->refresh_token;
  154. }
  155. /** Token request headers. */
  156. $token_headers = array(
  157. 'Cache-Control: no-cache',
  158. 'Pragma: no-cache',
  159. 'Authorization: Basic ' . base64_encode( $this->client_id . ':' . $this->client_secret ),
  160. );
  161. /** POST token request to API. */
  162. $token_ret = $this->post_url_contents( 'https://www.livecoding.tv/o/token/', $token_params, $token_headers );
  163. $token = json_decode( $token_ret );
  164. /** Stop checking on error. */
  165. if ( isset( $token->error ) ) {
  166. return;
  167. }
  168. /** Setup new token and save to data store. */
  169. $token->code = $this->token->code;
  170. $token->created_at = time();
  171. $this->token = $token;
  172. $this->token->delete_id = uniqid();
  173. $user = $this->api_request( 'v1/user/' );
  174. if ( ! isset( $user->result->detail ) ) {
  175. if ( empty( $this->user ) ) {
  176. $this->user = $user->result->slug;
  177. }
  178. $this->data_store->put_data( $user->result->slug, 'token', $this->token );
  179. }
  180. }
  181. /**
  182. * Get authorization url.
  183. *
  184. * @since 0.0.3
  185. * @access public
  186. */
  187. public function get_authorization_url() {
  188. /** If PHP session hasn't been started, start it now. */
  189. if ( session_status() !== PHP_SESSION_ACTIVE ) {
  190. if ( session_start() === false ) {
  191. return '';
  192. }
  193. }
  194. return 'https://www.livecoding.tv/o/authorize/' .'
  195. ?scope=' . urlencode( $this->scope ) .
  196. '&state=' . urlencode( session_id() ) .
  197. '&redirect_uri=' . urlencode( $this->redirect_url ) .
  198. '&response_type=code' .
  199. '&client_id=' . urlencode( $this->client_id );
  200. }
  201. /**
  202. * Check if there's a token.
  203. *
  204. * @since 0.0.3
  205. * @access public
  206. */
  207. public function is_authorized() {
  208. return ( $this->token !== false && isset( $this->token->access_token ) );
  209. }
  210. /**
  211. * Make an API request/call.
  212. *
  213. * @since 0.0.3
  214. * @access public
  215. *
  216. * @param string $api_path API endpoint. ex: 'v1/livestreams/'
  217. * @param int $cache_expires_in (optional) Override LCTVAPI_CACHE_EXPIRES_IN constant. Default: false
  218. * @param bool $cache (optional) True to cache result, false to not. Default: true
  219. */
  220. public function api_request( $api_path, $cache_expires_in = false, $cache = true ) {
  221. /** Check if we're authorized. */
  222. if ( ! $this->is_authorized() ) {
  223. $ret = new stdClass();
  224. $ret->result->detail = 'LCTVAPI not authorized';
  225. return $ret;
  226. }
  227. /** Setup api request type for data store. */
  228. $api_request_type = str_replace( '/', '', $api_path );
  229. /** Attempt to load API request from cache. */
  230. $api_cache = $this->data_store->get_data( $this->user, $api_request_type );
  231. /** Check for cache expire time override. */
  232. if ( $cache_expires_in !== false ) {
  233. $cache_check = $cache_expires_in;
  234. } else {
  235. $cache_check = LCTVAPI_CACHE_EXPIRES_IN;
  236. }
  237. /** Make API request call if we have no cache or if cache is expired. */
  238. if ( ! isset( $api_cache->created_at ) || ( time() - $api_cache->created_at ) > $cache_check ) {
  239. $headers = array(
  240. 'Cache-Control: no-cache',
  241. 'Pragma: no-cache',
  242. 'Authorization: ' . $this->token->token_type . ' ' . $this->token->access_token,
  243. );
  244. $api_ret = $this->get_url_contents( 'https://www.livecoding.tv:443/api/' . $api_path, $headers );
  245. $api_cache = new stdClass();
  246. $api_cache->result = json_decode( $api_ret, false );
  247. $api_cache->created_at = time();
  248. if ( $cache ) {
  249. $this->data_store->put_data( $this->user, $api_request_type, $api_cache );
  250. }
  251. }
  252. return $api_cache;
  253. }
  254. /**
  255. * Curl GET request.
  256. *
  257. * @since 0.0.3
  258. * @access private
  259. */
  260. private function get_url_contents( $url, $custom_header = [] ) {
  261. $crl = curl_init();
  262. curl_setopt( $crl, CURLOPT_HTTPHEADER, $custom_header );
  263. curl_setopt( $crl, CURLOPT_URL, $url );
  264. curl_setopt( $crl, CURLOPT_RETURNTRANSFER, 1 );
  265. curl_setopt( $crl, CURLOPT_CONNECTTIMEOUT, 5 );
  266. $ret = curl_exec( $crl );
  267. curl_close( $crl );
  268. return $ret;
  269. }
  270. /**
  271. * Curl POST request.
  272. *
  273. * @since 0.0.3
  274. * @access private
  275. */
  276. private function post_url_contents( $url, $fields, $custom_header = [] ) {
  277. $fields_string = '';
  278. foreach( $fields as $key => $value ) {
  279. $fields_string .= $key . '=' . urlencode( $value ) . '&';
  280. }
  281. rtrim( $fields_string, '&' );
  282. $crl = curl_init();
  283. curl_setopt( $crl, CURLOPT_HTTPHEADER, $custom_header );
  284. curl_setopt( $crl, CURLOPT_URL, $url );
  285. if ( ! empty( $fields_string ) ) {
  286. curl_setopt( $crl, CURLOPT_POST, count( $fields ) );
  287. curl_setopt( $crl, CURLOPT_POSTFIELDS, $fields_string );
  288. }
  289. curl_setopt( $crl, CURLOPT_RETURNTRANSFER, 1 );
  290. curl_setopt( $crl, CURLOPT_CONNECTTIMEOUT, 5 );
  291. $ret = curl_exec( $crl );
  292. curl_close( $crl );
  293. return $ret;
  294. }
  295. }
  296. /**
  297. * Flat file data store class.
  298. *
  299. * @since 0.0.3
  300. */
  301. class LCTVAPIDataStoreFlatFiles {
  302. /**
  303. * Get data.
  304. *
  305. * Data is always an object, and should be returned as an object.
  306. *
  307. * @since 0.0.3
  308. * @access public
  309. *
  310. * @param string $user User name/slug.
  311. * @param string $type Data type.
  312. *
  313. * @return bool|stdClass False on failure, object on success.
  314. */
  315. public function get_data( $user, $type ) {
  316. if ( empty( $user ) || empty( $type ) || ! file_exists( LCTVAPI_DATA_PATH . $user . '.' . $type ) ) {
  317. return false;
  318. }
  319. return json_decode( file_get_contents( LCTVAPI_DATA_PATH . $user . '.' . $type ), false );
  320. }
  321. /**
  322. * Put/store data.
  323. *
  324. * Data is always an object. False should be returned on failure,
  325. * any other value will be considered a success.
  326. *
  327. * @since 0.0.3
  328. * @access public
  329. *
  330. * @param string $user User name/slug.
  331. * @param string $type Data type.
  332. * @param stdClass $data Data object.
  333. *
  334. * @return bool|int False on failure, number of bytes written on success.
  335. */
  336. public function put_data( $user, $type, $data ) {
  337. if ( empty( $user ) || empty( $type ) || empty( $data ) ) {
  338. return false;
  339. }
  340. return file_put_contents( LCTVAPI_DATA_PATH . $user . '.' . $type, json_encode( $data ) );
  341. }
  342. /**
  343. * Delete data.
  344. *
  345. * Data type ($type) may include a '*' wildcard to indicate all data.
  346. *
  347. * @since 0.0.3
  348. * @access public
  349. *
  350. * @param string $user User name/slug.
  351. * @param string $type Data type.
  352. *
  353. * @return bool False on failure, true on success.
  354. */
  355. public function delete_data( $user, $type ) {
  356. if ( empty( $user ) || empty( $type ) ) {
  357. return false;
  358. }
  359. if ( strpos( $type, '*' ) !== false ) {
  360. array_map( 'unlink', glob( LCTVAPI_DATA_PATH . $user . '.' . $type ) );
  361. return true;
  362. } else {
  363. return unlink( LCTVAPI_DATA_PATH . $user . '.' . $type );
  364. }
  365. }
  366. }