Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Commit 8b29117

Browse files
authored
Merge pull request #225 from ExtremTechniker/feature/224-gsheets
Add nodecg-io-gsheets service
2 parents 108dbf4 + 85e899a commit 8b29117

File tree

8 files changed

+209
-0
lines changed

8 files changed

+209
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ nodecg-io is the successor of [ChatOverflow](https://github.com/codeoverflow-org
1919
- [x] Android (using adb)
2020
- [x] CurseForge
2121
- [x] Discord
22+
- [x] Google Sheets (gsheets)
2223
- [x] IntelliJ IDEs
2324
- [x] IRC (Internet Relay Chat)
2425
- [x] MIDI Input

nodecg-io-gsheets/extension/index.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { NodeCG } from "nodecg/types/server";
2+
import { Result, emptySuccess, success, error, ServiceBundle } from "nodecg-io-core";
3+
import { google, sheets_v4 } from "googleapis";
4+
import type { Credentials } from "google-auth-library/build/src/auth/credentials";
5+
import type { OAuth2Client } from "google-auth-library/build/src/auth/oauth2client";
6+
import * as express from "express";
7+
import opn = require("open");
8+
9+
interface GSheetsServiceConfig {
10+
clientID: string;
11+
clientSecret: string;
12+
refreshToken?: string;
13+
}
14+
15+
export type GSheetsServiceClient = sheets_v4.Sheets;
16+
17+
module.exports = (nodecg: NodeCG) => {
18+
new GSheetsService(nodecg, "gsheets", __dirname, "../gsheets-schema.json").register();
19+
};
20+
21+
class GSheetsService extends ServiceBundle<GSheetsServiceConfig, GSheetsServiceClient> {
22+
async validateConfig(_config: GSheetsServiceConfig): Promise<Result<void>> {
23+
return emptySuccess();
24+
}
25+
26+
async createClient(config: GSheetsServiceConfig): Promise<Result<GSheetsServiceClient>> {
27+
const auth = new google.auth.OAuth2({
28+
clientId: config.clientID,
29+
clientSecret: config.clientSecret,
30+
redirectUri: "http://localhost:9090/nodecg-io-gsheets/oauth2callback",
31+
});
32+
if (config.refreshToken) {
33+
this.nodecg.log.info("Re-using saved refresh token.");
34+
auth.setCredentials({
35+
refresh_token: config.refreshToken,
36+
});
37+
} else {
38+
this.nodecg.log.info("No refresh token found. Starting auth flow to get one...");
39+
auth.setCredentials(await this.initialAuth(auth));
40+
if (auth.credentials.refresh_token) {
41+
config.refreshToken = auth.credentials.refresh_token;
42+
}
43+
}
44+
45+
// Save refresh tokens so they can be used next time to get a access token again
46+
auth.on("tokens", (tokens) => {
47+
if (tokens.refresh_token) {
48+
config.refreshToken = tokens.refresh_token;
49+
}
50+
});
51+
52+
const client = new sheets_v4.Sheets({ auth });
53+
return success(client);
54+
}
55+
56+
private initialAuth(auth: OAuth2Client): Promise<Credentials> {
57+
const authUrl = auth.generateAuthUrl({
58+
access_type: "offline",
59+
scope: "https://www.googleapis.com/auth/spreadsheets",
60+
prompt: "consent",
61+
});
62+
63+
return new Promise((resolve, reject) => {
64+
const router: express.Router = express.Router();
65+
66+
router.get("/nodecg-io-gsheets/oauth2callback", async (req, res) => {
67+
try {
68+
const params = req.query;
69+
res.end("<script>open(location, '_self').close();</script>");
70+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
71+
const { tokens } = await auth.getToken(params.code!.toString());
72+
resolve(tokens);
73+
} catch (e) {
74+
reject(error(e));
75+
}
76+
});
77+
78+
this.nodecg.mount(router);
79+
opn(authUrl, { wait: false }).then((cp) => cp.unref());
80+
});
81+
}
82+
83+
stopClient(_client: GSheetsServiceClient): void {
84+
// Cannot stop client
85+
}
86+
}

nodecg-io-gsheets/gsheets-schema.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"additionalProperties": false,
5+
"properties": {
6+
"clientID": {
7+
"type": "string",
8+
"description": "The oauth client id https://console.cloud.google.com/apis/credentials/oauthclient"
9+
},
10+
"clientSecret": {
11+
"type": "string",
12+
"description": "The oauth client secret https://console.cloud.google.com/apis/credentials/oauthclient"
13+
},
14+
"refreshToken": {
15+
"type": "string",
16+
"description": "Token that allows the client to refresh access tokens. This is set automatically after first login, you don't need to set it."
17+
}
18+
},
19+
"required": ["clientID", "clientSecret"]
20+
}

