' . esc_html__( 'Live now', 'clanfront' ) . ''; } if ( clanfront_twitch_checked() ) { return '' . esc_html__( 'Offline', 'clanfront' ) . ''; } return ''; } /** Twitch API app token, cached until shortly before it expires. */ function clanfront_twitch_token() { $id = trim( (string) clanfront_setting( 'twitch_client_id' ) ); $secret = trim( (string) clanfront_setting( 'twitch_client_secret' ) ); if ( '' === $id || '' === $secret ) { return ''; } $token = get_transient( 'clanfront_twitch_token' ); if ( is_string( $token ) && '' !== $token ) { return $token; } if ( get_transient( 'clanfront_twitch_failed' ) ) { return ''; // The last attempt failed; wait a minute before trying again so pages stay fast. } $res = wp_remote_post( 'https://id.twitch.tv/oauth2/token', array( 'timeout' => 4, 'body' => array( 'client_id' => $id, 'client_secret' => $secret, 'grant_type' => 'client_credentials', ), ) ); if ( is_wp_error( $res ) || 200 !== (int) wp_remote_retrieve_response_code( $res ) ) { set_transient( 'clanfront_twitch_failed', 1, MINUTE_IN_SECONDS ); return ''; } $data = json_decode( wp_remote_retrieve_body( $res ), true ); if ( ! is_array( $data ) || empty( $data['access_token'] ) ) { set_transient( 'clanfront_twitch_failed', 1, MINUTE_IN_SECONDS ); return ''; } $ttl = isset( $data['expires_in'] ) ? max( 60, (int) $data['expires_in'] - 300 ) : HOUR_IN_SECONDS; set_transient( 'clanfront_twitch_token', (string) $data['access_token'], $ttl ); return (string) $data['access_token']; } /** Every Twitch channel name entered on a member, plus the clan's own channel. */ function clanfront_twitch_logins() { $logins = array(); foreach ( clanfront_get_members() as $m ) { $l = clanfront_twitch_login( get_post_meta( $m->ID, 'clanfront_twitch', true ) ); if ( $l ) { $logins[ $l ] = true; } } $own = clanfront_twitch_login( clanfront_setting( 'twitch_channel' ) ); if ( $own ) { $logins[ $own ] = true; } return array_slice( array_keys( $logins ), 0, 100 ); } /** * Channels that are live right now: login => array( name, title, game, viewers ). * Empty when Twitch is not set up or cannot be reached. */ function clanfront_live_streams() { $token = clanfront_twitch_token(); if ( '' === $token ) { return array(); } $logins = clanfront_twitch_logins(); if ( ! $logins ) { return array(); } sort( $logins ); $key = clanfront_live_key( $logins ); $cached = get_transient( $key ); if ( is_array( $cached ) ) { return $cached; } $query = array(); foreach ( $logins as $l ) { $query[] = 'user_login=' . rawurlencode( $l ); } $res = wp_remote_get( 'https://api.twitch.tv/helix/streams?' . implode( '&', $query ), array( 'timeout' => 4, 'headers' => array( 'Client-ID' => trim( (string) clanfront_setting( 'twitch_client_id' ) ), 'Authorization' => 'Bearer ' . $token, ), ) ); if ( is_wp_error( $res ) ) { set_transient( $key, array(), MINUTE_IN_SECONDS ); return array(); } if ( 401 === (int) wp_remote_retrieve_response_code( $res ) ) { delete_transient( 'clanfront_twitch_token' ); // The saved token is no longer accepted; fetch a new one next time. } $data = json_decode( wp_remote_retrieve_body( $res ), true ); $out = array(); if ( 200 === (int) wp_remote_retrieve_response_code( $res ) && is_array( $data ) && ! empty( $data['data'] ) && is_array( $data['data'] ) ) { foreach ( $data['data'] as $s ) { if ( empty( $s['user_login'] ) || ( isset( $s['type'] ) && 'live' !== $s['type'] ) ) { continue; } $out[ strtolower( (string) $s['user_login'] ) ] = array( 'name' => isset( $s['user_name'] ) ? sanitize_text_field( $s['user_name'] ) : sanitize_text_field( $s['user_login'] ), 'title' => isset( $s['title'] ) ? sanitize_text_field( $s['title'] ) : '', 'game' => isset( $s['game_name'] ) ? sanitize_text_field( $s['game_name'] ) : '', 'viewers' => isset( $s['viewer_count'] ) ? (int) $s['viewer_count'] : 0, ); } } if ( 200 === (int) wp_remote_retrieve_response_code( $res ) ) { set_transient( $key . '_ok', 1, 2 * MINUTE_IN_SECONDS ); // Twitch answered, so anyone not in the list really is offline. } set_transient( $key, $out, 2 * MINUTE_IN_SECONDS ); return $out; } /** The "Live now" bar: members who are streaming right now. */ function clanfront_live_block() { $live = clanfront_live_streams(); if ( ! $live ) { return ''; } ob_start(); ?> 4, 'headers' => array( 'Client-ID' => trim( (string) clanfront_setting( 'twitch_client_id' ) ), 'Authorization' => 'Bearer ' . $token, ), ) ); if ( is_wp_error( $res ) ) { return null; } $code = (int) wp_remote_retrieve_response_code( $res ); if ( 401 === $code ) { delete_transient( 'clanfront_twitch_token' ); } if ( 200 !== $code ) { return null; } $data = json_decode( wp_remote_retrieve_body( $res ), true ); return ( is_array( $data ) && isset( $data['data'] ) && is_array( $data['data'] ) ) ? $data['data'] : null; } /** Twitch's numeric id for a channel name, saved for a week. */ function clanfront_twitch_user_id( $login ) { $key = 'clanfront_tw_uid_' . md5( $login ); $id = get_transient( $key ); if ( false !== $id ) { return (string) $id; } $data = clanfront_twitch_get( 'users', array( 'login' => $login ) ); if ( null === $data ) { return ''; } $id = ( isset( $data[0]['id'] ) && ctype_digit( (string) $data[0]['id'] ) ) ? (string) $data[0]['id'] : ''; set_transient( $key, $id, $id ? WEEK_IN_SECONDS : HOUR_IN_SECONDS ); return $id; } /** "1h2m3s" → "1:02:03", "4m5s" → "4:05". */ function clanfront_twitch_duration( $d ) { if ( ! preg_match( '/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/', (string) $d, $m ) || '' === (string) $d ) { return ''; } $h = isset( $m[1] ) ? (int) $m[1] : 0; $i = isset( $m[2] ) ? (int) $m[2] : 0; $s = isset( $m[3] ) ? (int) $m[3] : 0; return $h ? sprintf( '%d:%02d:%02d', $h, $i, $s ) : sprintf( '%d:%02d', $i, $s ); } /** * The newest videos (past broadcasts, highlights and uploads) of one Twitch channel. * Saved for an hour (ten minutes when there is nothing), so Twitch is not asked on every visit. * Each item: url, title, thumb, date (Y-m-d), duration, views, type. */ function clanfront_twitch_videos( $login, $limit = 6 ) { $login = clanfront_twitch_login( $login ); if ( ! $login || '' === clanfront_twitch_token() ) { return array(); } $key = 'clanfront_tw_vid_' . md5( $login ); $cached = get_transient( $key ); if ( is_array( $cached ) ) { return array_slice( $cached, 0, $limit ); } $uid = clanfront_twitch_user_id( $login ); if ( '' === $uid ) { return array(); } $data = clanfront_twitch_get( 'videos', array( 'user_id' => $uid, 'first' => '12', 'sort' => 'time', ) ); if ( null === $data ) { set_transient( $key, array(), 10 * MINUTE_IN_SECONDS ); return array(); } $out = array(); foreach ( $data as $v ) { $url = isset( $v['url'] ) ? esc_url_raw( $v['url'] ) : ''; if ( ! $url || 0 !== strpos( $url, 'https://www.twitch.tv/' ) ) { continue; } $thumb = isset( $v['thumbnail_url'] ) ? (string) $v['thumbnail_url'] : ''; $thumb = str_replace( array( '%{width}', '%{height}' ), array( '640', '360' ), $thumb ); // Twitch shows a placeholder for a broadcast that is still being processed; leave it out. if ( '' !== $thumb && ( false !== strpos( $thumb, '_404' ) || 0 !== strpos( $thumb, 'https://' ) ) ) { $thumb = ''; } $out[] = array( 'url' => $url, 'title' => isset( $v['title'] ) ? sanitize_text_field( $v['title'] ) : '', 'thumb' => $thumb ? esc_url_raw( $thumb ) : '', 'date' => isset( $v['created_at'] ) ? substr( sanitize_text_field( $v['created_at'] ), 0, 10 ) : '', 'duration' => clanfront_twitch_duration( isset( $v['duration'] ) ? $v['duration'] : '' ), 'views' => isset( $v['view_count'] ) ? (int) $v['view_count'] : 0, 'type' => isset( $v['type'] ) ? sanitize_key( $v['type'] ) : '', ); } set_transient( $key, $out, $out ? HOUR_IN_SECONDS : 10 * MINUTE_IN_SECONDS ); return array_slice( $out, 0, $limit ); } /** Video cards for one channel, or '' when there are none. */ function clanfront_twitch_video_cards( $login, $limit = 6 ) { $videos = clanfront_twitch_videos( $login, $limit ); if ( ! $videos ) { return ''; } $types = array( 'archive' => 'Past broadcast', 'highlight' => 'Highlight', 'upload' => 'Upload', ); ob_start(); echo '
Checks your Twitch keys and every channel on the site, and clears the saved Twitch answers so the site asks Twitch again on the next visit.