Making of twitch-stream-countdown: Get followed channels

Twitch logo

The starting point for developers wanting to use the Twitch API is the Twitch Developers page. Getting started with the Twitch API is relatively easy: create an account and create an app. Once I’ve done that, I went through the documentation to find out what I will need to call from the code.

The idea of twitch-stream-countdown is that anyone could log in the webpage with their Twitch account so they can see a list of their followed channels. For this purpose I created an authentication with the OAuth Implicit Code Flow and it was super easy: I placed a button in the header for the login which calls a JavaScript funtion to get the URL using my client ID, redirect URL, responseType and scope.

<a class="btn btn-primary btn-lg" role="button" href="{{getLoginUrl()}}">Log in with Twitch »</a>
getLoginUrl(): string {
  const clientId = [ClientID]; // your client ID, are public and can be shared (for example, embedded in the source of a Web page).
  const redirectUri = [RedirectURL]; // your registered redirect URI
  const responseType = 'token';
  const scope = 'user_read'; // space-separated list of scopes

  let url = `https://api.twitch.tv/kraken/oauth2/authorize?client_id=${clientId}&amp;redirect_uri=${redirectUri}&amp;response_type=${responseType}&amp;scope=${scope}`;
  return url;
}

The next step would be to get the list of followed channels. I went through the Users Reference and discovered that the endpoint for getting the followed channels of a particular user wouldn’t actually require any authentication so I decided to go with a simpler approach for the first version of the webpage. I would use my Twitch user name to fetch my followed channels so no login would be required and I could just use the public API without authentication.

Getting to the code part, the twitch service would be responsible for communicating with the Twitch API and the channel component would use this service to get the data and display it.

<div class="container">
  <table class="table table-striped table-hover">
    <caption>List of followed channels</caption>
    <thead class="thead-light">
      <tr>
        <th scope="col">Twitch channel</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let channel of followedChannels">
        <td><a href="{{ channel.url }}" class="channel-link" target="_blank">{{ channel.name }}</a></td>
      </tr>
    </tbody>
  </table>
</div>
export class ChannelsComponent implements OnInit {

  followedChannels: Channel[];

  constructor(private twitchService: TwitchService) { }

  ngOnInit() {
      var me = new User();
      me.name = 'fitgeekgirl';
      this.getUserChannels(me);
  }

  getUserChannels(user: User): void {
    this.twitchService.getUserChannels(user)
      .then(followedChannels => this.followedChannels = followedChannels);
  }

}
@Injectable()
export class TwitchService {
  private userUrl = 'https://api.twitch.tv/kraken/user';

  constructor(private http: HttpClient) { }

  getUserFollowsUrl(userId): string {
    let url = `https://api.twitch.tv/kraken/users/${userId}/follows/channels`;
    return url;
  }

  getUserChannels(user: User): Promise<Channel[]> {
    return this.http.get(this.getUserFollowsUrl(user.name), httpOptions)
      .pipe(
        tap(channels => this.log(`fetched channels`)),
        catchError(this.handleError('getChannels', []))
      )
      .toPromise().then(function(data) {
        return (<any>data).follows.map(
          followee => ({
            name: followee.channel.display_name,
            url: followee.channel.url
          }));
      });
  }

  private handleError<T> (operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(error);
      this.log(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }

  private log(message: string) {
    console.warn('TwitchService: ' + message);
  }

}
export class Channel {
    id: number;
    name: string;
    url: string;
}

The repository with all the code is on GitHub.