Skip to content

Commit

Permalink
add an API call example with sendChunk
Browse files Browse the repository at this point in the history
  • Loading branch information
jhuleatt committed Jan 24, 2025
1 parent f7f3e56 commit aba908f
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 0 deletions.
81 changes: 81 additions & 0 deletions Node/youtube/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# YouTube: Get information about a YouTube channel

This quickstart demonstrates how to query the
[YouTube Data API](https://developers.google.com/youtube/v3) using **Cloud
Functions for Firebase** with an HTTPS trigger.

## Introduction

The function `getChannelInfo` returns information about a Youtube channel. By
default it will return information about the
[Firebase YouTube channel](https://www.youtube.com/user/Firebase), but you can pass it a
`channelId` URL Query parameter to query any channel you'd like.

## Setup

### Get a YouTube API Key

1. Create a Firebase Project on the
[Firebase Console](https://console.firebase.google.com) if you don't already have a project you want to use.
1. Upgrade your Firebase project to the
[Blaze "pay as you go" plan](https://firebase.google.com/pricing)
1. Enable the Youtube API by visiting the
[API console](http://console.cloud.google.com/marketplace/product/google/youtube.googleapis.com),
selecting your Firebase project, and clicking "ENABLE".
1. Once the API is enabled, visit the
[credentials tab](http://console.cloud.google.com/apis/api/youtube.googleapis.com/credentials)
and click "CREATE CREDENTIALS" to create a YouTube API key.

### Clone and configure the function

1. Install the Firebase CLI and log in:
```
npm install --global firebase-tools
firebase login
```
1. Clone or download this repo and open the `youtube` directory.
1. `cd` into the `functions` directory and install dependencies with `npm install`
1. Set up your Firebase project by running `firebase use --add` with the
Firebase CLI, select your Project ID and follow the instructions.
1. Set the YouTube API key as an environment variable:
```bash
firebase functions:config:set youtube.key="THE API KEY"
```

### Run your function locally with the Firebase Emulator Suite

1. Set up the Firebase emulators with your config ([docs](https://firebase.google.com/docs/functions/local-emulator#set_up_functions_configuration_optional)):
```bash
cd functions
firebase functions:config:get > .runtimeconfig.json
```
1. Run the following command to start the emulator:
```bash
firebase emulators:start --only functions
```
1. Check the emulator output to find the URL of the `getChannelInfo` function. It will looks something like `http://localhost:5001/my-project-id/us-central1/getChannelInfo`
1. Via CURL or in your browser, visit the URL that the function is running at. Optionally, add a query string `?channelId=SOME_CHANNEL_ID` to the end of the URL.
1. You should get a JSON response with information about the YouTube channel!


## Deploy the app to prod

Deploy to Firebase using the following command:

```bash
firebase deploy
```

This deploys and activates the `getChannelInfo` function.

> The first time you call `firebase deploy` on a new project with Functions will take longer than usual.

## Modify it to your needs

Now that you've got this sample working, modify it to work for your use case! Some ideas:
- Check out the other things you can query with the [YouTube Data API](https://developers.google.com/youtube/v3/docs)
- Convert `getChannelInfo` function to a scheduled function, and write the new latest videos for a channel into Firestore or Realtime Database
- ...anything else you can think of!
17 changes: 17 additions & 0 deletions Node/youtube/firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"functions": {
"codebase": "youtube",
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint"
],
"source": "functions"
},
"emulators": {
"functions": {
"port": 5001
},
"ui": {
"enabled": true
}
}
}
30 changes: 30 additions & 0 deletions Node/youtube/functions/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

module.exports = {
root: true,
env: {
es2020: true,
node: true,
},
extends: [
"eslint:recommended",
"google",
],
rules: {
quotes: ["error", "double"],
},
};
101 changes: 101 additions & 0 deletions Node/youtube/functions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const {onCall, HttpsError} = require("firebase-functions/v2/https");
const {defineString, defineSecret} = require("firebase-functions/params");

const {google} = require("googleapis");

const youtubeKey = defineSecret("YOUTUBE_API_KEY");
const defaultChannelId = defineString("DEFAULT_CHANNEL_ID", {
default: "UCP4bf6IHJJQehibu6ai__cg",
});

exports.getChannelInfo = onCall(
{secrets: [youtubeKey]},

async (request, response) => {
const youtube = google.youtube({
version: "v3",
auth: youtubeKey.value(),
});
const channelId = request.data.channelId || defaultChannelId;

const channelInfo = {id: channelId};

// Fetch channel information
let channelData;
try {
// tell the client app we're starting
response.sendChunk({status: "fetching channel info", channelInfo});

// https://developers.google.com/youtube/v3/docs/channels/list
const {data} = await youtube.channels.list({
part: "snippet,statistics",
id: channelId,
maxResults: 1,
});
channelData = data;
} catch (error) {
throw new HttpsError("internal", "Failed to fetch channel data.");
}

if (!channelData.items || channelData.items.length !== 1) {
throw new HttpsError(
"invalid-argument",
`Channel with ID ${channelId} not found.`,
);
}

const channel = channelData.items[0];
(channelInfo.channelTitle = channel.snippet.title),
(channelInfo.channelDescription = channel.snippet.description),
(channelInfo.subscriberCount = channel.statistics.subscriberCount),
// send latest data to the client app
response.sendChunk({status: "found channel info", channelInfo});

// Fetch the channel's latest videos
let videoData;
try {
// tell the client app we're starting to fetch videos
response.sendChunk({status: "fetching latest videos", channelInfo});
// https://developers.google.com/youtube/v3/docs/search/list
const {data} = await youtube.search.list({
part: "id, snippet",
order: "date",
channelId,
maxResults: 3,
});
videoData = data;
} catch (error) {
throw new HttpsError("internal", "Failed to fetch video data.");
}
const videos = (videoData.items || []).map((video) => ({
videoTitle: video.snippet.title,
videoUrl: `https://www.youtube.com/watch?v=${video.id.videoId}`,
videoDescription: video.snippet.description,
}));

channelInfo.recentVideos = videos;
// send the latest data to the client app
response.sendChunk({
status: `found ${videos.length} videos`,
channelInfo,
});

return channelInfo;
},
);
26 changes: 26 additions & 0 deletions Node/youtube/functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint .",
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "18"
},
"main": "index.js",
"dependencies": {
"firebase-admin": "^11.9.0",
"firebase-functions": "^4.4.1",
"googleapis": "^66.0.0"
},
"devDependencies": {
"eslint": "^8.40.0",
"firebase-functions-test": "^3.1.0"
},
"private": true
}

0 comments on commit aba908f

Please sign in to comment.