import FormData from 'form-data' ; import { Adapter , assertProperty , Bot , camelCase , Dict , Quester , renameProperty , Schema , snakeCase } from 'koishi' ; import * as Telegram from './types' ; import { AdapterConfig } from './utils' ; import { AxiosError } from 'axios' ; import { Sender } from './sender' ; export class SenderError extends Error { constructor ( args , url , retcode , selfId ) { super ( `Error when trying to send to ${ url }, args: ${ JSON . stringify ( args ) }, retcode: ${ retcode }` ) ; Object . defineProperties ( this , { name : { value : 'SenderError' } , selfId : { value : selfId } , code : { value : retcode } , args : { value : args } , url : { value : url } , } ) ; } } export interface TelegramResponse { ok : boolean ; result : any ; } export interface BotConfig extends Bot . BaseConfig { selfId ? : string ; token ? : string ; pollingTimeout ? : number | true ; } export const BotConfig = Schema . object ( { token : Schema . string ( ) . description ( '机器人的用户令牌。' ) . required ( ) , pollingTimeout : Schema . union ( [ Schema . number ( ) , Schema . const < true > ( true ) , ] ) . description ( '通过长轮询获取更新时请求的超时。单位为秒；true 为使用默认值 1 分钟。详情请见 Telegram API 中 getUpdate 的参数 timeout。不设置即使用 webhook 获取更新。' ) , } ) ; export class TelegramBot extends Bot < BotConfig > { static adaptUser ( data ) { data . userId = data . id . toString ( ) ; data . nickname = data . firstName + ( data . lastName || '' ) ; delete data . id ; delete data . firstName ; delete data . lastName ; return data as Bot . User ; } static schema = AdapterConfig ; http : Quester ; constructor ( adapter , config ) { assertProperty ( config , 'token' ) ; if ( ! config . selfId ) { if ( config . token . includes ( ':' ) ) { config . selfId = config . token . split ( ':' ) [ 0 ] ; } else { assertProperty ( config , 'selfId' ) ; } } super ( adapter , config ) ; this . http = adapter . http . extend ( { endpoint : `${ adapter . http . config . endpoint }/bot${ config . token }` , } ) ; } param action param params returns async get < T = any , P = any > ( action , params = undefined ) { this . logger . debug ( '[request] %s %o' , action , params ) ; const response = await this . http . get ( action , { params : snakeCase ( params || { } ) } ) ; this . logger . debug ( '[response] %o' , response ) ; const { ok , result } = response ; if ( ok ) return camelCase ( result ) ; throw new SenderError ( params , action , - 1 , this . selfId ) ; } param action param params param field param content returns async post < T = any , P = any > ( action , params = undefined , field = '' , content = null , filename = 'file' ) { this . logger . debug ( '[request] %s %o' , action , params ) ; const payload = new FormData ( ) ; for ( const key in params ) { payload . append ( snakeCase ( key ) , params [ key ] . toString ( ) ) ; } if ( field && content ) payload . append ( field , content , filename ) ; let response ; try { response = await this . http . post ( action , payload , { headers : payload . getHeaders ( ) } ) ; } catch ( e ) { response = ( e as AxiosError ) . response . data ; } this . logger . debug ( '[response] %o' , response ) ; const { ok , result } = response ; if ( ok ) return camelCase ( result ) ; throw new SenderError ( params , action , - 1 , this . selfId ) ; } async sendMessage ( channelId , content ) { if ( ! content ) return ; let subtype ; let chatId ; if ( channelId . startsWith ( 'private:' ) ) { subtype = 'private' ; chatId = channelId . slice ( 8 ) ; } else { subtype = 'group' ; chatId = channelId ; } const session = this . createSession ( { content , channelId , guildId : channelId } ) ; if ( await this . app . serial ( session , 'before-send' , session ) ) return ; const send = Sender . from ( this , chatId ) ; const results = await send ( session . content ) ; for ( const id of results ) { session . messageId = id ; this . app . emit ( session , 'send' , session ) ; } return results ; } async sendPrivateMessage ( userId , content ) { return this . sendMessage ( userId , content ) ; } async getMessage ( ) { return null ; } async deleteMessage ( chatId , messageId ) { await this . get ( '/deleteMessage' , { chatId , messageId } ) ; } static adaptGroup ( data ) { renameProperty ( data as any , 'guildId' , 'id' ) ; renameProperty ( data as any , 'guildName' , 'title' ) ; return data as any ; } async getGuild ( chatId ) { const data = await this . get < Telegram . Chat > ( '/getChat' , { chatId } ) ; return TelegramBot . adaptGroup ( data ) ; } async getGuildList ( ) { return [ ] ; } async getGuildMember ( chatId , userId ) { if ( Number . isNaN ( + userId ) ) return null ; const data = await this . get ( '/getChatMember' , { chatId , userId } ) ; return TelegramBot . adaptUser ( data ) ; } async getGuildMemberList ( chatId ) { const data = await this . get ( '/getChatAdministrators' , { chatId } ) ; return data . map ( TelegramBot . adaptUser ) ; } setGroupLeave ( chatId ) { return this . get ( '/leaveChat' , { chatId } ) ; } async handleGuildMemberRequest ( messageId , approve , comment ? ) { const [ chatId , userId ] = messageId . split ( '@' ) ; const method = approve ? '/approveChatJoinRequest' : '/declineChatJoinRequest' ; const success = await this . get < boolean > ( method , { chatId , userId } ) ; if ( ! success ) throw new Error ( `handel guild member request field ${ success }` ) ; } async getLoginInfo ( ) { const data = await this . get < Telegram . User > ( '/getMe' ) ; return TelegramBot . adaptUser ( data ) ; } }