nodecg-io-gsheets/package.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "nodecg-io-gsheets",
3+
"version": "0.2.0",
4+
"description": "Allow to control Google Sheets.",
5+
"homepage": "https://nodecg.io",
6+
"author": {
7+
"name": "ExtremTechniker",
8+
"url": "https://github.com/ExtremTechniker"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "https://github.com/codeoverflow-org/nodecg-io.git",
13+
"directory": "nodecg-io-gsheets"
14+
},
15+
"main": "extension",
16+
"scripts": {
17+
"build": "tsc -b",
18+
"watch": "tsc -b -w",
19+
"clean": "tsc -b --clean"
20+
},
21+
"keywords": [
22+
"nodecg-io",
23+
"nodecg-bundle"
24+
],
25+
"nodecg": {
26+
"compatibleRange": "^1.1.1",
27+
"bundleDependencies": {
28+
"nodecg-io-core": "^0.2.0"
29+
}
30+
},
31+
"license": "MIT",
32+
"devDependencies": {
33+
"@types/node": "^14.14.33",
34+
"nodecg": "^1.8.1",
35+
"typescript": "^4.2.3"
36+
},
37+
"dependencies": {
38+
"@types/gapi": "^0.0.39",
39+
"express": "^4.17.1",
40+
"googleapis": "^67.1.1",
41+
"nodecg-io-core": "^0.2.0",
42+
"open": "^8.0.2"
43+
}
44+
}

nodecg-io-gsheets/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../tsconfig.common.json"
3+
}

samples/gsheets/extension/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NodeCG } from "nodecg/types/server";
2+
import { GSheetsServiceClient } from "nodecg-io-gsheets";
3+
import { requireService } from "nodecg-io-core";
4+
5+
module.exports = function (nodecg: NodeCG) {
6+
nodecg.log.info("Sample bundle for Google Sheets started");
7+
8+
const gsheets = requireService<GSheetsServiceClient>(nodecg, "gsheets");
9+
10+
gsheets?.onAvailable(async (client) => {
11+
try {
12+
const data = await client.spreadsheets.values.get(
13+
{
14+
spreadsheetId: "<ID>", //Spreadsheet ID, URL is formatted https://docs.google.de/spreadsheets/d/<ID>/edit
15+
range: "<tableSheetName>", //The sheet name, witch will used to get the data.
16+
},
17+
undefined,
18+
);
19+
data.data.values = data.data.values?.filter((e) => !(!e[0] || 0 === e[0].length)); // filter out rows when column A is a empty String
20+
nodecg.log.info(data.data);
21+
} catch (error) {
22+
nodecg.log.error("Could it be, that you haven't specified the spreadsheetId and the range?");
23+
nodecg.log.error(error);
24+
}
25+
});
26+
27+
gsheets?.onUnavailable(() => nodecg.log.info("GSheets client has been unset."));
28+
};

samples/gsheets/package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "gsheets",
3+
"version": "0.2.0",
4+
"private": true,
5+
"nodecg": {
6+
"compatibleRange": "^1.1.1",
7+
"bundleDependencies": {
8+
"nodecg-io-gsheets": "^0.2.0"
9+
}
10+
},
11+
"scripts": {
12+
"build": "tsc -b",
13+
"watch": "tsc -b -w",
14+
"clean": "tsc -b --clean"
15+
},
16+
"license": "MIT",
17+
"dependencies": {
18+
"@types/node": "^14.14.33",
19+
"nodecg": "^1.8.1",
20+
"typescript": "^4.2.3",
21+
"nodecg-io-core": "^0.2.0",
22+
"nodecg-io-gsheets": "^0.2.0"
23+
}
24+
}

samples/gsheets/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../../tsconfig.common.json"
3+
}

0 commit comments

Comments
 (0)