diff options
Diffstat (limited to 'src/server/apis')
| -rw-r--r-- | src/server/apis/google/GoogleApiServerUtils.ts | 130 | ||||
| -rw-r--r-- | src/server/apis/google/GooglePhotosUploadUtils.ts | 176 | ||||
| -rw-r--r-- | src/server/apis/youtube/youtubeApiSample.d.ts | 2 | ||||
| -rw-r--r-- | src/server/apis/youtube/youtubeApiSample.js | 179 |
4 files changed, 487 insertions, 0 deletions
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts new file mode 100644 index 000000000..8785cd974 --- /dev/null +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -0,0 +1,130 @@ +import { google, docs_v1, slides_v1 } from "googleapis"; +import { createInterface } from "readline"; +import { readFile, writeFile } from "fs"; +import { OAuth2Client } from "google-auth-library"; +import { Opt } from "../../../new_fields/Doc"; +import { GlobalOptions } from "googleapis-common"; +import { GaxiosResponse } from "gaxios"; + +/** + * Server side authentication for Google Api queries. + */ +export namespace GoogleApiServerUtils { + + // If modifying these scopes, delete token.json. + const prefix = 'https://www.googleapis.com/auth/'; + const SCOPES = [ + 'documents.readonly', + 'documents', + 'presentations', + 'presentations.readonly', + 'drive', + 'drive.file', + ]; + + export const parseBuffer = (data: Buffer) => JSON.parse(data.toString()); + + export enum Service { + Documents = "Documents", + Slides = "Slides" + } + + + export interface CredentialPaths { + credentials: string; + token: string; + } + + export type ApiResponse = Promise<GaxiosResponse>; + export type ApiRouter = (endpoint: Endpoint, paramters: any) => ApiResponse; + export type ApiHandler = (parameters: any) => ApiResponse; + export type Action = "create" | "retrieve" | "update"; + + export type Endpoint = { get: ApiHandler, create: ApiHandler, batchUpdate: ApiHandler }; + export type EndpointParameters = GlobalOptions & { version: "v1" }; + + export const GetEndpoint = async (sector: string, paths: CredentialPaths) => { + return new Promise<Opt<Endpoint>>((resolve, reject) => { + readFile(paths.credentials, (err, credentials) => { + if (err) { + reject(err); + return console.log('Error loading client secret file:', err); + } + return authorize(parseBuffer(credentials), paths.token).then(auth => { + let routed: Opt<Endpoint>; + let parameters: EndpointParameters = { auth, version: "v1" }; + switch (sector) { + case Service.Documents: + routed = google.docs(parameters).documents; + break; + case Service.Slides: + routed = google.slides(parameters).presentations; + break; + } + resolve(routed); + }); + }); + }); + }; + + + /** + * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client + * @param {Object} credentials The authorization client credentials. + */ + export function authorize(credentials: any, token_path: string): Promise<OAuth2Client> { + const { client_secret, client_id, redirect_uris } = credentials.installed; + const oAuth2Client = new google.auth.OAuth2( + client_id, client_secret, redirect_uris[0]); + + return new Promise<OAuth2Client>((resolve, reject) => { + readFile(token_path, (err, token) => { + // Check if we have previously stored a token. + if (err) { + return getNewToken(oAuth2Client, token_path).then(resolve, reject); + } + oAuth2Client.setCredentials(parseBuffer(token)); + resolve(oAuth2Client); + }); + }); + } + + /** + * Get and store new token after prompting for user authorization, and then + * execute the given callback with the authorized OAuth2 client. + * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for. + * @param {getEventsCallback} callback The callback for the authorized client. + */ + function getNewToken(oAuth2Client: OAuth2Client, token_path: string) { + return new Promise<OAuth2Client>((resolve, reject) => { + const authUrl = oAuth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES.map(relative => prefix + relative), + }); + console.log('Authorize this app by visiting this url:', authUrl); + const rl = createInterface({ + input: process.stdin, + output: process.stdout, + }); + rl.question('Enter the code from that page here: ', (code) => { + rl.close(); + oAuth2Client.getToken(code, (err, token) => { + if (err || !token) { + reject(err); + return console.error('Error retrieving access token', err); + } + oAuth2Client.setCredentials(token); + // Store the token to disk for later program executions + writeFile(token_path, JSON.stringify(token), (err) => { + if (err) { + console.error(err); + reject(err); + } + console.log('Token stored to', token_path); + }); + resolve(oAuth2Client); + }); + }); + }); + } +}
\ No newline at end of file diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts new file mode 100644 index 000000000..35f986250 --- /dev/null +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -0,0 +1,176 @@ +import request = require('request-promise'); +import { GoogleApiServerUtils } from './GoogleApiServerUtils'; +import * as fs from 'fs'; +import { Utils } from '../../../Utils'; +import * as path from 'path'; +import { Opt } from '../../../new_fields/Doc'; +import * as sharp from 'sharp'; + +const uploadDirectory = path.join(__dirname, "../../public/files/"); + +export namespace GooglePhotosUploadUtils { + + export interface Paths { + uploadDirectory: string; + credentialsPath: string; + tokenPath: string; + } + + export interface MediaInput { + url: string; + description: string; + } + + const prepend = (extension: string) => `https://photoslibrary.googleapis.com/v1/${extension}`; + const headers = (type: string) => ({ + 'Content-Type': `application/${type}`, + 'Authorization': Bearer, + }); + + let Bearer: string; + let Paths: Paths; + + export const initialize = async (paths: Paths) => { + Paths = paths; + const { tokenPath, credentialsPath } = paths; + const token = await GoogleApiServerUtils.RetrieveAccessToken({ tokenPath, credentialsPath }); + Bearer = `Bearer ${token}`; + }; + + export const DispatchGooglePhotosUpload = async (url: string) => { + const body = await request(url, { encoding: null }); + const parameters = { + method: 'POST', + headers: { + ...headers('octet-stream'), + 'X-Goog-Upload-File-Name': path.basename(url), + 'X-Goog-Upload-Protocol': 'raw' + }, + uri: prepend('uploads'), + body + }; + return new Promise<any>(resolve => request(parameters, (error, _response, body) => resolve(error ? undefined : body))); + }; + + export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }) => { + return new Promise<any>((resolve, reject) => { + const parameters = { + method: 'POST', + headers: headers('json'), + uri: prepend('mediaItems:batchCreate'), + body: { newMediaItems } as any, + json: true + }; + album && (parameters.body.albumId = album.id); + request(parameters, (error, _response, body) => { + if (error) { + reject(error); + } else { + resolve(body); + } + }); + }); + }; + +} + +export namespace DownloadUtils { + + export interface Size { + width: number; + suffix: string; + } + + export const Sizes: { [size: string]: Size } = { + SMALL: { width: 100, suffix: "_s" }, + MEDIUM: { width: 400, suffix: "_m" }, + LARGE: { width: 900, suffix: "_l" }, + }; + + const png = ".png"; + const pngs = [".png", ".PNG"]; + const jpgs = [".jpg", ".JPG", ".jpeg", ".JPEG"]; + const formats = [".jpg", ".png", ".gif"]; + const size = "content-length"; + const type = "content-type"; + + export interface UploadInformation { + mediaPaths: string[]; + fileNames: { [key: string]: string }; + contentSize?: number; + contentType?: string; + } + + const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`; + const sanitize = (filename: string) => filename.replace(/\s+/g, "_"); + + export const UploadImage = async (url: string, filename?: string, prefix = ""): Promise<Opt<UploadInformation>> => { + const resolved = filename ? sanitize(filename) : generate(prefix, url); + const extension = path.extname(url) || path.extname(resolved) || png; + let information: UploadInformation = { + mediaPaths: [], + fileNames: { clean: resolved } + }; + const { isLocal, stream, normalized } = classify(url); + url = normalized; + if (!isLocal) { + const metadata = (await new Promise<any>((resolve, reject) => { + request.head(url, async (error, res) => { + if (error) { + return reject(error); + } + resolve(res); + }); + })).headers; + information.contentSize = parseInt(metadata[size]); + information.contentType = metadata[type]; + } + return new Promise<UploadInformation>(async (resolve, reject) => { + const resizers = [ + { resizer: sharp().rotate(), suffix: "_o" }, + ...Object.values(Sizes).map(size => ({ + resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(), + suffix: size.suffix + })) + ]; + if (pngs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.png()); + } else if (jpgs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.jpeg()); + } else if (!formats.includes(extension.toLowerCase())) { + return reject(); + } + for (let resizer of resizers) { + const suffix = resizer.suffix; + let mediaPath: string; + await new Promise<void>(resolve => { + const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension; + information.mediaPaths.push(mediaPath = uploadDirectory + filename); + information.fileNames[suffix] = filename; + stream(url).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) + .on('close', resolve) + .on('error', reject); + }); + } + resolve(information); + }); + }; + + const classify = (url: string) => { + const isLocal = /Dash-Web(\\|\/)src(\\|\/)server(\\|\/)public(\\|\/)files/g.test(url); + return { + isLocal, + stream: isLocal ? fs.createReadStream : request, + normalized: isLocal ? path.normalize(url) : url + }; + }; + + export const createIfNotExists = async (path: string) => { + if (await new Promise<boolean>(resolve => fs.exists(path, resolve))) { + return true; + } + return new Promise<boolean>(resolve => fs.mkdir(path, error => resolve(error === null))); + }; + + export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null))); +}
\ No newline at end of file diff --git a/src/server/apis/youtube/youtubeApiSample.d.ts b/src/server/apis/youtube/youtubeApiSample.d.ts new file mode 100644 index 000000000..427f54608 --- /dev/null +++ b/src/server/apis/youtube/youtubeApiSample.d.ts @@ -0,0 +1,2 @@ +declare const YoutubeApi: any; +export = YoutubeApi;
\ No newline at end of file diff --git a/src/server/apis/youtube/youtubeApiSample.js b/src/server/apis/youtube/youtubeApiSample.js new file mode 100644 index 000000000..50b3c7b38 --- /dev/null +++ b/src/server/apis/youtube/youtubeApiSample.js @@ -0,0 +1,179 @@ +const fs = require('fs'); +const readline = require('readline'); +const { google } = require('googleapis'); +const OAuth2 = google.auth.OAuth2; + + +// If modifying these scopes, delete your previously saved credentials +// at ~/.credentials/youtube-nodejs-quickstart.json +let SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; +let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH || + process.env.USERPROFILE) + '/.credentials/'; +let TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json'; + +module.exports.readApiKey = (callback) => { + fs.readFile('client_secret.json', function processClientSecrets(err, content) { + if (err) { + console.log('Error loading client secret file: ' + err); + return; + } + callback(content); + }); +} + +module.exports.authorizedGetChannel = (apiKey) => { + //this didnt get called + // Authorize a client with the loaded credentials, then call the YouTube API. + authorize(JSON.parse(apiKey), getChannel); +} + +module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => { + authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack }); +} + +module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => { + authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack }); +} + + +/** + * Create an OAuth2 client with the given credentials, and then execute the + * given callback function. + * + * @param {Object} credentials The authorization client credentials. + * @param {function} callback The callback to call with the authorized client. + */ +function authorize(credentials, callback, args = {}) { + let clientSecret = credentials.installed.client_secret; + let clientId = credentials.installed.client_id; + let redirectUrl = credentials.installed.redirect_uris[0]; + let oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl); + + // Check if we have previously stored a token. + fs.readFile(TOKEN_PATH, function (err, token) { + if (err) { + getNewToken(oauth2Client, callback); + } else { + oauth2Client.credentials = JSON.parse(token); + callback(oauth2Client, args); + } + }); +} + +/** + * Get and store new token after prompting for user authorization, and then + * execute the given callback with the authorized OAuth2 client. + * + * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for. + * @param {getEventsCallback} callback The callback to call with the authorized + * client. + */ +function getNewToken(oauth2Client, callback) { + var authUrl = oauth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES + }); + console.log('Authorize this app by visiting this url: ', authUrl); + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + rl.question('Enter the code from that page here: ', function (code) { + rl.close(); + oauth2Client.getToken(code, function (err, token) { + if (err) { + console.log('Error while trying to retrieve access token', err); + return; + } + oauth2Client.credentials = token; + storeToken(token); + callback(oauth2Client); + }); + }); +} + +/** + * Store token to disk be used in later program executions. + * + * @param {Object} token The token to store to disk. + */ +function storeToken(token) { + try { + fs.mkdirSync(TOKEN_DIR); + } catch (err) { + if (err.code != 'EEXIST') { + throw err; + } + } + fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => { + if (err) throw err; + console.log('Token stored to ' + TOKEN_PATH); + }); + console.log('Token stored to ' + TOKEN_PATH); +} + +/** + * Lists the names and IDs of up to 10 files. + * + * @param {google.auth.OAuth2} auth An authorized OAuth2 client. + */ +function getChannel(auth) { + var service = google.youtube('v3'); + service.channels.list({ + auth: auth, + part: 'snippet,contentDetails,statistics', + forUsername: 'GoogleDevelopers' + }, function (err, response) { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + var channels = response.data.items; + if (channels.length == 0) { + console.log('No channel found.'); + } else { + console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + + 'it has %s views.', + channels[0].id, + channels[0].snippet.title, + channels[0].statistics.viewCount); + } + }); +} + +function getVideos(auth, args) { + let service = google.youtube('v3'); + service.search.list({ + auth: auth, + part: 'id, snippet', + type: 'video', + q: args.userInput, + maxResults: 10 + }, function (err, response) { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + let videos = response.data.items; + args.callBack(videos); + }); +} + +function getVideoDetails(auth, args) { + if (args.videoIds === undefined) { + return; + } + let service = google.youtube('v3'); + service.videos.list({ + auth: auth, + part: 'contentDetails, statistics', + id: args.videoIds + }, function (err, response) { + if (err) { + console.log('The API returned an error from details: ' + err); + return; + } + let videoDetails = response.data.items; + args.callBack(videoDetails); + }); +}
\ No newline at end of file |
