yanyuyijian/Conf/Vtop/TGbotonCFworker2.0.js
2021-06-14 15:44:56 +08:00

853 lines
31 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 功能: 部署在 cloudflare worker 的 TGbot 后台代码,用于通过 telegram 查看/控制 elecV2P
* 地址: https://github.com/elecV2/elecV2P-dei/blob/master/examples/TGbotonCFworker2.0.js
* 更新: 2021-05-26
* 说明: 功能实现主要基于 elecV2P 的 webhookhttps://github.com/elecV2/elecV2P-dei/tree/master/docs/09-webhook.md
*
* 使用方式:
* 1. 准备工作
* - elecV2P 服务器配置域名访问(测试: http://你的 elecV2P 服务器地址/webhook?token=你的webhook token&type=status
* - 注册并登录 https://dash.cloudflare.com/ ,创建一个 workers 和 KV Namespace(建议命名: elecV2P),并进行绑定
* - 在 https://t.me/botfather 申请一个 TG BOT记下 api token
*
* 2. 部署代码
* - 根据下面代码中 CONFIG_EV2P 的注释,填写好相关内容
* - 然后把修改后的整个 JS 内容粘贴到 cloudflare worker 代码框,保存并部署。得到一个类似 https://xx.xxxxx.workers.dev 的网址
* - 接着在浏览器中打开链接: https://api.telegram.org/bot(你的 tgbot token)/setWebhook?url=https://xx.xxxxx.workers.dev (连接 TGbot 和 CFworkers
* - 最后,打开 TGbot 对话框,输入下面的相关指令(比如 status测试 TGbot 是否部署成功
*
* 2.0 更新: 添加上下文执行环境
* - /runjs 进入脚本执行环境,接下来直接输入文件名或远程链接则可直接运行
* - /task 进入任务操作环境,获取相关任务的 taskid 可暂停/开始/添加定时任务
* - /shell 进入 shell 执行环境,默认 timeout 为 3000mselecV2P v3.2.4 版本后生效)
* - /log 进入 日志查看模式
* - /store 进入 store/cookie 管理模式。默认处于关闭状态,可在 CONFIG_EV2P mode 设置开启
* - /context 获取当前执行环境,如果没有,则为普通模式
* 其它模式完善中...
*
* 特殊指令 sudo clear ; 用于清空当前 context 值(以防出现服务器长时间无返回而卡死的问题)
*
* 下面 /command 命令的优先级高于当前执行环境
*
* 实现功能及相关指令:
* 查看 elecV2P 运行状态
* status === /status ;任何包含 status 关键字的指令
*
* 查看服务器相关信息elecV2P v3.2.6 版本后适用)
* /info
* /info debug
*
* 删除 log 文件
* /deletelog file === /deletelog file.js.log === /dellog file
* /dellog all ;删除使用 log 文件
*
* 查看 log 文件
* /log file
*
* 定时任务相关
* /taskinfo all ;获取所有任务信息
* /taskinfo taskid ;获取单个任务信息
* /taskstart taskid ;开始任务
* /taskstop taskid ;停止任务
* /taskdel taskid ;删除任务
* /tasksave ;保存当前任务列表
*
* 脚本相关
* /runjs file.js ;运行脚本
* /runjs https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/webhook.js
* /runjs https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/feed.js anotify.js ;运行远程脚本同时重命名保存为 anotify.js
* /deljs file.js ;删除脚本
*
* shell 指令相关
* /exec ls === /shell ls === exec ls
* exec pm2 ls
*
* bot commands 2.0
runjs - 运行 JS
task - 开始暂停任务
status - 内存使用状态
shell - 执行简单 shell 指令
store - store/cookie 管理
end - 退出当前执行环境
tasksave - 保存任务列表
taskdel - 删除任务
deljs - 删除 JS
dellog - 删除日志
log - 获取日志
context - 查看当前执行模式
info - 查看服务器信息
command - 列出所有指令
* 更新方式:
* - 如果在 CONFIG_EV2P 中设置了 store直接复制当前整个文件到 cf worker 即可
* - 如果没有设置 store则复制除了开头的 CONFIG_EV2P 外其他所有内容到 cf worker
*
* 适用版本: elecV2P v3.3.6 (低版本下部分指令可能无法正常处理)
**/
const kvname = elecV2P // 保存上下文内容的 kv namespace。在 cf 上创建并绑定后自行更改
let CONFIG_EV2P = {
name: 'elecV2P', // bot 名称。可省略
store: 'elecV2PBot_CONFIG', // 是否将当前 CONFIG 设置保存到 kv 库(运行时会自动读取并覆盖下面的设置,即下面的设置更改无效(方便更新)。建议调试时留空,调试完成后再设置回 'elecV2PBot_CONFIG'
storeforce: false, // true: 使用当前设置强制覆盖 cf kv 库中的数据false: kv 库中有配置相关数据则读取,没有则使用当前设置运行并保存
url: "http://你的 elecV2P 服务器地址/", // elecV2P 服务器地址(必须是域名cf worker 不支持 IP 直接访问)
wbrtoken: 'xxxxxx-xxxxxxxxxxxx-xxxx', // elecV2P 服务器 webhook token(在 webUI->SETTING 界面查看)
token: "xxxxxxxx:xxxxxxxxxxxxxxxxxxx", // telegram bot api token
userid: [], // 只对该列表中的 userid 发出的指令进行回应。默认: 回应所有用户的指令(高风险!)
slice: -1200, // 截取部分返回结果的最后 1200 个字符,以防太长无法传输(可自行修改)
shell: {
timeout: 1000*6, // shell exec 超时时间,单位: ms
contexttimeout: 1000*60*5, // shell 模式自动退出时间,单位: ms
},
timeout: 5000, // runjs 请求超时时间以防脚本运行时间过长无回应导致反复请求bot 被卡死
mycommand: { // 自定义快捷命令,比如 restart: 'exec pm2 restart elecV2P'
rtest: '/runjs test.js', // 表示当输入命令 /rtest 或 rtest 时会自动替换成命令 '/runjs test.js' 运行 JS 脚本 test.js
execls: 'exec ls -al', // 同上,表示自动将命令 /execls 替换成 exec ls -al。 其他命令可参考自行添加
update: { // 当为 object 类型时note 表示备注显示信息, command 表示实际执行命令
note: '软更新升级',
command: 'runjs https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/softupdate.js'
}
},
mode: {
storemanage: false, // 是否开启 store/cookie 管理模式。false: 不开启默认true: 开启
}
}
/************ 后面部分为主运行代码,若没有特殊情况,无需改动 ****************/
const store = {
put: async (key, value)=>{
return await kvname.put(key, value)
},
get: async (key, type)=>{
return await kvname.get(key, type)
},
delete: async (key)=>{
await kvname.delete(key)
},
list: async ()=>{
const val = await kvname.list()
return val.keys
}
}
const context = {
get: async (uid) => {
return await store.get(uid, 'json')
},
put: async (uid, uenv, command) => {
let ctx = await context.get(uid)
if (ctx === null || typeof ctx !== 'object') {
ctx = {
command: []
}
}
if (uenv) {
ctx.context = uenv
}
if (command) {
ctx.command ? ctx.command.push(command) : ctx.command = [command]
}
ctx.active = Date.now()
await store.put(uid, JSON.stringify(ctx))
},
run: async (uid, target) => {
const ctx = await context.get(uid)
},
end: async (uid) => {
await store.put(uid, JSON.stringify({}))
}
}
function surlName(url) {
if (!url) {
return ''
}
let name = ''
let sdurl = url.split(/\/|\?|#/)
while (name === '' && sdurl.length) {
name = sdurl.pop()
}
return name
}
function timeoutPromise({ timeout = CONFIG_EV2P.timeout || 5000, fn }) {
return new Promise(resolve => setTimeout(resolve, timeout, '请求超时 ' + timeout + ' ms相关请求应该已发送至 elecV2P这里提前返回结果以免发送重复请求' + `${fn ? ('\n\n运行日志: ' + CONFIG_EV2P.url + 'logs/' + surlName(fn) + '.log') : '' }`))
}
function getLogs(s){
if (s !== 'all' && !/\.log$/.test(s)) {
s = s + '.js.log'
}
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=getlog&fn=' + s).then(res=>res.text()).then(r=>{
resolve(s === 'all' ? r : r.slice(CONFIG_EV2P.slice))
}).catch(e=>{
reject(e)
})
})
}
function delLogs(logn) {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=deletelog&fn=' + logn).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function getStatus() {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?type=status&token=' + CONFIG_EV2P.wbrtoken).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function getInfo(debug) {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?type=info&token=' + CONFIG_EV2P.wbrtoken + (debug ? '&debug=true' : '')).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function getTaskinfo(tid) {
tid = tid.replace(/^\//, '')
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=taskinfo&tid=' + tid).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function opTask(tid, op) {
if (!/start|stop|del|delete/.test(op)) {
return 'unknow operation' + op
}
tid = tid.replace(/^\//, '')
if (/^\/?stop/.test(tid)) {
op = 'stop'
tid = tid.replace(/^\/?stop/, '')
}
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=task' + op + '&tid=' + tid).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function saveTask() {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=tasksave').then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function taskNew(taskinfo) {
// 新建任务
if (!taskinfo) {
return '没有任何任务信息'
}
let finfo = taskinfo.split(/\r|\n/)
if (finfo.length < 2) {
return '任务信息输入有误 '
}
taskinfo = {
name: finfo[2] || '新的任务' + Math.ceil(Math.random()*100),
type: finfo[0].split(' ').length > 4 ? 'cron' : 'schedule',
time: finfo[0],
job: {
type: finfo[3] || 'runjs',
target: finfo[1],
},
running: finfo[4] !== 'false'
}
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
token: CONFIG_EV2P.wbrtoken,
type: 'taskadd',
task: taskinfo
})
}).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function jsRun(fn, rename) {
let rfn = fn.split(/ +/)
if (rfn.length !== 1) {
fn = rfn[0]
rename = rfn[1]
}
return Promise.race([new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=runjs&fn=' + fn + (rename ? '&rename=' + rename : '')).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
}), timeoutPromise({ fn })])
}
function getJsLists() {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=jslist').then(res=>res.json()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function deleteJS(name) {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=deletejs&fn=' + name).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function shellRun(command) {
if (command) {
command = encodeURI(command)
} else {
return '请输入 command 指令,比如: ls'
}
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + `&type=shell&timeout=${CONFIG_EV2P.shell && CONFIG_EV2P.shell.timeout || 3000}&command=` + command).then(res=>res.text()).then(r=>{
resolve(r.slice(CONFIG_EV2P.slice))
}).catch(e=>{
reject(e)
})
})
}
function storeManage(keyvt) {
if (!keyvt) {
return '请输入要获取的 cookie/store 相关的 key 值'
}
let keys = keyvt.split(' ')
if (keys.length === 1) {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + `&type=store&key=${keyvt}`).then(res=>res.text()).then(r=>{
if (r) {
resolve(r.slice(CONFIG_EV2P.slice))
} else {
resolve(keyvt + ' 暂不存在')
}
}).catch(e=>{
reject(e)
})
})
} else {
let body = {
token: CONFIG_EV2P.wbrtoken,
type: 'store'
}
if (keys[0] === 'delete') {
body.op = 'delete'
body.key = keys[1]
} else {
body.op = 'put'
body.key = keys[0]
body.value = decodeURI(keys[1])
body.options = {
type: keys[2]
}
}
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}).then(res=>res.text()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
}
function storeList() {
return new Promise((resolve,reject)=>{
fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=store&op=all').then(res=>res.json()).then(r=>{
resolve(r)
}).catch(e=>{
reject(e)
})
})
}
function getFile(file_id) {
return new Promise((resolve,reject)=>{
fetch(`https://api.telegram.org/bot${CONFIG_EV2P.token}/getFile?file_id=${file_id}`).then(res=>res.json()).then(r=>{
if (r.ok) {
resolve(`https://api.telegram.org/file/bot${CONFIG_EV2P.token}/${r.result.file_path}`)
} else {
resolve(r.description)
}
}).catch(e=>{
reject(e)
})
})
}
async function handlePostRequest(request) {
if (CONFIG_EV2P.store) {
let config = await store.get(CONFIG_EV2P.store, 'json')
if (!CONFIG_EV2P.storeforce && config) {
Object.assign(CONFIG_EV2P, config)
} else {
await store.put(CONFIG_EV2P.store, JSON.stringify(CONFIG_EV2P))
}
}
if (!CONFIG_EV2P.url.endsWith('/')) {
CONFIG_EV2P.url = CONFIG_EV2P.url + '/'
}
CONFIG_EV2P.timeout = CONFIG_EV2P.timeout || 5000
let bodyString = await readRequestBody(request)
let payload = {
"method": "sendMessage",
"chat_id": CONFIG_EV2P.userid[0],
"parse_mode": "html",
"disable_web_page_preview": true,
}
try {
let body = JSON.parse(bodyString)
if (!body.message) {
payload.text = 'elecV2P bot get unknow message:\n' + bodyString
await tgPush(payload)
return new Response("OK")
}
payload["chat_id"] = body.message.chat.id
if (body.message.document) {
let bodydoc = body.message.document
payload.text = `文件名称: ${bodydoc.file_name}\n文件类型: ${bodydoc.mime_type}\n文件 id: ${bodydoc.file_id}\n`
let fpath = await getFile(bodydoc.file_id)
payload.text += `文件地址: ${fpath}\n\n(进一步功能待完成)`
await tgPush(payload)
return new Response("OK")
}
if (body.message.text) {
let bodytext = body.message.text.trim()
let uid = 'u' + payload['chat_id']
if (CONFIG_EV2P.mycommand && Object.keys(CONFIG_EV2P.mycommand).length) {
let tcom = bodytext.replace(/^\//, '')
if (CONFIG_EV2P.mycommand[tcom]) {
bodytext = CONFIG_EV2P.mycommand[tcom].command || CONFIG_EV2P.mycommand[tcom]
}
}
if (bodytext === 'sudo clear') {
await store.delete(uid)
payload.text = 'current context is cleared.'
tgPush(payload)
return new Response("OK")
} else if (bodytext === '/command') {
payload.text = `/runjs - 运行 JS
/task - 开始暂停任务
/status - 内存使用状态
/shell - 执行简单 shell 指令
/store - store/cookie 管理
/end - end context
/tasksave - 保存任务列表
/log - 获取日志
/context - 查看当前执行模式
/info - 查看服务器信息
/command - 列出所有指令`
if (CONFIG_EV2P.mycommand && Object.keys(CONFIG_EV2P.mycommand).length) {
payload.text += '\n\n自定义快捷命令'
for (let x in CONFIG_EV2P.mycommand) {
payload.text += '\n' + (x.startsWith('/') ? '' : '/') + x + ' - ' + (CONFIG_EV2P.mycommand[x].note || CONFIG_EV2P.mycommand[x])
}
}
await tgPush(payload)
return new Response("OK")
}
let userenv = await context.get(uid)
if (CONFIG_EV2P.userid && CONFIG_EV2P.userid.length && CONFIG_EV2P.userid.indexOf(body.message.chat.id) === -1) {
payload.text = "这是 " + CONFIG_EV2P.name + " 私人 bot不接受其他人的指令。\n如果有兴趣可以自己搭建一个: https://github.com/elecV2/elecV2P-dei\n\n频道: @elecV2 | 交流群: @elecV2G"
tgPush({
...payload,
"chat_id": CONFIG_EV2P.userid[0],
"text": `用户: ${body.message.chat.username}ID: ${body.message.chat.id} 正在连接 elecV2P bot发出指令为: ${bodytext}`
})
} else if (/^\/?end/.test(bodytext)) {
await context.end(uid)
payload.text = `退出上文执行环境${(userenv && userenv.context) || ''},回到普通模式`
} else if (/^\/?context$/.test(bodytext)) {
if (userenv && userenv.context) {
payload.text = '当前执行环境为: ' + userenv.context + '\n输入 end 回到普通模式'
} else {
payload.text = '当前执行环境为: 普通模式'
}
} else if (/^\/?status/.test(bodytext)) {
payload.text = await getStatus()
} else if (/^\/?info/.test(bodytext)) {
let cont = bodytext.trim().split(' ')
if (cont.length === 1) {
payload.text = await getInfo()
} else if (cont.pop() === 'debug') {
payload.text = await getInfo('debug')
} else {
payload.text = 'unknow info command'
}
} else if (/^\/?(dellog|deletelog) /.test(bodytext)) {
let cont = bodytext.replace(/^\/?(dellog|deletelog) /, '')
if (!(cont === 'all' || /\.log$/.test(cont))) cont = cont + '.js.log'
payload.text = await delLogs(cont)
} else if (/^\/?taskinfo /.test(bodytext)) {
let cont = bodytext.replace(/^\/?taskinfo /, '')
payload.text = await getTaskinfo(cont)
} else if (/^\/?taskstart /.test(bodytext)) {
let cont = bodytext.replace(/^\/?taskstart /, '')
payload.text = await opTask(cont, 'start')
} else if (/^\/?taskstop /.test(bodytext)) {
let cont = bodytext.replace(/^\/?taskstop /, '')
payload.text = await opTask(cont, 'stop')
} else if (/^\/?taskdel /.test(bodytext)) {
let cont = bodytext.replace(/^\/?taskdel /, '')
payload.text = await opTask(cont, 'del')
} else if (/^\/?tasksave/.test(bodytext)) {
payload.text = await saveTask()
} else if (/^\/?deljs /.test(bodytext)) {
let cont = bodytext.replace(/^\/?deljs /, '')
payload.text = await deleteJS(cont)
} else if (/^\/?task/.test(bodytext)) {
let cont = bodytext.trim().split(' ')
if (cont.length === 1) {
try {
await context.put('u' + payload['chat_id'], 'task')
let tasklists = await getTaskinfo('all')
let tlist = JSON.parse(tasklists)
let tlstr = ''
for (let tid in tlist.info) {
tlstr += `${tlist.info[tid].running ? '🐢' : '🐰'} ${tlist.info[tid].name} /${tid} | /stop${tid}\n`
}
tlstr += `${tlist.total} 个定时任务,运行中(🐢)的任务 ${tlist.running}`
payload.text = `当前 elecV2P 任务列表如下:\n${tlstr}\n点击任务名后面的 /+tid 开始任务,/+stoptid 停止任务\n也可以手动输入对应的 tid 开始任务, stop tid 停止任务\ntaskinfo tid 查看任务信息`
await tgPush(payload)
payload.text = `按照下面格式多行输入可直接添加新的任务(每行表示一个任务参数)\n
任务时间(cron 定时,比如: 8 0,8 * * * ,倒计时,比如: 1 10 6)
任务目标(test.jsnode -v, LOlxkcdI(某个任务的 tid),远程 JS 链接等)
任务名称(可省略,默认为 新的任务+随机参数)
任务类型(可省略,默认为 运行 JSshell: 运行 shell 指令taskstart开始其他任务taskstop停止其他任务)
是否执行(可省略,默认为 true当且仅当该值为 false 时,表示只添加任务信息而不运行)
示例一:添加一个 cron 定时任务
30 20 * * *
https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/deletelog.js
删除日志
示例二:添加一个倒计时任务,运行 test.js每次倒计时 1 秒,执行 3 次
1 3
test.js`
} catch(e) {
payload.text = e.message
}
} else {
payload.text = 'unknow task operation'
}
} else if (/^\/?runjs/.test(bodytext)) {
let cont = bodytext.trim().split(/ +/)
if (cont.length === 1) {
try {
await context.put('u' + payload['chat_id'], 'runjs')
let jslists = await getJsLists()
let keyb = {
keyboard: [],
resize_keyboard: false,
one_time_keyboard: true,
selective: true
}
let over = ''
if (jslists.length >= 200) {
over = '\n\n文件数超过 200以防 reply_keyboard 过长 TG 无返回,剩余 JS 以文字形式返回\n\n'
}
for (let ind in jslists) {
let s = jslists[ind]
if (ind >= 200) {
over += s + ' '
continue
}
let row = parseInt(ind/2)
keyb.keyboard[row]
? keyb.keyboard[row].push({
text: s.replace(/\.js$/, '')
})
: keyb.keyboard[row] = [{
text: s.replace(/\.js$/, '')
}]
}
payload.text = '进入 RUNJS 模式,当前 elecV2P 上 JS 文件数: ' + jslists.length + '\n点击运行 JS也可以直接输入文件名或者远程链接\n后面可加空格及其他参数重命名运行的文件比如\nhttps://随便一个远程JS rmyname.js' + over
payload.reply_markup = keyb
} catch(e) {
payload.text = e.message
}
} else {
payload.text = await jsRun(cont[1], cont[2])
}
} else if (/^\/?(shell|exec)/.test(bodytext)) {
let cont = bodytext.trim().split(' ')
if (cont.length === 1) {
try {
await context.put('u' + payload['chat_id'], 'shell')
let keyb = {
keyboard: [
[{text: 'ls'}, {text: 'node -v'}],
[{text: 'apk add python3 ffmpeg'}],
[{text: 'python3 -V'}, {text: 'pm2 ls'}]
],
resize_keyboard: false,
one_time_keyboard: true,
selective: true
}
payload.text = '进入 SHELL 模式,可执行简单 shell 指令,比如: ls, node -v 等'
payload.reply_markup = keyb
} catch(e) {
payload.text = e.message
}
} else {
payload.text = await shellRun(bodytext.replace(/^\/?(shell|exec) /, ''))
}
} else if (/^\/?store/.test(bodytext)) {
if (CONFIG_EV2P.mode && CONFIG_EV2P.mode.storemanage) {
let cont = bodytext.trim().split(' ')
if (cont.length === 1) {
try {
await context.put('u' + payload['chat_id'], 'store')
let storelists = await storeList()
let keyb = {
keyboard: [],
resize_keyboard: false,
one_time_keyboard: true,
selective: true
}
let over = ''
if (storelists.length >= 200) {
over = '\n\nCookie 数超过 200以防 reply_keyboard 过长 TG 无返回,剩余 Cookie KEY 以文字形式返回\n\n'
}
for (let ind in storelists) {
let s = storelists[ind]
if (ind >= 200) {
over += s + ' '
continue
}
let row = parseInt(ind/2)
keyb.keyboard[row]
? keyb.keyboard[row].push({
text: s
})
: keyb.keyboard[row] = [{
text: s
}]
}
payload.reply_markup = keyb
payload.text = '进入 cookie/store 管理模式,当前 elecV2P 上 Cookie 数: ' + storelists.length + '\n\n点击或者直接输入关键字(key)查看 store 内容,比如 cookieKEY\n\n输入 delete key 删除某个 Cookie。比如: delete cookieKEY\n\n输入 key value type(可省略) 修改 store 内容(以空格进行分隔)。如果 value 中包含空格等其他特殊字符,请先使用 encodeURI 函数进行转换。比如:\n\nCookieJD pt_pin=xxx;%20pt_key=app_xxxxxxx;\n\ntype 可省略,也可设定为:\nstring 表示将 value 保存为普通字符(默认)\nobject 表示将 value 保存为 json 格式\na 表示在原来的值上新增。(更多说明可参考 https://github.com/elecV2/elecV2P-dei/tree/master/docs/04-JS.md $store 部分)' + over
} catch(e) {
payload.text = e.message
}
} else {
payload.text = await storeManage(bodytext.replace(/^\/?store /, ''))
}
} else {
payload.text = 'store/cookie 管理模式处于关闭状态'
}
} else if (/^\/?log/.test(bodytext)) {
let cont = bodytext.trim().split(' ')
if (cont.length === 1) {
try {
await context.put('u' + payload['chat_id'], 'log')
let res = await getLogs('all')
let map = JSON.parse(res)
let keyb = {
inline_keyboard: [ ],
}
map.forEach((s, ind)=> {
let row = parseInt(ind/2)
keyb.inline_keyboard[row]
? keyb.inline_keyboard[row].push({
text: s.replace(/\.js\.log$/g, ''),
url: CONFIG_EV2P.url + 'logs/' + s
})
: keyb.inline_keyboard[row] = [{
text: s.replace(/\.js\.log$/g, ''),
url: CONFIG_EV2P.url + 'logs/' + s
}]
})
payload.text = "开始日志查看模式,当前 elecV2P 上日志文件数: " + map.length + "\n点击查看日志或者直接输入 log 文件名进行查看"
payload.reply_markup = keyb
} catch(e) {
payload.text = e.message
}
} else {
payload.text = await getLogs(bodytext.replace(/^\/?log /, ''))
}
} else if (userenv && userenv.context) {
switch (userenv.context) {
case 'log':
payload.text = await getLogs(bodytext)
break
case 'runjs':
payload.text = await jsRun(bodytext)
break
case 'task':
if (bodytext.trim().split(/\r|\n/).length > 1) {
payload.text = await taskNew(bodytext)
} else {
payload.text = await opTask(bodytext.split(' ').pop(), /^(🐢|\/?stop)/.test(bodytext) ? 'stop' : 'start')
}
break
case 'shell':
if (Date.now() - userenv.active > (CONFIG_EV2P.shell && CONFIG_EV2P.shell.contexttimeout)) {
payload.text = '已经超过 ' + CONFIG_EV2P.shell.contexttimeout/1000/60 + ' 分钟没有执行 shell 指令,自动退出 shell 模式\n使用 /shell 命令重新进入\n/end 回到普通模式\n/command 查看所有指令'
payload.reply_markup = JSON.stringify({
remove_keyboard: true
})
userenv.context = 'normal'
} else {
payload.text = await shellRun(bodytext)
}
break
case 'store':
if (CONFIG_EV2P.mode && CONFIG_EV2P.mode.storemanage) {
payload.text = await storeManage(bodytext)
} else {
payload.text = 'store/cookie 管理模式处于关闭状态'
}
break
default: {
payload.text = '当前执行环境: ' + userenv.context + ' 无法处理指令: ' + bodytext
}
}
await context.put(uid, userenv.context, bodytext)
} else {
payload.text = 'TGbot 部署成功,可以使用相关指令和 elecV2P 服务器进行交互了\nPowered By: https://github.com/elecV2/elecV2P\n\n频道: @elecV2 | 交流群: @elecV2G'
if (CONFIG_EV2P.userid.length === 0) {
payload.text += '\n危险⚠当前 elecV2P bot 并没有设置 userid所有人可进行交互'
}
if (bodytext === '/start') {
let status = ''
try {
status = await getStatus()
status = '当前 bot 与 elecV2P 连接成功 ' + status
} catch(e) {
status = (e.message || e) + '\nelecV2P 服务器没有响应,请检查服务器地址和 webhook token 是否设置正确。'
}
payload.text += '\n' + status
}
}
await tgPush(payload)
return new Response("OK")
}
return new Response(JSON.stringify(body), {
headers: { 'content-type': 'application/json' },
})
} catch(e) {
payload.text = e.message || e
tgPush(payload)
return new Response("OK")
}
}
async function handleRequest(request) {
let retBody = `welcome to elecV2P.\n\nPowered By: https://github.com/elecV2/elecV2P\n\nTG 频道: https://t.me/elecV2 | TG 交流群: @elecV2G`
return new Response(retBody)
}
addEventListener('fetch', event => {
const { request } = event
// const { url } = request
if (request.method === 'POST') {
return event.respondWith(handlePostRequest(request))
} else if (request.method === 'GET') {
return event.respondWith(handleRequest(request))
}
})
/**
* readRequestBody reads in the incoming request body
* Use await readRequestBody(..) in an async function to get the string
* @param {Request} request the incoming request to read from
*/
async function readRequestBody(request) {
const { headers } = request
const contentType = headers.get('content-type')
if (contentType.includes('application/json')) {
const body = await request.json()
return JSON.stringify(body)
} else if (contentType.includes('application/text')) {
const body = await request.text()
return body
} else if (contentType.includes('text/html')) {
const body = await request.text()
return body
} else if (contentType.includes('form')) {
const formData = await request.formData()
let body = {}
for (let entry of formData.entries()) {
body[entry[0]] = entry[1]
}
return JSON.stringify(body)
} else {
let myBlob = await request.blob()
var objectURL = URL.createObjectURL(myBlob)
return objectURL
}
}
async function tgPush(payload) {
const myInit = {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify(payload)
};
let myRequest = new Request(`https://api.telegram.org/bot${CONFIG_EV2P.token}/`, myInit)
await fetch(myRequest)
}