telegram-node-singlebot
Version:
Module for creating Telegram Single Bot
985 lines (892 loc) • 31.2 kB
JavaScript
'use strict'
const InlineKeyboardButton = require('../models/InlineKeyboardButton')
const InlineKeyboardMarkup = require('../models/InlineKeyboardMarkup')
const ReplyKeyboardMarkup = require('../models/ReplyKeyboardMarkup')
const KeyboardButton = require('../models/KeyboardButton')
class Scope {
/**
*
* @param {Update} update
* @param {TelegramApi} api
* @param {BaseScopeExtension[]} extensions
* @param {Function[]} waitingRequests
* @param {Object} waitingCallbackQueries
* @param {BaseLogger} logger
* @param {Function} processUpdate
* @param {TelegramSessionStorage} sessionStorage
* @param {Function} waitForUpdate
*/
constructor(
update,
api,
extensions,
waitingRequests,
waitingCallbackQueries,
logger,
sessionStorage,
waitForUpdate
) {
this._api = api
this._update = update
/**
*
* @type {BaseScopeExtension[]}
* @private
*/
this._extensions = extensions
this._waitingRequests = waitingRequests
this._waitingCallbackQueries = waitingCallbackQueries
this._isEditedMessage = update.editedMessage ? true : false
this._callbackQuery = update.callbackQuery
this._message = update.message || update.editedMessage || update.callbackQuery.message
this._chatId = this._message.chat.id
this._userId = this._message.from.id
this._messageId = this._message.messageId
this._fromGroupChat = !(this._userId === this._chatId)
this._logger = logger
this._sessionStorage = sessionStorage
this._waitForUpdate = waitForUpdate
this._extensions.forEach(extension => {
const extensionInstance = new extension(this)
this[extensionInstance.name] = extensionInstance.process
})
if(update.callbackQuery)
this.answerCallbackQuery()
}
/**
* @returns {TelegramSessionStorage}
*/
get sessionStorage() {
return this._sessionStorage
}
/**
* @returns {BaseStorage}
*/
get storage() {
return this._sessionStorage
}
/**
*
* @returns {Update}
*/
get update() {
return this._update
}
/**
*
* @returns {Message}
*/
get message() {
return this._message
}
/**
*
* @returns {Message}
*/
get defaultmessage() {
return {reply_markup: JSON.stringify({hide_keyboard: true}), parse_mode: 'HTML', disable_web_page_preview: true}
}
/**
*
* @returns {CallBackQuery}
*/
get callbackQuery() {
return this._callbackQuery
}
/**
*
* @returns {number}
*/
get chatId() {
return this._chatId
}
/**
*
* @returns {string}
*/
get firstName() {
return this._message.from.firstName
}
/**
*
* @returns {number}
*/
get userId() {
return this._userId
}
/**
*
* @returns {number}
*/
get messageId() {
return this._messageId
}
/**
*
* @returns {boolean}
*/
get idFromGroupChat() {
return this._fromGroupChat
}
/**
*
* @returns {TelegramApi}
*/
get api() {
return this._api
}
/**
* @param {string} key
* @returns {Promise.<*>}
*/
getUserSession(key) {
return this._sessionStorage.getUserSession(this.userId, key)
}
/**
* @param {string} key
* @param {*} value
* @returns {Promise}
*/
setUserSession(key, value) {
return this._sessionStorage.setUserSession(this.userId, key, value)
}
/**
* @param {string} key
* @returns {Promise.<*>}
*/
getChatSession(key) {
return this._sessionStorage.getChatSession(this.chatId, key)
}
/**
* @param {string} key
* @param {*} value
* @returns {Promise}
*/
setChatSession(key, value) {
return this._sessionStorage.setChatSession(this.chatId, key, value)
}
/**
*
* @returns {BaseLogger}
*/
get logger() {
return this._logger
}
/**
*
* @returns {boolean}
*/
get isEditedMessage() {
return this._isEditedMessage
}
/**
* After calling this the next update
* from current user will be passed to promise
*
* @returns {Promise<Scope>}
*/
get waitForRequest() {
return new Promise(resolve => {
this._waitingRequests[this.chatId] = resolve
this._waitForUpdate(this.chatId)
})
}
/**
*
* @param text
* @param keyboard
* @param callback
* @param close
* @param updatekeyboard
* @constructor
*/
SessionSetMessage(text, keyboard, callback, close, updatekeyboard){
this._sessionStorage.getUserSession(this.userId, 'session').then(messageId => {
if((isNaN(messageId) || (messageId != this.messageId && !this._callbackQuery) || close)){
if(this._callbackQuery)
this.SessionClose(this._messageId, null, ()=>{})
this._api.sendMessage(this.userId, text, {
parse_mode: 'HTML',
reply_markup: keyboard
}).then(message => {
this._sessionStorage.setUserSession(this.userId, 'session', message.messageId).then(() => {
callback()
})
})
}else if ((messageId == this.messageId) && !updatekeyboard) {
this._api.editMessageText(text, {
chat_id: this._chatId,
message_id: messageId,
parse_mode: 'HTML',
reply_markup: keyboard
}).then(() => {
callback()
})
}else if((messageId != this.messageId) && updatekeyboard){
this.api.editMessageReplyMarkup({
chat_id: this._chatId,
message_id: messageId,
reply_markup: keyboard
}).then(() => {
callback()
})
}else{
callback()
}
})
}
/**
*
* @param onthisquery
* @param onquery
* @param onmessage
* @constructor
*/
SessionGetMessage(onthisquery, onquery, onmessage){
this._sessionStorage.getUserSession(this.userId, 'session').then(messageId => {
if(this._callbackQuery){
if(messageId == this._messageId){
onthisquery(messageId)
}else {
onquery(messageId)
}
}else {
onmessage(messageId)
}
})
}
/**
*
* @param messageId
* @param text
* @param callback
* @constructor
*/
SessionClose(messageId, text, callback){
this._api.editMessageText(text || lg.closesession, {
chat_id: this._chatId,
message_id: messageId,
parse_mode: 'HTML'
}).then(() => {
callback()
})
}
/**
*
* @param menuData
* @param stop
* @param updatekeyboard
*/
runMenu(menuData, stop, updatekeyboard){
if(typeof menuData.title == 'undefined')
throw Error('titleis not set')
if(typeof menuData.menu == 'undefined')
throw Error('menu is not set')
const message = '<b>'+menuData.title+'</b>'+((typeof menuData.message == 'undefined') ? '' : '\n'+menuData.message)
var keyboard = [],
action = {}
for (var i = 0; i < menuData.menu.length; i++){
var array = []
for (var q = 0; q < menuData.menu[i].length; q++){
let random = Math.random().toString(36).substring(7)
array.push({
text: menuData.menu[i][q].text,
callback_data: random
})
action[random] = menuData.menu[i][q].action
}
keyboard.push(array)
if(menuData.menu.length-1 == i){
if(typeof menuData.back != 'undefined'){
let random = Math.random().toString(36).substring(7)
keyboard.push([{
text: lg.back,
callback_data: random
}])
action[random] = menuData.back
}
keyboard = JSON.stringify({inline_keyboard: keyboard})
}
}
this.SessionSetMessage(message, keyboard, () => {
this.waitForRequest.then($ => {
$.SessionGetMessage((messageId) => {
if(typeof action[$.message.text] != 'undefined')
action[$.message.text]($)
}, (messageId) => {
$.SessionClose($.messageId, null, () => {
$.runMenu(menuData, null, true)
})
}, (messageId) => {
$.SessionClose(messageId, null, () => {
$.runMenu(menuData)
})
})
})
}, stop, updatekeyboard)
}
/**
*
* @param menuData
* @param offset
* @param stop
* @param updatekeyboard
*/
runManyMenu(menuData, offset, stop, updatekeyboard){
if(menuData == null || typeof menuData == 'undefined')
throw Error('menuData for not set runManyMenu')
if(offset == null || typeof offset =='undefined')
offset = 0
if(!menuData.menu.length)
throw Error('menu not set')
if(typeof menuData.menu[offset].message == 'undefined')
throw Error('Not set message for menu')
let message = '<i>'+menuData.title+'</i>'+'\n'+menuData.menu[offset].message,
keyboard = [], action = {}
if(menuData.menu.length > 1 && menuData.menu.length < 6){
let i = 0
keyboard.push([])
for(i; menuData.menu.length>i; i++){
let random = Math.random().toString(36).substring(7)
keyboard[0].push({
text: (i == offset) ? '·'+(i+1).toString()+'·' : (i+1).toString(),
callback_data: random
})
action[random] = {
page: true,
number: i
}
}
}else if (menuData.menu.length > 5) {
keyboard.push([])
for (let i = 0; 5 > i; i++) {
let text = '';
let noffset = 0;
if (offset < 3) {
switch (i) {
case 3:
text = (i + 1).toString() + '›'
noffset = i
break
case 4:
text = menuData.menu.length.toString() + '»'
noffset = menuData.menu.length - 1
break
default:
text = (i == offset) ? '·' + (i + 1).toString() + '·' : (i + 1).toString()
noffset = i
break
}
}
else if (offset > menuData.menu.length - 4) {
let menu_offset;
switch (i) {
case 0:
text = '«1'
break
case 1:
text = '‹' + (menuData.menu.length - 3).toString()
noffset = menuData.menu.length - 4
break
case 2:
menu_offset = 2;
text = ((menuData.menu.length - 1 - menu_offset) == offset) ? '·' + ((menuData.menu.length - menu_offset)).toString() + '·' : ((menuData.menu.length - menu_offset)).toString()
noffset = (menuData.menu.length - 1 - menu_offset);
break;
case 3:
menu_offset = 1;
text = ((menuData.menu.length - 1 - menu_offset) == offset) ? '·' + ((menuData.menu.length - menu_offset)).toString() + '·' : ((menuData.menu.length - menu_offset)).toString()
noffset = (menuData.menu.length - 1 - menu_offset);
break
case 4:
menu_offset = 0;
text = ((menuData.menu.length - 1 - menu_offset) == offset) ? '·' + ((menuData.menu.length - menu_offset)).toString() + '·' : ((menuData.menu.length - menu_offset)).toString()
noffset = (menuData.menu.length - 1 - menu_offset);
break
}
}
else {
switch (i) {
case 0:
text = '«1'
break
case 1:
text = '‹' + (offset).toString()
noffset = offset - 1
break
case 2:
text = '·' + (offset + 1).toString() + '·'
noffset = offset
break
case 3:
text = (offset + 2).toString() + '›'
noffset = offset + 1
break
case 4:
text = menuData.menu.length.toString() + '»'
noffset = menuData.menu.length - 1
break
default:
break
}
}
let random = Math.random().toString(36).substring(7)
keyboard[0].push({
text: text,
callback_data: random
})
action[random] = {
page: true,
number: noffset
}
}
}
for (var i = 0; i < menuData.menu[offset].keyboard.length; i++){
var array = []
for (var q = 0; q < menuData.menu[offset].keyboard[i].length; q++){
let random = Math.random().toString(36).substring(7)
if(typeof menuData.menu[offset].keyboard[i][q].text == "undefined")
throw Error('not set text button for '+menuData.menu[offset].message)
if(typeof menuData.menu[offset].keyboard[i][q].action == "undefined")
throw Error('not set action for '+menuData.menu[offset].message)
array.push({
text: menuData.menu[offset].keyboard[i][q].text,
callback_data: random
})
action[random] = menuData.menu[offset].keyboard[i][q].action
}
keyboard.push(array)
}
if(typeof menuData.back != 'undefined'){
let random = Math.random().toString(36).substring(7)
keyboard.push([{
text: lg.back,
callback_data: random
}])
action[random] = menuData.back
}
keyboard = JSON.stringify({inline_keyboard: keyboard})
this.SessionSetMessage(message, keyboard, () => {
this.waitForRequest.then($ => {
$.SessionGetMessage((messageId) => {
if(typeof action[$.message.text] == 'object'){
if(action[$.message.text].page){
$.runManyMenu(menuData, action[$.message.text].number)
}
}else if (typeof action[$.message.text] == 'function'){
action[$.message.text]($)
}
}, (messageId) => {
$.SessionClose($.messageId, null, () => {
$.runManyMenu(menuData, offset, null, true)
})
}, (messageId) => {
$.SessionClose(messageId, null, () => {
$.runManyMenu(menuData, offset)
})
})
})
}, stop, updatekeyboard)
}
/**
*
* @param formData
* @param callback
* @param resultData
* @param index
* @param stop
* @param updatekeyboard
*/
runform(formData, callback, resultData, index, stop, updatekeyboard){
var i = index ? index : 0
const run = (stop, updatekeyboard) => {
var keyboardword = {}, keyboard = [], syskeyboard = [], message
if (i === Object.keys(formData.form).length) {
try {
message = formData.confirm(result)
keyboard.push(
[{
text: lg.confirm,
callback_data: '/confirm'
}],
[{
text: lg.cancel,
callback_data: '/cancel'
},{
text: lg.back,
callback_data: '/back'
}]
)
keyboard = JSON.stringify({inline_keyboard: keyboard})
this.SessionSetMessage(message, keyboard, () => {
this.waitForRequest.then($ => {
$.SessionGetMessage(
messageId => {
switch ($.message.text){
case '/confirm':
$.SessionClose(messageId, formData.finish(result), () => {
callback(result)
})
break
case '/cancel':
formData.cancel($)
break
case '/back':
delete result[keys[i-1]]
var index = i-1
$.runform(formData, callback, result, index)
break
default:
$.runform(formData, callback, result, i)
}
},
messageId => {
$.SessionClose($.messageId, null, () => {
$.runform(formData, callback, result, i, null, true)
})
},
messageId => {
$.SessionClose(messageId, null, () => {
$.runform(formData, callback, result, i, null, null)
})
}
)
})
})
}
catch (e) {
this.logger.error({ 'error in user callback:': e })
}
return
}
const key = formData.form[keys[i]]
var message = '<b>'+formData.title+'</b>'
for(var y = 0; i+1 > y; y++){
if(y == i){
message += '\n<i>'+formData.form[keys[y]].text
message += (typeof formData.form[keys[y]].question != 'undefined' && formData.form[keys[y]].question) ?
'?' : ''
message += '</i>'
}
else {
if(typeof result[keys[y]] === 'string')
message += '\n<i>'+formData.form[keys[y]].text+':</i> <b>'+result[keys[y]]+'</b>'
else
message += '\n<i>'+formData.form[keys[y]].text+'</i> \u2714'
}
}
if(typeof key.keyboard != 'undefined'){
for (var k = 0; k < key.keyboard.length; k++){
var array = []
for (var q = 0; q < key.keyboard[k].length; q++){
let random = Math.random().toString(36).substring(7)
array.push({
text: key.keyboard[k][q],
callback_data: random
})
keyboardword[random] = key.keyboard[k][q]
}
keyboard.push(array)
}
}
syskeyboard.push({
text: lg.cancel,
callback_data: '/cancel'
})
if(i){
syskeyboard.push({
text: lg.back,
callback_data: '/back'
})
}
keyboard.push(syskeyboard)
keyboard = JSON.stringify({inline_keyboard: keyboard})
this.SessionSetMessage(message, keyboard, () => {
this.waitForRequest.then($ => {
$.SessionGetMessage(
messageId => {
if($.message && $.message.text == '/cancel')
return formData.cancel($)
if($.message && $.message.text == '/back'){
delete result[keys[i-1]]
var index = i-1
return $.runform(formData, callback, result, index)
}
if(typeof keyboardword[$.message.text] != 'undefined')
result[keys[i]] = keyboardword[$.message.text]
var index = i+1
$.runform(formData, callback, result, index)
},
messageId => {
$.SessionClose($.messageId, null, () => {
$.runform(formData, callback, result, i, null, true)
})
},
messageId => {
var returnmessage = '<i>'+key.text+'</i>'
returnmessage += (typeof key.question != 'undefined' && key.question) ? '?' : ''
if(typeof key.keyboard != 'undefined'){
if(key.keyboardonly || $.message.text == null)
return $.SessionClose(messageId, key.error, () => {
$.runform(formData, callback, result, i)
})
$.SessionClose(messageId, returnmessage, () => {
result[keys[i]] = $.message.text
var index = i+1
$.runform(formData, callback, result, index)
})
}else {
key.validator($.message, (valid, value) => {
if(valid)
return $.SessionClose(messageId, returnmessage, () => {
result[keys[i]] = value
var index = i+1
$.runform(formData, callback, result, index)
})
else
return $.SessionClose(messageId, key.error, () => {
$.runform(formData, callback, result, i)
})
})
}
})
})
}, stop, updatekeyboard)
}
var result = (resultData) ? resultData : {}
const keys = Object.keys(formData.form)
run(stop, updatekeyboard)
}
//api methods starts here
/**
*
* @returns {Promise<answerCallbackQuery>}
*/
answerCallbackQuery() {
return this._api.answerCallbackQuery(this._callbackQuery.id)
}
/**
*
* @param {string} text
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendMessage(text, options) {
return this._api.sendMessage(this.chatId, text, Object.assign({parse_mode: 'HTML'}, options))
}
/**
*
* @param {string} text
* @returns {Promise<Message>}
*/
senddefaultMessage(text) {
return this._api.sendMessage(this.chatId, text, this.defaultmessage)
}
/**
*
* @param keybord
*/
inlinekeybord(keybord){
var options = {inline_keyboard: []}
for (var i = 0; i < keybord.length; i++){
var array = []
for (var q = 0; q < keybord[i].length; q = q + 2){
array.push({
text: keybord[i][q],
callback_data: keybord[i][q+1]
})
}
options.inline_keyboard.push(array)
}
return JSON.stringify(options)
}
/**
*
* @param {string} text
* @param {array}, keybord
* @returns {Promise<Message>}
*/
sendinlinekeyMessage(text, keybord) {
return this._api.sendMessage(this.chatId, text, {reply_markup: this.inlinekeybord(keybord), parse_mode: 'HTML', disable_web_page_preview: true})
}
/**
*
* @param {string} text
* @param {Object} [options]
* @returns {Promise<Message>}
*/
editMessageText(text, options) {
options = options ? options : {}
return this._api.editMessageText(text, Object.assign({
chat_id: this._chatId,
message_id: this._messageId,
parse_mode: 'HTML'
}, options))
}
/**
*
* @param {number} fromChatId
* @param {number} messageId
* @param {Object} [options]
* @returns {Promise<Message>}
*/
forwardMessage(fromChatId, messageId, options) {
return this._api.forwardMessage(this.chatId, fromChatId, messageId, options)
}
/**
*
* @param {InputFile|Object} photo
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendPhoto(photo, options) {
return this._api.sendPhoto(this.chatId, photo, options)
}
/**
*
* @param {InputFile|Object} audio
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendAudio(audio, options) {
return this._api.sendAudio(this.chatId, audio, options)
}
/**
*
* @param {InputFile|Object} document
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendDocument(document, options) {
return this._api.sendDocument(this.chatId, document, options)
}
/**
*
* @param {InputFile|Object} sticker
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendSticker(sticker, options) {
return this._api.sendSticker(this.chatId, sticker, options)
}
/**
*
* @param {InputFile|Object} video
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendVideo(video, options) {
return this._api.sendVideo(this.chatId, video, options)
}
/**
*
* @param {InputFile|Object} voice
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendVoice(voice, options) {
return this._api.sendVoice(this.chatId, voice, options)
}
/**
*
* @param {number} latitude
* @param {number} longitude
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendLocation(latitude, longitude, options) {
return this._api.sendLocation(this.chatId, latitude, longitude, options)
}
/**
*
* @param {number} latitude
* @param {number} longitude
* @param {string} title
* @param {string}address
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendVenue(latitude, longitude, title, address, options) {
return this._api.sendVenue(this.chatId, latitude, longitude, title, address, options)
}
/**
*
* @param {string} phoneNumber
* @param {string} firstName
* @param {Object} [options]
* @returns {Promise<Message>}
*/
sendContact(phoneNumber, firstName, options) {
return this._api.sendContact(this.chatId, phoneNumber, firstName, options)
}
/**
*
* @param {string} action
* @returns {Promise<Object>}
*/
sendChatAction(action) {
return this._api.sendChatAction(this.chatId, action)
}
/**
*
* @param {number} offset
* @param {number} limit
* @returns {Promise<UserProfilePhotos>}
*/
getUserProfilePhotos(offset, limit) {
return this._api.getUserProfilePhotos(userId, offset, limit)
}
/**
*
* @param {number} userId
* @returns {Promise.<boolean>}
*/
kickChatMember(userId) {
return this._api.kickChatMember(this.chatId, userId)
}
/**
*
* @returns {Promise.<boolean>}
*/
leaveChat() {
return this._api.leaveChat(this.chatId)
}
/**
*
* @param {number} userId
* @returns {Promise.<boolean>}
*/
unbanChatMember(userId) {
return this._api.unbanChatMember(this.chatId, userId)
}
/**
*
* @returns {Promise<Chat>}
*/
getChat() {
return this._api.getChat(this.chatId)
}
/**
*
* @returns {Promise<ChatMember[]>}
*/
getChatAdministrators() {
return this._api.getChatAdministrators(this.chatId)
}
/**
*
* @returns {Promise<number>}
*/
getChatMembersCount() {
return this._api.getChatMembersCount(this.chatId)
}
/**
*
* @param {number} userId
* @returns {Promise.<ChatMember>}
*/
getChatMember(userId) {
return this._api.getChatMember(this.chatId, userId)
}
}
module.exports = Scope