Skip to content

Commit aba908f

Browse files
committed
add an API call example with sendChunk
1 parent f7f3e56 commit aba908f

File tree

5 files changed

+255
-0
lines changed

5 files changed

+255
-0
lines changed

Node/youtube/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# YouTube: Get information about a YouTube channel
2+
3+
This quickstart demonstrates how to query the
4+
[YouTube Data API](https://developers.google.com/youtube/v3) using **Cloud
5+
Functions for Firebase** with an HTTPS trigger.
6+
7+
## Introduction
8+
9+
The function `getChannelInfo` returns information about a Youtube channel. By
10+
default it will return information about the
11+
[Firebase YouTube channel](https://www.youtube.com/user/Firebase), but you can pass it a
12+
`channelId` URL Query parameter to query any channel you'd like.
13+
14+
## Setup
15+
16+
### Get a YouTube API Key
17+
18+
1. Create a Firebase Project on the
19+
[Firebase Console](https://console.firebase.google.com) if you don't already have a project you want to use.
20+
1. Upgrade your Firebase project to the
21+
[Blaze "pay as you go" plan](https://firebase.google.com/pricing)
22+
1. Enable the Youtube API by visiting the
23+
[API console](http://console.cloud.google.com/marketplace/product/google/youtube.googleapis.com),
24+
selecting your Firebase project, and clicking "ENABLE".
25+
1. Once the API is enabled, visit the
26+
[credentials tab](http://console.cloud.google.com/apis/api/youtube.googleapis.com/credentials)
27+
and click "CREATE CREDENTIALS" to create a YouTube API key.
28+
29+
### Clone and configure the function
30+
31+
1. Install the Firebase CLI and log in:
32+
```
33+
npm install --global firebase-tools
34+
35+
firebase login
36+
```
37+
1. Clone or download this repo and open the `youtube` directory.
38+
1. `cd` into the `functions` directory and install dependencies with `npm install`
39+
1. Set up your Firebase project by running `firebase use --add` with the
40+
Firebase CLI, select your Project ID and follow the instructions.
41+
1. Set the YouTube API key as an environment variable:
42+
```bash
43+
firebase functions:config:set youtube.key="THE API KEY"
44+
```
45+
46+
### Run your function locally with the Firebase Emulator Suite
47+
48+
1. Set up the Firebase emulators with your config ([docs](https://firebase.google.com/docs/functions/local-emulator#set_up_functions_configuration_optional)):
49+
```bash
50+
cd functions
51+
52+
firebase functions:config:get > .runtimeconfig.json
53+
```
54+
1. Run the following command to start the emulator:
55+
```bash
56+
firebase emulators:start --only functions
57+
```
58+
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`
59+
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.
60+
1. You should get a JSON response with information about the YouTube channel!
61+
62+
63+
## Deploy the app to prod
64+
65+
Deploy to Firebase using the following command:
66+
67+
```bash
68+
firebase deploy
69+
```
70+
71+
This deploys and activates the `getChannelInfo` function.
72+
73+
> The first time you call `firebase deploy` on a new project with Functions will take longer than usual.
74+
75+
## Modify it to your needs
76+
77+
Now that you've got this sample working, modify it to work for your use case! Some ideas:
78+
79+
- Check out the other things you can query with the [YouTube Data API](https://developers.google.com/youtube/v3/docs)
80+
- Convert `getChannelInfo` function to a scheduled function, and write the new latest videos for a channel into Firestore or Realtime Database
81+
- ...anything else you can think of!

Node/youtube/firebase.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"functions": {
3+
"codebase": "youtube",
4+
"predeploy": [
5+
"npm --prefix \"$RESOURCE_DIR\" run lint"
6+
],
7+
"source": "functions"
8+
},
9+
"emulators": {
10+
"functions": {
11+
"port": 5001
12+
},
13+
"ui": {
14+
"enabled": true
15+
}
16+
}
17+
}

Node/youtube/functions/.eslintrc.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module.exports = {
18+
root: true,
19+
env: {
20+
es2020: true,
21+
node: true,
22+
},
23+
extends: [
24+
"eslint:recommended",
25+
"google",
26+
],
27+
rules: {
28+
quotes: ["error", "double"],
29+
},
30+
};

Node/youtube/functions/index.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const {onCall, HttpsError} = require("firebase-functions/v2/https");
18+
const {defineString, defineSecret} = require("firebase-functions/params");
19+
20+
const {google} = require("googleapis");
21+
22+
const youtubeKey = defineSecret("YOUTUBE_API_KEY");
23+
const defaultChannelId = defineString("DEFAULT_CHANNEL_ID", {
24+
default: "UCP4bf6IHJJQehibu6ai__cg",
25+
});
26+
27+
exports.getChannelInfo = onCall(
28+
{secrets: [youtubeKey]},
29+
30+
async (request, response) => {
31+
const youtube = google.youtube({
32+
version: "v3",
33+
auth: youtubeKey.value(),
34+
});
35+
const channelId = request.data.channelId || defaultChannelId;
36+
37+
const channelInfo = {id: channelId};
38+
39+
// Fetch channel information
40+
let channelData;
41+
try {
42+
// tell the client app we're starting
43+
response.sendChunk({status: "fetching channel info", channelInfo});
44+
45+
// https://developers.google.com/youtube/v3/docs/channels/list
46+
const {data} = await youtube.channels.list({
47+
part: "snippet,statistics",
48+
id: channelId,
49+
maxResults: 1,
50+
});
51+
channelData = data;
52+
} catch (error) {
53+
throw new HttpsError("internal", "Failed to fetch channel data.");
54+
}
55+
56+
if (!channelData.items || channelData.items.length !== 1) {
57+
throw new HttpsError(
58+
"invalid-argument",
59+
`Channel with ID ${channelId} not found.`,
60+
);
61+
}
62+
63+
const channel = channelData.items[0];
64+
(channelInfo.channelTitle = channel.snippet.title),
65+
(channelInfo.channelDescription = channel.snippet.description),
66+
(channelInfo.subscriberCount = channel.statistics.subscriberCount),
67+
// send latest data to the client app
68+
response.sendChunk({status: "found channel info", channelInfo});
69+
70+
// Fetch the channel's latest videos
71+
let videoData;
72+
try {
73+
// tell the client app we're starting to fetch videos
74+
response.sendChunk({status: "fetching latest videos", channelInfo});
75+
// https://developers.google.com/youtube/v3/docs/search/list
76+
const {data} = await youtube.search.list({
77+
part: "id, snippet",
78+
order: "date",
79+
channelId,
80+
maxResults: 3,
81+
});
82+
videoData = data;
83+
} catch (error) {
84+
throw new HttpsError("internal", "Failed to fetch video data.");
85+
}
86+
const videos = (videoData.items || []).map((video) => ({
87+
videoTitle: video.snippet.title,
88+
videoUrl: `https://www.youtube.com/watch?v=${video.id.videoId}`,
89+
videoDescription: video.snippet.description,
90+
}));
91+
92+
channelInfo.recentVideos = videos;
93+
// send the latest data to the client app
94+
response.sendChunk({
95+
status: `found ${videos.length} videos`,
96+
channelInfo,
97+
});
98+
99+
return channelInfo;
100+
},
101+
);

Node/youtube/functions/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "functions",
3+
"description": "Cloud Functions for Firebase",
4+
"scripts": {
5+
"lint": "eslint .",
6+
"serve": "firebase emulators:start --only functions",
7+
"shell": "firebase functions:shell",
8+
"start": "npm run shell",
9+
"deploy": "firebase deploy --only functions",
10+
"logs": "firebase functions:log"
11+
},
12+
"engines": {
13+
"node": "18"
14+
},
15+
"main": "index.js",
16+
"dependencies": {
17+
"firebase-admin": "^11.9.0",
18+
"firebase-functions": "^4.4.1",
19+
"googleapis": "^66.0.0"
20+
},
21+
"devDependencies": {
22+
"eslint": "^8.40.0",
23+
"firebase-functions-test": "^3.1.0"
24+
},
25+
"private": true
26+
}

0 commit comments

Comments
 (0)