import * as moment from "moment" ; import * as schedule from "node-schedule" ; import * as TelegramBot from "node-telegram-bot-api" ; import * as request from "request" ; import * as sharp from "sharp" ; import { TelegramBotExtended } from "../typings" ; import { BotConfig , InitialConfig } from "./botConfig" ; import * as CONSTS from "./consts" ; import { Operations } from "./operations" ; import * as PhotoData from "./photoData" ; import { TelegramDownload } from "./telegramDownload" ; import { Utils } from "./utils" ; const pixivApi = require ( "pixiv-api-client" ) ; const tracer = require ( "tracer" ) ; const logger = tracer . colorConsole ( { level : process . env . DEBUG !== undefined ? process . env . DEBUG : "info" } ) ; moment . locale ( "zh-tw" ) ; export class AutoChangeGroupPhotoBot { private static _instance ? : AutoChangeGroupPhotoBot ; private readonly downloader : TelegramDownload ; private readonly config : BotConfig ; private readonly bot : TelegramBot ; private readonly data : PhotoData . PhotoDataStructure [ ] ; private uploadQueue : Promise < any > = Promise . resolve ( ) ; private readonly pixiv ? : any ; param _config public static getInstance ( _config = { } ) { return ( AutoChangeGroupPhotoBot . _instance ) ? AutoChangeGroupPhotoBot . _instance : ( AutoChangeGroupPhotoBot . _instance = new AutoChangeGroupPhotoBot ( _config ) ) ; } param _config private constructor ( _config = { } ) { this . config = InitialConfig ( Object . assign ( new BotConfig ( ) , _config ) , ( ) => { BotConfig . saveConfig ( this . config ) ; } ) ; if ( this . config . token ) { this . data = PhotoData . PhotoDataStore ( PhotoData . PhotoDataStructure . readData ( ) , ( ) => { PhotoData . PhotoDataStructure . saveData ( this . data ) ; } ) ; this . bot = new TelegramBot ( this . config . token , { polling : { interval : 0 , params : { timeout : 60 } } } ) ; this . downloader = TelegramDownload . getInstance ( this . bot , logger ) ; this . registerEvent ( ) . then ( ( ) => { } ) . catch ( ( ) => { } ) ; if ( this . config . pixiv . account && this . config . pixiv . password ) { this . pixiv = new pixivApi ( ) ; Utils . registerPixiv ( this . pixiv , this . config ) ; } else { this . pixiv = null ; logger . info ( CONSTS . DISABLED_PIXIV_ACCOUNT ) ; } } else { throw Error ( CONSTS . NEED_TELEGRAM_BOT_TOKEN ) ; } } /**
     * Register Bot events and cronjob
     */ private async registerEvent ( ) { schedule . scheduleJob ( "0 * * * * *" , ( ) => { this . doUpdate ( ) ; } ) ; this . bot . onText ( CONSTS . REGEXP_MATCH_TAG_COMMAND , async ( msg ) => { if ( msg . reply_to_message && ( msg . reply_to_message . photo || msg . reply_to_message . document ) ) { logger . info ( CONSTS . QUEUE_TEXT ( "Text" , msg . chat ) ) ; await Operations . addPhoto ( this . data , this . bot , this . downloader , msg . reply_to_message ) ; } else if ( msg . reply_to_message && msg . reply_to_message . entities ) { this . doAddPhotoByUrl ( msg . reply_to_message ) ; } } ) ; this . bot . on ( "photo" , async ( msg ) => { if ( msg . caption && msg . caption . match ( CONSTS . REGEXP_MATCH_TAG_COMMAND ) ) { logger . info ( CONSTS . QUEUE_TEXT ( "Photo" , msg . chat ) ) ; await Operations . addPhoto ( this . data , this . bot , this . downloader , msg ) ; } } ) ; this . bot . on ( "document" , async ( msg ) => { if ( msg . caption && msg . caption . match ( CONSTS . REGEXP_MATCH_TAG_COMMAND ) ) { logger . info ( CONSTS . QUEUE_TEXT ( "Document" , msg . chat ) ) ; await Operations . addPhoto ( this . data , this . bot , this . downloader , msg ) ; } } ) ; this . bot . on ( "message" , async ( msg ) => { if ( msg . text !== undefined && msg . text . match ( CONSTS . REGEXP_MATCH_TAG_COMMAND ) !== null ) { if ( msg . reply_to_message && msg . reply_to_message . sticker ) { await this . doAddSticker ( msg ) ; } else if ( msg . entities ) { this . doAddPhotoByUrl ( msg ) ; } } } ) ; await this . bot . getMe ( ) . then ( async ( me ) => { await this . getMeEvent ( me ) ; } ) . catch ( ( reason ) => { logger . warn ( "Get Me occurred error" , reason ) ; } ) ; } /**
     * process get me events
     */ private async getMeEvent ( me ) { if ( me instanceof Error ) { logger . warn ( "Get Me Event occurred error" , me ) ; return ; } this . bot . on ( "inline_query" , async ( inlineQuery ) => { logger . info ( inlineQuery ) ; if ( inlineQuery . query . match ( /^\d+$/ ) ) { const pid = Number ( inlineQuery . query ) ; await Utils . getPixivIllustDetail ( this . pixiv , this . config , pid ) . then ( ( illustObj ) => { const result = illustObj . originalUrl . map ( ( url , i ) => { let caption = CONSTS . GROUP_PHOTO_PIXIV_CAPTION ( illustObj ) ; caption = ( caption . length > CONSTS . PHOTO_CAPTION_MAX_LENGTH ) ? caption . substr ( 0 , CONSTS . PHOTO_CAPTION_MAX_LENGTH ) : caption ; const r = { caption , id : `${ inlineQuery . id }_${ i }` , photo_url : url , thumb_url : url , type : "photo" , } ; return r ; } ) ; this . bot . answerInlineQuery ( inlineQuery . id , result , { cache_time : 1 } ) . catch ( ( e ) => logger . error ( e ) ) ; } ) ; } } ) ; this . bot . onText ( /^\/(\w+)@?(\w*)/i , async ( msg , regex ) => { if ( ! regex || regex [ 2 ] && regex [ 2 ] !== me . username ) { logger . info ( "Received regex" , regex , "but not @ me, ignored" ) ; return ; } if ( msg . chat . type !== "private" && regex [ 2 ] !== me . username ) { logger . info ( "Received regex" , regex , "but not @ me, ignored" ) ; return ; } const command = regex [ 1 ] . toLowerCase ( ) ; const commandArgs = msg . text ! . replace ( regex [ 0 ] , "" ) . trim ( ) . split ( " " ) ; logger . info ( "Received command" , command , "and args" , commandArgs ) ; await this . bot . getChatAdministrators ( msg . chat . id ) . then ( async ( members ) => Operations . parseCommand ( this . data , this . bot , this . config , members , msg , command as CONSTS . COMMANDS , commandArgs ) ) ; } ) ; } /**
     * Update the Group Photo Icon
     */ private doUpdate ( ) { this . data . map ( async ( chatData ) => { if ( chatData . disabled ) { return ; } const isMomentBefore = moment ( chatData . last ) . add ( chatData . interval , "h" ) . isBefore ( moment ( ) ) ; if ( ! chatData . paused && ( ! chatData . last || isMomentBefore ) ) { await chatData . nextPhoto ( this . bot ) ; } this . downloader . checkGroup ( chatData ) ; } ) ; } private async doAddSticker ( msg ) { if ( msg . reply_to_message && msg . reply_to_message . sticker ) { const stickerURL = await this . bot . getFileLink ( msg . reply_to_message . sticker . file_id ) ; if ( stickerURL instanceof Error ) { return Promise . reject ( stickerURL ) ; } else { return request ( stickerURL , Utils . requestOptions , async ( error , response , body ) => { const stickerSharp = sharp ( body ) ; const stickerPng = await stickerSharp . png ( ) . toBuffer ( ) ; await Operations . sendPhotoPromise ( this . data , this . bot , this . downloader , msg , stickerPng , { caption : CONSTS . GROUP_PHOTO_CAPTION } ) ; } ) ; } } return Promise . reject ( null ) ; } param msg param ent param url private async tryGetPhotoFromUrl ( msg , ent , url ) { this . uploadQueue = this . uploadQueue . then ( async ( ) => new Promise < void > ( async ( resolve , reject ) => { const imgUrl = await Utils . preProcessUrl ( this . pixiv , this . config , this . bot , msg , url ) ; const isBuffer = imgUrl instanceof Buffer ; const isPixiv = imgUrl instanceof PhotoData . PixivIllustStructure ; if ( ! ( imgUrl instanceof PhotoData . PixivIllustStructure ) && imgUrl . length === 0 ) { reject ( ) ; return ; } const downloadImage = async ( err , response , body ) => { if ( err ) { logger . error ( err ) ; reject ( ) ; return ; } if ( response . statusCode === 200 ) { await Utils . parsePhoto ( Operations . uploadPhoto , this . data , this . bot , this . downloader , msg , Buffer . from ( response . body ) , ent , url , imgUrl instanceof Buffer ? undefined : imgUrl ) ; resolve ( ) ; } else { await this . bot . sendMessage ( msg . chat . id , CONSTS . URL_REQUESTED_IS_NOT_OK ( url ) , { reply_to_message_id : msg . message_id } ) ; reject ( ) ; } } ; if ( isBuffer ) { const imgBuffer = imgUrl as Buffer ; await Utils . parsePhoto ( Operations . uploadPhoto , this . data , this . bot , this . downloader , msg , imgBuffer , ent , url ) ; resolve ( ) ; return ; } else if ( isPixiv ) { const illust = imgUrl as PhotoData . PixivIllustStructure ; return request . get ( illust . originalUrl [ 0 ] , Utils . requestOptions , downloadImage ) ; } else { const urlString = imgUrl as string ; return request . get ( urlString , Utils . requestOptions , downloadImage ) ; } } ) ) . catch ( ( ) => { } ) ; return this . uploadQueue ; } param msg private doAddPhotoByUrl ( msg ) { if ( msg . entities ) { const urls = [ ] ; msg . entities . map ( async ( ent , idx ) => { if ( ent . type === "url" && msg . text !== undefined ) { const url = msg . text . substr ( ent . offset , ent . length ) ; if ( urls . indexOf ( url ) === - 1 ) { logger . info ( CONSTS . QUEUE_REQUEST_TEXT ( "URL" , `${ msg . chat . title }(${ msg . chat . id }): ${ url }` ) ) ; urls . push ( url ) ; await this . tryGetPhotoFromUrl ( msg , ent , url ) ; } } else if ( ent . type === "text_link" ) { if ( urls . indexOf ( ent . url ! ) === - 1 ) { logger . info ( CONSTS . QUEUE_REQUEST_TEXT ( "URL" , `${ msg . chat . title }(${ msg . chat . id }): ${ ent . url }` ) ) ; urls . push ( ent . url ! ) ; await this . tryGetPhotoFromUrl ( msg , ent , ent . url ! ) ; } } } ) ; } } }