import mongoose = require ( 'mongoose' ) ; import { webhookDoc , webhookSchema , webhookObject } from './database/schemas' ; import { google } from "googleapis" ; import { Bot } from '.' ; import { googleAPIKey , callback } from "./bot-config.json" ; import request = require ( "request" ) ; import express = require ( "express" ) ; import bodyParser = require ( "body-parser" ) ; import xml2js = require ( 'xml2js' ) ; import { sendMentionMessage } from './utils/messages' ; import { durations } from './utils/time' ; const xmlParser = new xml2js . Parser ( { explicitArray : false } ) ; export param { string } input returns export async function getYTChannelID ( input ) { if ( input . includes ( "channel/" ) ) { return input . split ( "channel/" ) [ 1 ] ; } if ( input . includes ( "user/" ) ) { input = input . split ( "user/" ) [ 1 ] ; } var response = await google . youtube ( "v3" ) . channels . list ( { key : googleAPIKey , forUsername : input , part : "id" } ) ; if ( response . data && response . data . items && response . data . items [ 0 ] && response . data . items [ 0 ] . id ) { return response . data . items [ 0 ] . id ; } } export param { string } YTChannelID returns export async function YTChannelExists ( YTChannelID ) { var response = await google . youtube ( "v3" ) . channels . list ( { key : googleAPIKey , id : YTChannelID , part : "id" } ) ; if ( response . data && response . data . items && response . data . items [ 0 ] ) return true ; return false ; } export param { express . Application } app export function addYoutubeCatcher ( app ) { app . use ( callback . path + '/youtube' , bodyParser . text ( { type : 'application/atom+xml' } ) ) ; app . get ( callback . path + '/youtube' , ( req , res ) => { res . status ( 200 ) . send ( req . query [ 'hub.challenge' ] ) ; } ) ; app . post ( callback . path + '/youtube' , function ( req , res ) { xmlParser . parseString ( req . body , async ( error , result ) => { if ( ! Bot . youtube . webhooks ) return ; if ( error ) { res . status ( 422 ) . json ( { code : 'xml_parse_error' , details : "Something went wrong while parsing the XML" , error } ) ; } else { var video = result . feed . entry ; if ( ! video || ! video . updated || ! video . published || ! video [ "yt:videoId" ] || ! video . author || ! video [ "yt:channelId" ] ) { res . sendStatus ( 400 ) ; return ; } var publishUpdateDifference = Date . parse ( video . updated ) - Date . parse ( video . published ) ; const type = ( publishUpdateDifference > 300000 ) ? 'updated' : 'published' ; if ( type != "published" ) { res . sendStatus ( 200 ) ; return ; } var webhookDocs = await Bot . youtube . webhooks . find ( { feed : video [ "yt:channelId" ] } ) . exec ( ) ; if ( webhookDocs . length == 0 ) { res . sendStatus ( 404 ) ; return ; } const link = "https://youtu.be/" + video [ "yt:videoId" ] ; for ( const webhookDoc of webhookDocs ) { const webhookObject = webhookDoc . toObject ( ) ; var guild = Bot . client . guilds . get ( webhookObject . guild ) ; if ( ! guild ) { console . warn ( "in youtube webhookcatcher guild " + webhookObject . guild + " wasn't found" ) ; webhookDoc . remove ( ) ; } var channel = guild . channels . get ( webhookObject . channel ) ; if ( ! channel ) webhookDoc . remove ( ) ; var message = webhookObject . message ; message = message . replace ( "{{link}}" , link ) . replace ( "{{title}}" , video . title ) . replace ( "{{channelName}}" , video . author . name ) . replace ( "{{channelLink}}" , video . author . uri ) ; if ( message . includes ( "{{role:" ) ) { sendMentionMessage ( guild , channel , message , false ) ; } else { channel . send ( message , { disableEveryone : false } ) ; } Bot . mStats . logMessageSend ( ) ; } res . sendStatus ( 200 ) ; } } ) ; } ) ; } export class export class YTWebhookManager { type { mongoose . Connection } memberof connection : mongoose . Connection ; type { mongoose . Model < webhookDoc > } memberof webhooks : mongoose . Model < webhookDoc > ; param { string } URI param { string } authDB memberof constructor ( clusterInfo ) { this . connection = mongoose . createConnection ( clusterInfo . url + '/webhooks' + clusterInfo . suffix , { useNewUrlParser : true } ) ; this . connection . on ( 'error' , error => { console . error ( 'connection error:' , error ) ; Bot . mStats . logError ( error ) ; } ) ; this . connection . once ( 'open' , function ( ) { console . log ( 'connected to /webhooks database' ) ; Bot . youtube . webhooks = Bot . youtube . connection . model ( "youtubeWebhook" , webhookSchema , "youtube" ) ; } ) ; } param { mongoose . Schema . Types . ObjectId } webhookID returns memberof get ( webhookID ) { return this . webhooks . findById ( webhookID ) . exec ( ) ; } memberof async resubWebhooks ( ) { let channelIDs = await this . webhooks . distinct ( 'feed' ) . exec ( ) ; for ( const channelID of channelIDs ) { this . subToChannel ( channelID , true ) ; } console . log ( `Resubbed to ${ channelIDs . length } YouTube webhooks` ) ; } private param { string } YTChannelID param { boolean } subscribe returns memberof private async subToChannel ( YTChannelID , subscribe ) { return new Promise ( ( resolve , reject ) => { request . post ( 'https://pubsubhubbub.appspot.com/subscribe' , { form : { 'hub.mode' : subscribe ? 'subscribe' : 'unsubscribe' , 'hub.callback' : `http://${ callback . URL }:${ callback . port }${ callback . path }/youtube` , 'hub.topic' : 'https://www.youtube.com/xml/feeds/videos.xml?channel_id=' + YTChannelID , 'hub.lease_seconds' : durations . thirtyDays * 240 / 1000 } } , ( error , response , body ) => { if ( error ) reject ( error ) ; if ( response . statusCode != 202 ) { reject ( 'Invalid status code <' + response . statusCode + '>' ) ; } resolve ( body ) ; } ) ; } ) ; } param { string } guildID param { string } channelID param { string } YTChannelID param { string } message returns memberof async createWebhook ( guildID , channelID , YTChannelID , message ) { var sameFeedWebhook = await this . webhooks . findOne ( { feed : YTChannelID } ) ; if ( sameFeedWebhook && sameFeedWebhook . guild == guildID && sameFeedWebhook . channel == channelID ) return undefined ; if ( ! sameFeedWebhook ) { try { this . subToChannel ( YTChannelID , true ) ; } catch ( e ) { console . error ( 'error while subscribing to youtube webhook:' , e ) ; Bot . mStats . logError ( e ) ; return null ; } } var guildDoc = await Bot . database . findGuildDoc ( guildID ) ; if ( ! guildDoc ) return undefined ; var webhhookDoc = new this . webhooks ( { feed : YTChannelID , guild : guildID , channel : channelID , message : message } ) ; await webhhookDoc . save ( ) ; if ( ! guildDoc . webhooks ) guildDoc . webhooks = { } ; if ( ! guildDoc . webhooks . youtube ) guildDoc . webhooks . youtube = [ ] ; guildDoc . webhooks . youtube . push ( webhhookDoc . id ) ; await guildDoc . save ( ) ; Bot . mStats . logWebhookAction ( 'youtube' , 'created' ) ; return webhhookDoc ; } param { string } guildID param { string } channelID param { string } YTChannelID returns memberof async deleteWebhook ( guildID , channelID , YTChannelID ) { var webhhookDoc = await this . webhooks . findOne ( { feed : YTChannelID , guild : guildID , channel : channelID } ) . exec ( ) ; if ( ! webhhookDoc ) return undefined ; var guildDoc = await Bot . database . findGuildDoc ( guildID ) ; if ( ! guildDoc ) return undefined ; guildDoc . webhooks . youtube . splice ( guildDoc . webhooks . youtube . indexOf ( webhhookDoc . id ) , 1 ) ; await guildDoc . save ( ) ; var sameFeedWebhookCount = await this . webhooks . countDocuments ( { feed : YTChannelID } ) . exec ( ) ; if ( sameFeedWebhookCount == 1 ) { try { this . subToChannel ( YTChannelID , false ) ; } catch ( e ) { console . error ( 'error while unsubscribing to youtube webhook:' , e ) ; Bot . mStats . logError ( e ) ; return null ; } } Bot . mStats . logWebhookAction ( 'youtube' , 'deleted' ) ; return await webhhookDoc . remove ( ) ; } param { string } guildID param { string } channelID param { string } YTChannelID param { string } newChannelID param { string } newYTChannelID param { string } newMessage returns memberof async changeWebhook ( guildID , channelID , YTChannelID , newChannelID ? , newYTChannelID ? , newMessage ? ) { var webhhookDoc = await this . webhooks . findOne ( { feed : YTChannelID , guild : guildID , channel : channelID } ) . exec ( ) ; if ( ! webhhookDoc ) return undefined ; Bot . mStats . logWebhookAction ( 'youtube' , 'changed' ) ; if ( newYTChannelID ) { var newWebhookDoc = await this . createWebhook ( guildID , ( newChannelID ? newChannelID : channelID ) , newYTChannelID , ( newMessage ? newMessage : webhhookDoc . toObject ( ) . message ) ) ; if ( ! newWebhookDoc ) return undefined ; await this . deleteWebhook ( guildID , channelID , YTChannelID ) ; return newWebhookDoc ; } if ( newChannelID ) { webhhookDoc . channel = newChannelID ; } if ( newMessage ) { webhhookDoc . message = newMessage ; } webhhookDoc . save ( ) ; return webhhookDoc ; } }