diff --git a/nodecg-io-googleapis/extension/index.ts b/nodecg-io-googleapis/extension/index.ts new file mode 100644 index 000000000..74cadf91c --- /dev/null +++ b/nodecg-io-googleapis/extension/index.ts @@ -0,0 +1,87 @@ +import { NodeCG } from "nodecg-types/types/server"; +import { Result, emptySuccess, success, error, ServiceBundle } from "nodecg-io-core"; +import { google, GoogleApis } from "googleapis"; +import type { Credentials } from "google-auth-library/build/src/auth/credentials"; +import type { OAuth2Client } from "google-auth-library/build/src/auth/oauth2client"; +import * as express from "express"; +import opn = require("open"); + +interface GoogleApisServiceConfig { + clientID: string; + clientSecret: string; + refreshToken?: string; + scopes?: string | string[]; +} + +export type GoogleApisServiceClient = GoogleApis; + +module.exports = (nodecg: NodeCG) => { + new GoogleApisService(nodecg, "googleapis", __dirname, "../googleapis-schema.json").register(); +}; + +class GoogleApisService extends ServiceBundle { + async validateConfig(_config: GoogleApisServiceConfig): Promise> { + return emptySuccess(); + } + + async createClient(config: GoogleApisServiceConfig): Promise> { + const auth = new google.auth.OAuth2({ + clientId: config.clientID, + clientSecret: config.clientSecret, + redirectUri: "http://localhost:9090/nodecg-io-googleapis/oauth2callback", + }); + + await this.refreshTokens(config, auth); + + const client = new GoogleApis({ auth }); + return success(client); + } + + stopClient(_client: GoogleApisServiceClient): void { + return; + } + + private async initialAuth(config: GoogleApisServiceConfig, auth: OAuth2Client): Promise { + const authURL = auth.generateAuthUrl({ + access_type: "offline", + prompt: "consent", + scope: config.scopes, + }); + + return new Promise((resolve, reject) => { + const router = express.Router(); + + router.get("/nodecg-io-googleapis/oauth2callback", async (req, res) => { + try { + const response = `Google Api connection successful! You may close this window now.`; + res.send(response); + + const { tokens } = await auth.getToken(req.query.code as string); + resolve(tokens); + } catch (e) { + reject(error(e)); + } + }); + + this.nodecg.mount(router); + opn(authURL, { wait: false }).then((cp) => cp.unref()); + }); + } + + private async refreshTokens(config: GoogleApisServiceConfig, auth: OAuth2Client) { + if (config.refreshToken) { + this.nodecg.log.info("Re-using saved refresh token."); + auth.setCredentials({ refresh_token: config.refreshToken }); + } else { + this.nodecg.log.info("No refresh token found. Starting auth flow to get one ..."); + auth.setCredentials(await this.initialAuth(config, auth)); + if (auth.credentials.refresh_token) { + config.refreshToken = auth.credentials.refresh_token; + } + } + + auth.on("tokens", (tokens) => { + if (tokens.refresh_token) config.refreshToken = tokens.refresh_token; + }); + } +} diff --git a/nodecg-io-gsheets/gsheets-schema.json b/nodecg-io-googleapis/googleapis-schema.json similarity index 86% rename from nodecg-io-gsheets/gsheets-schema.json rename to nodecg-io-googleapis/googleapis-schema.json index 3cc2be27a..a6d8508a6 100644 --- a/nodecg-io-gsheets/gsheets-schema.json +++ b/nodecg-io-googleapis/googleapis-schema.json @@ -14,6 +14,10 @@ "refreshToken": { "type": "string", "description": "Token that allows the client to refresh access tokens. This is set automatically after first login, you don't need to set it." + }, + "scopes": { + "type": "array", + "description": "Scope URLs for all used services." } }, "required": ["clientID", "clientSecret"] diff --git a/nodecg-io-youtube/package.json b/nodecg-io-googleapis/package.json similarity index 84% rename from nodecg-io-youtube/package.json rename to nodecg-io-googleapis/package.json index 60927db72..84faaaf27 100644 --- a/nodecg-io-youtube/package.json +++ b/nodecg-io-googleapis/package.json @@ -1,11 +1,11 @@ { - "name": "nodecg-io-youtube", + "name": "nodecg-io-googleapis", "version": "0.2.0", - "description": "Allows to connect and interact to youtube", + "description": "Allows to connect to and interact with many google-apis", "homepage": "https://nodecg.io/RELEASE/samples/youtube", "author": { - "name": "CodeOverflow team", - "url": "http://codeoverflow.org" + "name": "LarsVomMars", + "url": "https://github.com/LarsVomMars" }, "repository": { "type": "git", diff --git a/nodecg-io-gsheets/tsconfig.json b/nodecg-io-googleapis/tsconfig.json similarity index 100% rename from nodecg-io-gsheets/tsconfig.json rename to nodecg-io-googleapis/tsconfig.json diff --git a/nodecg-io-gsheets/extension/index.ts b/nodecg-io-gsheets/extension/index.ts deleted file mode 100644 index 2b8641928..000000000 --- a/nodecg-io-gsheets/extension/index.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { NodeCG } from "nodecg-types/types/server"; -import { Result, emptySuccess, success, error, ServiceBundle } from "nodecg-io-core"; -import { google, sheets_v4 } from "googleapis"; -import type { Credentials } from "google-auth-library/build/src/auth/credentials"; -import type { OAuth2Client } from "google-auth-library/build/src/auth/oauth2client"; -import * as express from "express"; -import opn = require("open"); - -interface GSheetsServiceConfig { - clientID: string; - clientSecret: string; - refreshToken?: string; -} - -export type GSheetsServiceClient = sheets_v4.Sheets; - -module.exports = (nodecg: NodeCG) => { - new GSheetsService(nodecg, "gsheets", __dirname, "../gsheets-schema.json").register(); -}; - -class GSheetsService extends ServiceBundle { - async validateConfig(_config: GSheetsServiceConfig): Promise> { - return emptySuccess(); - } - - async createClient(config: GSheetsServiceConfig): Promise> { - const auth = new google.auth.OAuth2({ - clientId: config.clientID, - clientSecret: config.clientSecret, - redirectUri: "http://localhost:9090/nodecg-io-gsheets/oauth2callback", - }); - if (config.refreshToken) { - this.nodecg.log.info("Re-using saved refresh token."); - auth.setCredentials({ - refresh_token: config.refreshToken, - }); - } else { - this.nodecg.log.info("No refresh token found. Starting auth flow to get one..."); - auth.setCredentials(await this.initialAuth(auth)); - if (auth.credentials.refresh_token) { - config.refreshToken = auth.credentials.refresh_token; - } - } - - // Save refresh tokens so they can be used next time to get a access token again - auth.on("tokens", (tokens) => { - if (tokens.refresh_token) { - config.refreshToken = tokens.refresh_token; - } - }); - - const client = new sheets_v4.Sheets({ auth }); - return success(client); - } - - private initialAuth(auth: OAuth2Client): Promise { - const authUrl = auth.generateAuthUrl({ - access_type: "offline", - scope: "https://www.googleapis.com/auth/spreadsheets", - prompt: "consent", - }); - - return new Promise((resolve, reject) => { - const router: express.Router = express.Router(); - - router.get("/nodecg-io-gsheets/oauth2callback", async (req, res) => { - try { - const params = req.query; - res.end(""); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { tokens } = await auth.getToken(params.code!.toString()); - resolve(tokens); - } catch (e) { - reject(error(e)); - } - }); - - this.nodecg.mount(router); - opn(authUrl, { wait: false }).then((cp) => cp.unref()); - }); - } - - stopClient(_client: GSheetsServiceClient): void { - // Cannot stop client - } -} diff --git a/nodecg-io-gsheets/package.json b/nodecg-io-gsheets/package.json deleted file mode 100644 index 38fda9360..000000000 --- a/nodecg-io-gsheets/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "nodecg-io-gsheets", - "version": "0.2.0", - "description": "Allow to control Google Sheets.", - "homepage": "https://nodecg.io/RELEASE", - "author": { - "name": "ExtremTechniker", - "url": "https://github.com/ExtremTechniker" - }, - "repository": { - "type": "git", - "url": "https://github.com/codeoverflow-org/nodecg-io.git", - "directory": "nodecg-io-gsheets" - }, - "files": [ - "**/*.js", - "**/*.js.map", - "**/*.d.ts", - "*.json" - ], - "main": "extension/index", - "scripts": { - "build": "tsc -b", - "watch": "tsc -b -w", - "clean": "tsc -b --clean" - }, - "keywords": [ - "nodecg-io", - "nodecg-bundle" - ], - "nodecg": { - "compatibleRange": "^1.1.1", - "bundleDependencies": { - "nodecg-io-core": "^0.2.0" - } - }, - "license": "MIT", - "devDependencies": { - "@types/node": "^15.0.2", - "nodecg-types": "^1.8.2", - "typescript": "^4.2.4" - }, - "dependencies": { - "@types/gapi": "^0.0.39", - "express": "^4.17.1", - "googleapis": "^73.0.0", - "nodecg-io-core": "^0.2.0", - "open": "^8.0.8" - } -} diff --git a/nodecg-io-youtube/extension/index.ts b/nodecg-io-youtube/extension/index.ts deleted file mode 100644 index b71b3a005..000000000 --- a/nodecg-io-youtube/extension/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { NodeCG } from "nodecg-types/types/server"; -import { Result, emptySuccess, success, error, ServiceBundle } from "nodecg-io-core"; -import { google, youtube_v3 } from "googleapis"; -import type { Credentials } from "google-auth-library/build/src/auth/credentials"; -import type { OAuth2Client } from "google-auth-library/build/src/auth/oauth2client"; -import * as express from "express"; -import opn = require("open"); - -interface YoutubeServiceConfig { - clientID: string; - clientSecret: string; - refreshToken?: string; -} - -export type YoutubeServiceClient = youtube_v3.Youtube; - -module.exports = (nodecg: NodeCG) => { - new YoutubeService(nodecg, "youtube", __dirname, "../youtube-schema.json").register(); -}; - -class YoutubeService extends ServiceBundle { - async validateConfig(_config: YoutubeServiceConfig): Promise> { - return emptySuccess(); - } - - async createClient(config: YoutubeServiceConfig): Promise> { - const auth = new google.auth.OAuth2({ - clientId: config.clientID, - clientSecret: config.clientSecret, - redirectUri: "http://localhost:9090/nodecg-io-youtube/oauth2callback", - }); - - if (config.refreshToken) { - this.nodecg.log.info("Re-using saved refresh token."); - auth.setCredentials({ - refresh_token: config.refreshToken, - }); - } else { - this.nodecg.log.info("No refresh token found. Starting auth flow to get one..."); - auth.setCredentials(await this.initialAuth(auth)); - if (auth.credentials.refresh_token) { - config.refreshToken = auth.credentials.refresh_token; - } - } - - // Save refresh tokens so they can be used next time to get a access token again - auth.on("tokens", (tokens) => { - if (tokens.refresh_token) { - config.refreshToken = tokens.refresh_token; - } - }); - - const client = new youtube_v3.Youtube({ auth }); - return success(client); - } - - stopClient(_client: YoutubeServiceClient): void { - // Cannot stop client - } - - private initialAuth(auth: OAuth2Client): Promise { - const authUrl = auth.generateAuthUrl({ - access_type: "offline", - scope: "https://www.googleapis.com/auth/youtube", - prompt: "consent", - }); - - return new Promise((resolve, reject) => { - const router: express.Router = express.Router(); - - router.get("/nodecg-io-youtube/oauth2callback", async (req, res) => { - try { - const params = req.query; - - const callbackWebsite = - "YouTube connection successful! You may close this window now."; - res.send(callbackWebsite); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { tokens } = await auth.getToken(params.code!.toString()); - resolve(tokens); - } catch (e) { - reject(error(e)); - } - }); - - this.nodecg.mount(router); - opn(authUrl, { wait: false }).then((cp) => cp.unref()); - }); - } -} diff --git a/nodecg-io-youtube/tsconfig.json b/nodecg-io-youtube/tsconfig.json deleted file mode 100644 index 1c8405620..000000000 --- a/nodecg-io-youtube/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../tsconfig.common.json" -} diff --git a/nodecg-io-youtube/youtube-schema.json b/nodecg-io-youtube/youtube-schema.json deleted file mode 100644 index 3cc2be27a..000000000 --- a/nodecg-io-youtube/youtube-schema.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "additionalProperties": false, - "properties": { - "clientID": { - "type": "string", - "description": "The oauth client id https://console.cloud.google.com/apis/credentials/oauthclient" - }, - "clientSecret": { - "type": "string", - "description": "The oauth client secret https://console.cloud.google.com/apis/credentials/oauthclient" - }, - "refreshToken": { - "type": "string", - "description": "Token that allows the client to refresh access tokens. This is set automatically after first login, you don't need to set it." - } - }, - "required": ["clientID", "clientSecret"] -} diff --git a/samples/gsheets/extension/index.ts b/samples/gsheets/extension/index.ts index fda54890e..ed3d4cd9f 100644 --- a/samples/gsheets/extension/index.ts +++ b/samples/gsheets/extension/index.ts @@ -1,15 +1,16 @@ import { NodeCG } from "nodecg-types/types/server"; -import { GSheetsServiceClient } from "nodecg-io-gsheets"; +import { GoogleApisServiceClient } from "nodecg-io-googleapis"; import { requireService } from "nodecg-io-core"; module.exports = function (nodecg: NodeCG) { nodecg.log.info("Sample bundle for Google Sheets started"); - const gsheets = requireService(nodecg, "gsheets"); + const googleApis = requireService(nodecg, "googleapis"); - gsheets?.onAvailable(async (client) => { + googleApis?.onAvailable(async (client) => { + const gsheets = client.sheets("v4"); try { - const data = await client.spreadsheets.values.get( + const data = await gsheets.spreadsheets.values.get( { spreadsheetId: "", //Spreadsheet ID, URL is formatted https://docs.google.de/spreadsheets/d//edit range: "", //The sheet name, witch will used to get the data. @@ -24,5 +25,5 @@ module.exports = function (nodecg: NodeCG) { } }); - gsheets?.onUnavailable(() => nodecg.log.info("GSheets client has been unset.")); + googleApis?.onUnavailable(() => nodecg.log.info("GSheets client has been unset.")); }; diff --git a/samples/gsheets/package.json b/samples/gsheets/package.json index bfcfeb4d6..9fd5fa84b 100644 --- a/samples/gsheets/package.json +++ b/samples/gsheets/package.json @@ -5,7 +5,7 @@ "nodecg": { "compatibleRange": "^1.1.1", "bundleDependencies": { - "nodecg-io-gsheets": "^0.2.0" + "nodecg-io-googleapis": "^0.2.0" } }, "scripts": { @@ -18,7 +18,7 @@ "@types/node": "^15.0.2", "nodecg-types": "^1.8.2", "nodecg-io-core": "^0.2.0", - "nodecg-io-gsheets": "^0.2.0", + "nodecg-io-googleapis": "^0.2.0", "typescript": "^4.2.4" } } diff --git a/samples/youtube-playlist/extension/index.ts b/samples/youtube-playlist/extension/index.ts index 9c252c6af..3fba31088 100644 --- a/samples/youtube-playlist/extension/index.ts +++ b/samples/youtube-playlist/extension/index.ts @@ -1,16 +1,18 @@ import { NodeCG } from "nodecg-types/types/server"; -import { YoutubeServiceClient } from "nodecg-io-youtube"; -import { youtube_v3 } from "googleapis"; +import { GoogleApisServiceClient } from "nodecg-io-googleapis"; import { requireService } from "nodecg-io-core"; +import type { youtube_v3 } from "googleapis"; -module.exports = function (nodecg: NodeCG) { +module.exports = (nodecg: NodeCG) => { nodecg.log.info("Sample bundle for youtube started"); - const youtube = requireService(nodecg, "youtube"); + const googleApis = requireService(nodecg, "googleapis"); + + googleApis?.onAvailable(async (client) => { + const youtube = client.youtube("v3"); - youtube?.onAvailable(async (client) => { nodecg.log.info("Youtube client has been updated, listing videos from playlist."); - const resp = await client.playlists.list({ + const resp = await youtube.playlists.list({ part: ["id", "snippet"], id: ["PL9oBXB6tQnlX013V1v20WkfzI9R2zamHi"], }); @@ -24,5 +26,5 @@ module.exports = function (nodecg: NodeCG) { } }); - youtube?.onUnavailable(() => nodecg.log.info("Youtube client has been unset.")); + googleApis?.onUnavailable(() => nodecg.log.info("Youtube client has been unset.")); }; diff --git a/samples/youtube-playlist/package.json b/samples/youtube-playlist/package.json index a768e98e3..0c620334f 100644 --- a/samples/youtube-playlist/package.json +++ b/samples/youtube-playlist/package.json @@ -5,7 +5,7 @@ "nodecg": { "compatibleRange": "^1.1.1", "bundleDependencies": { - "nodecg-io-youtube": "^0.2.0" + "nodecg-io-googleapis": "^0.2.0" } }, "scripts": { @@ -18,7 +18,7 @@ "@types/node": "^15.0.2", "nodecg-types": "^1.8.2", "nodecg-io-core": "^0.2.0", - "nodecg-io-youtube": "^0.2.0", + "nodecg-io-googleapis": "^0.2.0", "typescript": "^4.2.4" } }