diff --git a/docker/Dockerfile b/docker/Dockerfile index 01c0b43f..b305cec0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -7,6 +7,7 @@ ARG KEY="-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAA ENV DEFAULT_LIST_FILE=crontab_list.sh \ CUSTOM_LIST_MERGE_TYPE=append \ + COOKIES_LIST=/scripts/logs/cookies.list \ REPO_URL=git@gitee.com:lxk0301/jd_scripts.git \ REPO_BRANCH=master diff --git a/docker/Readme.md b/docker/Readme.md index c9e568b2..11fc7f31 100644 --- a/docker/Readme.md +++ b/docker/Readme.md @@ -1,6 +1,19 @@ ![Docker Pulls](https://img.shields.io/docker/pulls/lxk0301/jd_scripts?style=for-the-badge) ### Usage ```diff ++ 2021-03-21更新 增加bot交互,spnode指令,功能是否开启自动根据你的配置判断,详见 https://gitee.com/lxk0301/jd_docker/pulls/18 + **bot交互启动前置条件为 配置telegram通知,并且未使用自己代理的 TG_API_HOST** + **spnode使用前置条件未启动bot交互** _(后续可能去掉该限制_ + 使用bot交互+spnode后 后续用户的cookie维护更新只需要更新logs/cookies.conf即可 + 使用bot交互+spnode后 后续执行脚本命令请使用spnode否者无法使用logs/cookies.conf的cookies执行脚本,定时任务也将自动替换为spnode命令执行 + 发送/spnode给bot获取可执行脚本的列表,选择对应的按钮执行。(拓展使用:运行指定路径脚本,例:/spnode /scripts/jd_818.js) + spnode功能概述示例 + spnode conc /scripts/jd_bean_change.js 为每个cookie单独执行jd_bean_change脚本(伪并发 + spnode 1 /scripts/jd_bean_change.js 为logs/cookies.conf文件里面第一行cookie账户单独执行jd_bean_change脚本 + spnode jd_XXXX /scripts/jd_bean_change.js 为logs/cookies.conf文件里面pt_pin=jd_XXXX的cookie账户单独执行jd_bean_change脚本 + spnode /scripts/jd_bean_change.js 为logs/cookies.conf所有cookies账户一起执行jd_bean_change脚本 + +**请仔细阅读并理解上面的内容,使用bot交互默认开启spnode指令功能功能。** + 2021-03-9更新 新版docker单容器多账号自动互助 +开启方式:docker-compose.yml 中添加环境变量 - ENABLE_AUTO_HELP=true +助力原则:不考虑需要被助力次数与提供助力次数 假设有3个账号,则生成: ”助力码1@助力码2@助力码3&助力码1@助力码2@助力码3&助力码1@助力码2@助力码3“ @@ -210,8 +223,6 @@ jd_scripts `docker exec -it jd_scripts /bin/sh -c 'env'` 查看设置的环境变量 - `docker exec -it jd_scripts /bin/sh -c 'crontab -l'` 查看crontab_list列表 - `docker exec -it jd_scripts /bin/sh -c 'git pull'` 手动更新jd_scripts仓库最新脚本 `docker exec -it jd_scripts /bin/sh` 仅进入容器命令 diff --git a/docker/bot/jd.png b/docker/bot/jd.png new file mode 100644 index 00000000..c948dada Binary files /dev/null and b/docker/bot/jd.png differ diff --git a/docker/bot/jd_bot b/docker/bot/jd_bot new file mode 100644 index 00000000..791055e7 --- /dev/null +++ b/docker/bot/jd_bot @@ -0,0 +1,1039 @@ +# -*- coding: utf-8 -*- +# @Author : iouAkira(lof) +# @mail : e.akimoto.akira@gmail.com +# @CreateTime: 2020-11-02 +# @UpdateTime: 2021-03-21 + +import math +import os +import subprocess +from subprocess import TimeoutExpired +from MyQR import myqr +import requests +import time +import logging +import re +from urllib.parse import quote, unquote + +import telegram.utils.helpers as helpers +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode +from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler + +# 启用日志 +logging.basicConfig(format='%(asctime)s-%(name)s-%(levelname)s=> [%(funcName)s] %(message)s ', level=logging.INFO) +logger = logging.getLogger(__name__) + +_base_dir = '/scripts/' +_logs_dir = '%slogs/' % _base_dir +_docker_dir = '%sdocker/' % _base_dir +_bot_dir = '%sbot/' % _docker_dir +_share_code_conf = '%scode_gen_conf.list' % _logs_dir + +if 'GEN_CODE_CONF' in os.environ: + share_code_conf = os.getenv("GEN_CODE_CONF") + if share_code_conf.startswith("/"): + _share_code_conf = share_code_conf + + +def start(update, context): + from_user_id = update.message.from_user.id + if admin_id == str(from_user_id): + spnode_readme = "" + if "DISABLE_SPNODE" not in os.environ: + spnode_readme = "/spnode 获取可执行脚本的列表,选择对应的按钮执行。(拓展使用:运行指定路径脚本,例:/spnode /scripts/jd_818.js)\n" \ + "使用bot交互+spnode后 后续用户的cookie维护更新只需要更新logs/cookies.conf即可\n" \ + "使用bot交互+spnode后 后续执行脚本命令请使用spnode否者无法使用logs/cookies.conf的cookies执行脚本,定时任务也将自动替换为spnode命令执行\n" \ + "spnode功能概述示例\n" \ + "spnode conc /scripts/jd_bean_change.js 为每个cookie单独执行jd_bean_change脚本(伪并发\n" \ + "spnode 1 /scripts/jd_bean_change.js 为logs/cookies.conf文件里面第一行cookie账户单独执行jd_bean_change脚本\n" \ + "spnode jd_XXXX /scripts/jd_bean_change.js 为logs/cookies.conf文件里面pt_pin=jd_XXXX的cookie账户单独执行jd_bean_change脚本\n" \ + "spnode /scripts/jd_bean_change.js 为logs/cookies.conf所有cookies账户一起执行jd_bean_change脚本\n" \ + "请仔细阅读并理解上面的内容,使用bot交互默认开启spnode指令功能功能。\n" \ + "如需____停用___请配置环境变量 -DISABLE_SPNODE=True" + context.bot.send_message(chat_id=update.effective_chat.id, + text="限制自己使用的JD Scripts拓展机器人\n" \ + "\n" \ + "/start 开始并获取指令说明\n" \ + "/node 获取可执行脚本的列表,选择对应的按钮执行。(拓展使用:运行指定路径脚本,例:/node /scripts/jd_818.js) \n" \ + "/git 获取可执行git指令列表,选择对应的按钮执行。(拓展使用:运行指定路径脚本,例:/git -C /scripts/ pull)\n" \ + "/logs 获取logs下的日志文件列表,选择对应名字可以下载日志文件\n" \ + "/env 获取系统环境变量列表。(拓展使用:设置系统环境变量,例:/env export JD_DEBUG=true,环境变量只针对当前bot进程生效) \n" \ + "/bash 执行执行命令。参考:/bash ls -l 未完善不开放使用\n" \ + "/gen_long_code 长期活动互助码提交消息生成\n" \ + "/gen_temp_code 短期临时活动互助码提交消息生成\n" \ + "/gen_daily_code 每天变化互助码活动提交消息生成\n\n%s" % spnode_readme) + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def node(update, context): + """关于定时任务中nodejs脚本的相关操作 + """ + if is_admin(update.message.from_user.id): + commands = update.message.text.split() + commands.remove('/node') + if len(commands) > 0: + cmd = 'node %s' % (' '.join(commands)) + try: + + out_bytes = subprocess.check_output( + cmd, shell=True, timeout=600, stderr=subprocess.STDOUT) + + out_text = out_bytes.decode('utf-8') + + if len(out_text.split()) > 30: + + msg = context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果超长,请查看log ↓↓↓' % cmd)), chat_id=update.effective_chat.id, + parse_mode=ParseMode.MARKDOWN_V2) + + log_name = '%sbot_%s_%s.log' % ( + _logs_dir, 'node', os.path.splitext(commands[-1])[0]) + + with open(log_name, 'a+') as wf: + wf.write(out_text) + + msg.reply_document( + reply_to_message_id=msg.message_id, quote=True, document=open(log_name, 'rb')) + else: + + context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果 ↓↓↓ \n\n%s ' % (cmd, out_text))), + chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + + except TimeoutExpired: + + context.bot.sendMessage(text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行超时 ←←← ' % ( + cmd))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + + except: + + context.bot.sendMessage( + text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行出错,请检查确认命令是否正确 ←←← ' % ( + cmd))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + raise + else: + reply_markup = get_reply_markup_btn('node') + update.message.reply_text(text='```{}```'.format(helpers.escape_markdown(' ↓↓↓ 请选择想要执行的nodejs脚本 ↓↓↓ ')), + reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def spnode(update, context): + """关于定时任务中nodejs脚本的相关操作 + """ + if is_admin(update.message.from_user.id): + commands = update.message.text.split() + commands.remove('/spnode') + if len(commands) > 0: + cmd = 'spnode %s' % (' '.join(commands)) + try: + + out_bytes = subprocess.check_output( + cmd, shell=True, timeout=600, stderr=subprocess.STDOUT) + + out_text = out_bytes.decode('utf-8') + + if len(out_text.split()) > 30: + + msg = context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果超长,请查看log ↓↓↓' % cmd)), chat_id=update.effective_chat.id, + parse_mode=ParseMode.MARKDOWN_V2) + + log_name = '%sbot_%s_%s.log' % ( + _logs_dir, 'spnode', os.path.splitext(commands[-1])[0]) + + with open(log_name, 'a+') as wf: + wf.write(out_text) + + msg.reply_document( + reply_to_message_id=msg.message_id, quote=True, document=open(log_name, 'rb')) + else: + + context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果 ↓↓↓ \n\n%s ' % (cmd, out_text))), + chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + + except TimeoutExpired: + + context.bot.sendMessage(text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行超时 ←←← ' % ( + cmd))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + + except: + + context.bot.sendMessage( + text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行出错,请检查确认命令是否正确 ←←← ' % ( + cmd))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + raise + else: + reply_markup = get_reply_markup_btn('node') + update.message.reply_text(text='```{}```'.format(helpers.escape_markdown(' ↓↓↓ 请选择想要执行的nodejs脚本 ↓↓↓ ')), + reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def git(update, context): + """关于/scripts仓库的git相关操作 + """ + if is_admin(update.message.from_user.id): + commands = update.message.text.split() + commands.remove('/git') + if len(commands) > 0: + cmd = 'git %s' % (' '.join(commands)) + try: + + out_bytes = subprocess.check_output( + cmd, shell=True, timeout=600, stderr=subprocess.STDOUT) + + out_text = out_bytes.decode('utf-8') + + if len(out_text.split()) > 30: + + msg = context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果超长,请查看log ↓↓↓' % cmd)), chat_id=update.effective_chat.id, + parse_mode=ParseMode.MARKDOWN_V2) + + log_name = '%sbot_%s_%s.log' % ( + _logs_dir, 'git', os.path.splitext(commands[-1])[0]) + + with open(log_name, 'a+') as wf: + wf.write(out_text) + + msg.reply_document( + reply_to_message_id=msg.message_id, quote=True, document=open(log_name, 'rb')) + else: + + context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果 ↓↓↓ \n\n%s ' % (cmd, out_text))), + chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + + except TimeoutExpired: + + context.bot.sendMessage(text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行超时 ←←← ' % ( + cmd))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + + except: + + context.bot.sendMessage( + text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行出错,请检查确认命令是否正确 ←←← ' % ( + cmd))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + raise + + else: + reply_markup = get_reply_markup_btn('git') + + update.message.reply_text(text='```{}```'.format(helpers.escape_markdown(' ↓↓↓ 请选择想要执行的git指令 ↓↓↓ ')), + reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def env(update, context): + """env 环境变量相关操作 + """ + if is_admin(update.message.from_user.id): + commands = update.message.text.split() + commands.remove('/env') + if len(commands) == 2 and (commands[0]) == 'export': + + try: + envs = commands[1].split('=') + os.putenv(envs[0], envs[1]) + + context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ 环境变量设置成功 ↓↓↓ \n\n%s ' % ('='.join(envs)))), + chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + except: + context.bot.sendMessage( + text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行出错,请检查确认命令是否正确 ←←← ' % ( + ' '.join(commands)))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + raise + + elif len(commands) == 0: + out_bytes = subprocess.check_output( + 'env', shell=True, timeout=600, stderr=subprocess.STDOUT) + + out_text = out_bytes.decode('utf-8') + + if len(out_text.split()) > 30: + + msg = context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果超长,请查看log ↓↓↓' % 'env')), chat_id=update.effective_chat.id, + parse_mode=ParseMode.MARKDOWN_V2) + + log_name = '%sbot_%s.log' % (_logs_dir, 'env') + + with open(log_name, 'a+') as wf: + wf.write(out_text + '\n\n') + + msg.reply_document( + reply_to_message_id=msg.message_id, quote=True, document=open(log_name, 'rb')) + else: + + context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ env 执行结果 ↓↓↓ \n\n%s ' % out_text)), + chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + else: + context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' →→→ env 指令不正确,请参考说明输入 ←←← ')), chat_id=update.effective_chat.id, + parse_mode=ParseMode.MARKDOWN_V2) + + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def crontab(update, context): + """关于crontab 定时任务相关操作 + """ + reply_markup = get_reply_markup_btn('crontab_l') + + if is_admin(update.message.from_user.id): + try: + update.message.reply_text(text='```{}```'.format(helpers.escape_markdown(' ↓↓↓ 下面为定时任务列表,请选择需要的操作 ↓↓↓ ')), + reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) + except: + raise + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def logs(update, context): + """关于_logs_dir目录日志文件的相关操作 + """ + reply_markup = get_reply_markup_btn('logs') + + if is_admin(update.message.from_user.id): + update.message.reply_text(text='```{}```'.format(helpers.escape_markdown(' ↓↓↓ 请选择想想要下载的日志文件或者清除所有日志 ↓↓↓ ')), + reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN_V2) + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def callback_run(update, context): + """执行按钮响应回调方法处理按钮点击事件 + """ + query = update.callback_query + select_btn_type = query.data.split()[0] + chat = query.message.chat + logger.info("callback query.message.chat ==> %s " % chat) + logger.info("callback query.data ==> %s " % query.data) + if select_btn_type == 'node' or select_btn_type == 'spnode' or select_btn_type == 'git': + try: + context.bot.edit_message_text(text='```{}```'.format(' ↓↓↓ 任务正在执行 ↓↓↓ \n\n%s' % query.data), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + out_bytes = subprocess.check_output( + query.data, shell=True, timeout=600, stderr=subprocess.STDOUT) + out_text = out_bytes.decode('utf-8') + if len(out_text.split()) > 30: + context.bot.edit_message_text(text='```{}```'.format(' ↓↓↓ %s 执行结果超长,请查看log ↓↓↓' % query.data), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + log_name = '%sbot_%s_%s.log' % ( + _logs_dir, select_btn_type, os.path.splitext(query.data.split('/')[-1])[0]) + logger.info('log_name==>' + log_name) + + with open(log_name, 'a+') as wf: + wf.write(out_text) + + query.message.reply_document( + reply_to_message_id=query.message.message_id, quote=True, document=open(log_name, 'rb')) + + else: + context.bot.edit_message_text(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果 ↓↓↓ \n\n%s ' % (query.data, out_text))), + chat_id=update.effective_chat.id, message_id=query.message.message_id, + parse_mode=ParseMode.MARKDOWN_V2) + except TimeoutExpired: + logger.error(' →→→ %s 执行超时 ←←← ' % query.data) + context.bot.edit_message_text(text='```{}```'.format(' →→→ %s 执行超时 ←←← ' % query.data), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + except: + context.bot.edit_message_text(text='```{}```'.format(' →→→ %s 执行出错 ←←← ' % query.data), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + raise + elif select_btn_type.startswith('cl'): + if select_btn_type == 'cl': + logger.info(f'crontab_l ==> {query.data.split()[1:]}') + reply_markup = InlineKeyboardMarkup([ + [InlineKeyboardButton( + ' '.join(query.data.split()[1:]), callback_data='pass')], + [InlineKeyboardButton('⚡️执行', callback_data='cle %s' % ' '.join(query.data.split()[1:])), + InlineKeyboardButton('❌删除', callback_data='cld %s' % ' '.join(query.data.split()[1:]))] + ]) + context.bot.edit_message_text(text='```{}```'.format(' ↓↓↓ 请选择对该定时任务的操作 ↓↓↓ '), + chat_id=query.message.chat_id, + reply_markup=reply_markup, message_id=query.message.message_id, + parse_mode=ParseMode.MARKDOWN_V2) + elif select_btn_type == 'cle': + cmd = ' '.join(query.data.split()[6:]) + try: + context.bot.edit_message_text(text='```{}```'.format(' ↓↓↓ 任务正在执行 ↓↓↓ \n\n%s' % cmd), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + out_bytes = subprocess.check_output(cmd, shell=True, timeout=600, + stderr=subprocess.STDOUT) + out_text = out_bytes.decode('utf-8') + if len(out_text.split()) > 30: + + context.bot.edit_message_text(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果超长,请查看log ↓↓↓' % cmd)), + chat_id=update.effective_chat.id, message_id=query.message.message_id, + parse_mode=ParseMode.MARKDOWN_V2) + + log_name = '%sbot_%s_%s.log' % ( + _logs_dir, select_btn_type, os.path.splitext(cmd.split('/')[-1])[0]) + + with open(log_name, 'a+') as wf: + wf.write(out_text) + + query.message.reply_document( + reply_to_message_id=query.message.message_id, quote=True, document=open(log_name, 'rb')) + + else: + context.bot.edit_message_text(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果 ↓↓↓ \n\n%s ' % (cmd, out_text))), + chat_id=update.effective_chat.id, message_id=query.message.message_id, + parse_mode=ParseMode.MARKDOWN_V2) + except TimeoutExpired: + logger.error(' →→→ %s 执行超时 ←←← ' % ' '.join(cmd[6:])) + context.bot.edit_message_text(text='```{}```'.format(' →→→ %s 执行超时 ←←← ' % cmd), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + except: + context.bot.edit_message_text(text='```{}```'.format(' →→→ %s 执行出错 ←←← ' % cmd), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + raise + elif select_btn_type == 'cld': + query.answer(text='删除任务功能暂未实现', show_alert=True) + + elif select_btn_type == 'logs': + log_file = query.data.split()[-1] + if log_file == 'clear': + cmd = 'rm -rf %s*.log' % _logs_dir + try: + subprocess.check_output( + cmd, shell=True, timeout=600, stderr=subprocess.STDOUT) + + context.bot.edit_message_text(text='```{}```'.format(' →→→ 日志文件已清除 ←←← '), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + except: + context.bot.sendMessage(text='```{}```'.format(helpers.escape_markdown( + ' →→→ 清除日志执行出错 ←←← ')), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + raise + else: + context.bot.edit_message_text(text='```{}```'.format(' ↓↓↓ 下面为下载的%s文件 ↓↓↓ ' % select_btn_type), + chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + + query.message.reply_document(reply_to_message_id=query.message.message_id, quote=True, + document=open(log_file, 'rb')) + + else: + context.bot.edit_message_text(text='```{}```'.format(' →→→ 操作已取消 ←←← '), chat_id=query.message.chat_id, + message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2) + + +def get_reply_markup_btn(cmd_type): + """因为编辑后也可能会需要到重新读取按钮,所以抽出来作为一个方法返回按钮list + + Returns + ------- + 返回一个对应命令类型按钮列表,方便读取 + """ + if cmd_type == 'logs': + keyboard_line = [] + logs_list = get_dir_file_list(_logs_dir, 'log') + if (len(logs_list)) > 0: + file_list = list(set(get_dir_file_list(_logs_dir, 'log'))) + file_list.sort() + for i in range(math.ceil(len(file_list) / 2)): + ret = file_list[0:2] + keyboard_column = [] + for ii in ret: + keyboard_column.append(InlineKeyboardButton( + ii.split('/')[-1], callback_data='logs %s' % ii)) + file_list.remove(ii) + keyboard_line.append(keyboard_column) + + keyboard_line.append([InlineKeyboardButton('清除日志', callback_data='logs clear'), + InlineKeyboardButton('取消操作', callback_data='cancel')]) + else: + keyboard_line.append([InlineKeyboardButton( + '未找到相关日志文件', callback_data='cancel')]) + reply_markup = InlineKeyboardMarkup(keyboard_line) + elif cmd_type == 'crontab_l': + button_list = list(set(get_crontab_list(cmd_type))) + keyboard_line = [] + if len(button_list) > 0: + for i in button_list: + keyboard_column = [InlineKeyboardButton( + i, callback_data='cl %s' % i)] + + button_list.remove(i) + keyboard_line.append(keyboard_column) + keyboard_line.append( + [InlineKeyboardButton('取消操作', callback_data='cancel')]) + else: + keyboard_line.append([InlineKeyboardButton( + '未从%s获取到任务列表' % crontab_list_file, callback_data='cancel')]) + reply_markup = InlineKeyboardMarkup(keyboard_line) + elif cmd_type == 'node': + button_list = list(set(get_crontab_list(cmd_type))) + button_list.sort() + keyboard_line = [] + for i in range(math.ceil(len(button_list) / 2)): + ret = button_list[0:2] + keyboard_column = [] + for ii in ret: + keyboard_column.append(InlineKeyboardButton( + ii.split('/')[-1], callback_data=ii)) + button_list.remove(ii) + keyboard_line.append(keyboard_column) + + keyboard_line.append( + [InlineKeyboardButton('取消操作', callback_data='cancel')]) + + reply_markup = InlineKeyboardMarkup(keyboard_line) + elif cmd_type == 'git': + # button_list = list(set(get_crontab_list(cmd_type))) + # button_list.sort() + keyboard_line = [[InlineKeyboardButton('git pull', callback_data='git -C %s pull' % _base_dir), + InlineKeyboardButton('git reset --hard', callback_data='git -C %s reset --hard' % _base_dir)], + [InlineKeyboardButton('取消操作', callback_data='cancel')]] + # for i in range(math.ceil(len(button_list) / 2)): + # ret = button_list[0:2] + # keyboard_column = [] + # for ii in ret: + # keyboard_column.append(InlineKeyboardButton( + # ii.split('/')[-1], callback_data=ii)) + # button_list.remove(ii) + # keyboard_line.append(keyboard_column) + # if len(keyboard_line) < 1: + # keyboard_line.append( + # [InlineKeyboardButton('git pull', callback_data='git -C %s pull' % _base_dir), + # InlineKeyboardButton('git reset --hard', callback_data='git -C %s reset --hard' % _base_dir)]) + reply_markup = InlineKeyboardMarkup(keyboard_line) + else: + reply_markup = InlineKeyboardMarkup( + [[InlineKeyboardButton('没找到对的命令操作按钮', callback_data='cancel')]]) + + return reply_markup + + +def get_crontab_list(cmd_type): + """获取任务列表里面的node相关的定时任务 + + Parameters + ---------- + cmd_type: 是任务的命令类型 + # item_idx: 确定取的需要取之是定任务里面第几个空格后的值作为后面执行指令参数 + + Returns + ------- + 返回一个指定命令类型定时任务列表 + """ + button_list = [] + try: + with open(_docker_dir + crontab_list_file) as lines: + array = lines.readlines() + for i in array: + i = i.replace('|ts', '').strip('\n') + if i.startswith('#') or len(i) < 5: + pass + else: + items = i.split('>>') + item_sub = items[0].split()[5:] + if cmd_type == item_sub[0]: + button_list.append(' '.join(item_sub)) + elif cmd_type == 'crontab_l': + button_list.append(items[0]) + except: + logger.warning(f'读取定时任务配置文件 {crontab_list_file} 出错') + finally: + return button_list + + +def get_dir_file_list(dir_path, file_type): + """获取传入的路径下的文件列表 + + Parameters + ---------- + dir_path: 完整的目录路径,不需要最后一个 + file_type: 或者文件的后缀名字 + + Returns + ------- + 返回一个完整绝对路径的文件列表,方便读取 + """ + cmd = 'ls %s*.%s' % (dir_path, file_type) + file_list = [] + try: + out_bytes = subprocess.check_output( + cmd, shell=True, timeout=5, stderr=subprocess.STDOUT) + out_text = out_bytes.decode('utf-8') + file_list = out_text.split() + except: + logger.warning(f'{dir_path}目录下,不存在{file_type}对应的文件') + finally: + return file_list + + +def is_admin(from_user_id): + if str(admin_id) == str(from_user_id): + return True + else: + return False + + +class CodeConf(object): + def __init__(self, bot_id, submit_code, log_name, activity_code, find_split_char): + self.bot_id = bot_id + self.submit_code = submit_code + self.log_name = log_name + self.activity_code = activity_code + self.find_split_char = find_split_char + + def get_submit_msg(self): + code_list = [] + ac = self.activity_code if self.activity_code != "@N" else "" + try: + with open("%s%s" % (_logs_dir, self.log_name), 'r') as lines: + array = lines.readlines() + for i in array: + # print(self.find_split_char) + if i.find(self.find_split_char) > -1: + code_list.append(i.split(self.find_split_char)[ + 1].replace('\n', '')) + if self.activity_code == "@N": + return '%s %s' % (self.submit_code, + "&".join(list(set(code_list)))) + else: + return '%s %s %s' % (self.submit_code, ac, + "&".join(list(set(code_list)))) + except: + return "%s %s活动获取系统日志文件异常,请检查日志文件是否存在" % (self.submit_code, ac) + + +def gen_long_code(update, context): + """ + 长期活动互助码提交消息生成 + """ + long_code_conf = [] + bot_list = [] + try: + with open(_share_code_conf, 'r') as lines: + array = lines.readlines() + for i in array: + if i.startswith("long"): + bot_list.append(i.split('-')[1]) + code_conf = CodeConf( + i.split('-')[1], i.split('-')[2], i.split('-')[3], i.split('-')[4], + i.split('-')[5].replace('\n', '')) + long_code_conf.append(code_conf) + + for bot in list(set(bot_list)): + for cf in long_code_conf: + if cf.bot_id == bot: + print() + context.bot.send_message(chat_id=update.effective_chat.id, text=cf.get_submit_msg()) + context.bot.send_message(chat_id=update.effective_chat.id, text="以上为 %s 可以提交的活动互助码" % bot) + except: + context.bot.send_message(chat_id=update.effective_chat.id, + text="获取互助码消息生成配置文件失败,请检查%s文件是否存在" % _share_code_conf) + + +def gen_temp_code(update, context): + """ + 短期临时活动互助码提交消息生成 + """ + temp_code_conf = [] + bot_list = [] + try: + with open(_share_code_conf, 'r') as lines: + array = lines.readlines() + for i in array: + if i.startswith("temp"): + bot_list.append(i.split('-')[1]) + code_conf = CodeConf( + i.split('-')[1], i.split('-')[2], i.split('-')[3], i.split('-')[4], + i.split('-')[5].replace('\n', '')) + temp_code_conf.append(code_conf) + + for bot in list(set(bot_list)): + for cf in temp_code_conf: + if cf.bot_id == bot: + print() + context.bot.send_message(chat_id=update.effective_chat.id, text=cf.get_submit_msg()) + context.bot.send_message(chat_id=update.effective_chat.id, text="以上为 %s 可以提交的短期临时活动互助码" % bot) + except: + context.bot.send_message(chat_id=update.effective_chat.id, + text="获取互助码消息生成配置文件失败,请检查%s文件是否存在" % _share_code_conf) + + +def gen_daily_code(update, context): + """ + 每天变化互助码活动提交消息生成 + """ + daily_code_conf = [] + bot_list = [] + try: + with open(_share_code_conf, 'r') as lines: + array = lines.readlines() + for i in array: + if i.startswith("daily"): + bot_list.append(i.split('-')[1]) + code_conf = CodeConf( + i.split('-')[1], i.split('-')[2], i.split('-')[3], i.split('-')[4], + i.split('-')[5].replace('\n', '')) + daily_code_conf.append(code_conf) + + for bot in list(set(bot_list)): + for cf in daily_code_conf: + if cf.bot_id == bot: + print() + context.bot.send_message(chat_id=update.effective_chat.id, text=cf.get_submit_msg()) + context.bot.send_message(chat_id=update.effective_chat.id, text="以上为 %s 可以提交的每天变化活动互助码" % bot) + except: + context.bot.send_message(chat_id=update.effective_chat.id, + text="获取互助码消息生成配置文件失败,请检查%s文件是否存在" % _share_code_conf) + + +# getSToken请求获取,s_token用于发送post请求是的必须参数 +s_token = "" +# getSToken请求获取,guid,lsid,lstoken用于组装cookies +guid, lsid, lstoken = "", "", "" +# 由上面参数组装生成,getOKLToken函数发送请求需要使用 +cookies = "" +# getOKLToken请求获取,token用户生成二维码使用、okl_token用户检查扫码登录结果使用 +token, okl_token = "", "" +# 最终获取到的可用的cookie +jd_cookie = "" + + +def get_jd_cookie(update, context): + getSToken() + getOKLToken() + + qr_code_path = genQRCode() + photo_file = open(qr_code_path, 'rb') + photo_message = context.bot.send_photo(chat_id=update.effective_chat.id, photo=photo_file, + caption="请扫描二维码获取Cookie") + photo_file.close() + + return_msg = chekLogin() + if return_msg == 0: + context.bot.delete_message(chat_id=update.effective_chat.id, message_id=photo_message.message_id) + context.bot.send_message(chat_id=update.effective_chat.id, text="获取Cookie成功\n`%s`" % jd_cookie, + parse_mode=ParseMode.MARKDOWN_V2) + + elif return_msg == 21: + context.bot.delete_message(chat_id=update.effective_chat.id, message_id=photo_message.message_id) + context.bot.edit_message_text(chat_id=update.effective_chat.id, message_id=photo_message.message_id, + text="二维码已经失效,请重新获取") + else: + context.bot.delete_message(chat_id=update.effective_chat.id, message_id=photo_message.message_id) + context.bot.edit_message_text(chat_id=update.effective_chat.id, message_id=photo_message.message_id, + text=return_msg) + + +def getSToken(): + time_stamp = int(time.time() * 1000) + get_url = 'https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance?lang=chs&appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=%s&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport' % time_stamp + get_header = { + 'Connection': 'Keep-Alive', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-cn', + 'Referer': 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=%s&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport' % time_stamp, + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', + 'Host': 'plogin.m.jd.com' + } + try: + resp = requests.get(url=get_url, headers=get_header) + parseGetRespCookie(resp.headers, resp.json()) + logger.info(resp.headers) + logger.info(resp.json()) + except Exception as error: + logger.exception("Get网络请求异常", error) + + +def parseGetRespCookie(headers, get_resp): + global s_token + global cookies + s_token = get_resp.get('s_token') + set_cookies = headers.get('set-cookie') + logger.info(set_cookies) + + guid = re.findall(r"guid=(.+?);", set_cookies)[0] + lsid = re.findall(r"lsid=(.+?);", set_cookies)[0] + lstoken = re.findall(r"lstoken=(.+?);", set_cookies)[0] + + cookies = f"guid={guid}; lang=chs; lsid={lsid}; lstoken={lstoken}; " + logger.info(cookies) + + +def getOKLToken(): + post_time_stamp = int(time.time() * 1000) + post_url = 'https://plogin.m.jd.com/cgi-bin/m/tmauthreflogurl?s_token=%s&v=%s&remember=true' % ( + s_token, post_time_stamp) + post_data = { + 'lang': 'chs', + 'appid': 300, + 'returnurl': 'https://wqlogin2.jd.com/passport/LoginRedirect?state=%s&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action' % post_time_stamp, + 'source': 'wq_passport' + } + post_header = { + 'Connection': 'Keep-Alive', + 'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8', + 'Accept': 'application/json, text/plain, */*', + 'Cookie': cookies, + 'Referer': 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=%s&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport' % post_time_stamp, + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', + 'Host': 'plogin.m.jd.com', + } + try: + global okl_token + resp = requests.post(url=post_url, headers=post_header, data=post_data, timeout=20) + parsePostRespCookie(resp.headers, resp.json()) + logger.info(resp.headers) + except Exception as error: + logger.exception("Post网络请求错误", error) + + +def parsePostRespCookie(headers, data): + global token + global okl_token + + token = data.get('token') + print(headers.get('set-cookie')) + okl_token = re.findall(r"okl_token=(.+?);", headers.get('set-cookie'))[0] + + logger.info("token:" + token) + logger.info("okl_token:" + okl_token) + + +def genQRCode(): + global qr_code_path + cookie_url = f'https://plogin.m.jd.com/cgi-bin/m/tmauth?appid=300&client_type=m&token=%s' % token + version, level, qr_name = myqr.run( + words=cookie_url, + # 扫描二维码后,显示的内容,或是跳转的链接 + version=5, # 设置容错率 + level='H', # 控制纠错水平,范围是L、M、Q、H,从左到右依次升高 + picture='jd.png', # 图片所在目录,可以是动图 + colorized=True, # 黑白(False)还是彩色(True) + contrast=1.0, # 用以调节图片的对比度,1.0 表示原始图片。默认为1.0。 + brightness=1.0, # 用来调节图片的亮度,用法同上。 + save_name='getJDCookies.png', # 控制输出文件名,格式可以是 .jpg, .png ,.bmp ,.gif + ) + return qr_name + + +def chekLogin(): + expired_time = time.time() + 60 * 3 + while True: + check_time_stamp = int(time.time() * 1000) + check_url = 'https://plogin.m.jd.com/cgi-bin/m/tmauthchecktoken?&token=%s&ou_state=0&okl_token=%s' % ( + token, okl_token) + check_data = { + 'lang': 'chs', + 'appid': 300, + 'returnurl': 'https://wqlogin2.jd.com/passport/LoginRedirect?state=%s&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action' % check_time_stamp, + 'source': 'wq_passport' + + } + check_header = { + 'Referer': f'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=%s&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport' % check_time_stamp, + 'Cookie': cookies, + 'Connection': 'Keep-Alive', + 'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8', + 'Accept': 'application/json, text/plain, */*', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36', + + } + resp = requests.post(url=check_url, headers=check_header, data=check_data, timeout=30) + data = resp.json() + if data.get("errcode") == 0: + parseJDCookies(resp.headers) + return data.get("errcode") + if data.get("errcode") == 21: + return data.get("errcode") + if time.time() > expired_time: + return "超过3分钟未扫码,二维码已过期。" + + +def parseJDCookies(headers): + global jd_cookie + logger.info("扫码登录成功,下面为获取到的用户Cookie。") + set_cookie = headers.get('Set-Cookie') + pt_key = re.findall(r"pt_key=(.+?);", set_cookie)[0] + pt_pin = re.findall(r"pt_pin=(.+?);", set_cookie)[0] + logger.info(pt_key) + logger.info(pt_pin) + jd_cookie = f'pt_key={pt_key};pt_pin={pt_pin};' + + +def resp_text(update, context): + """ + 监听用户输入的文本消息 + """ + from_user_id = update.message.from_user.id + if admin_id == str(from_user_id): + msg_text = update.message.text + logger.info(msg_text) + if str(update.message.text).find("api.m.jd.com/client.action?functionId=liveDrawLotteryV842") > 0: + url = re.findall(r"已登陆京东):(.+?)?$", msg_text)[0] + body = re.findall(r"&body=(.+?)&", msg_text)[0] + url = url.replace(body, quote(body)) + logger.info(url) + try: + os.putenv('LIVE_LOTTTERY_URL', url) + cmd = f'node {_base_dir}jd_live_lottery.js' + out_bytes = subprocess.check_output( + cmd, shell=True, timeout=600, stderr=subprocess.STDOUT) + out_text = out_bytes.decode('utf-8') + context.bot.sendMessage(text='```{}```'.format( + helpers.escape_markdown(' ↓↓↓ %s 执行结果 ↓↓↓ \n\n%s ' % (cmd, out_text))), + chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + except TimeoutExpired: + context.bot.sendMessage(text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行超时 ←←← ' % (cmd))), + chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + except Exception as e: + context.bot.sendMessage( + text='```{}```'.format(helpers.escape_markdown(' →→→ %s 执行出错,请检查确认命令是否正确 ←←← ' % ( + cmd))), chat_id=update.effective_chat.id, parse_mode=ParseMode.MARKDOWN_V2) + raise + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def unknown(update, context): + """回复用户输入不存在的指令 + """ + from_user_id = update.message.from_user.id + if admin_id == str(from_user_id): + spnode_readme = "" + if "DISABLE_SPNODE" not in os.environ: + spnode_readme = "/spnode 获取可执行脚本的列表,选择对应的按钮执行。(拓展使用:运行指定路径脚本,例:/spnode /scripts/jd_818.js)\n" \ + "使用bot交互+spnode后 后续用户的cookie维护更新只需要更新logs/cookies.conf即可\n" \ + "使用bot交互+spnode后 后续执行脚本命令请使用spnode否者无法使用logs/cookies.conf的cookies执行脚本,定时任务也将自动替换为spnode命令执行\n" \ + "spnode功能概述示例\n" \ + "spnode conc /scripts/jd_bean_change.js 为每个cookie单独执行jd_bean_change脚本(伪并发\n" \ + "spnode 1 /scripts/jd_bean_change.js 为logs/cookies.conf文件里面第一行cookie账户单独执行jd_bean_change脚本\n" \ + "spnode jd_XXXX /scripts/jd_bean_change.js 为logs/cookies.conf文件里面pt_pin=jd_XXXX的cookie账户单独执行jd_bean_change脚本\n" \ + "spnode /scripts/jd_bean_change.js 为logs/cookies.conf所有cookies账户一起执行jd_bean_change脚本\n" \ + "请仔细阅读并理解上面的内容,使用bot交互默认开启spnode指令功能功能。\n" \ + "如需____停用___请配置环境变量 -DISABLE_SPNODE=True" + update.message.reply_text(text="⚠️ 您输入了一个错误的指令,请参考说明使用\n" \ + "\n" \ + "/start 开始并获取指令说明\n" \ + "/node 获取可执行脚本的列表,选择对应的按钮执行。(拓展使用:运行指定路径脚本,例:/node /scripts/jd_818.js) \n" \ + "/git 获取可执行git指令列表,选择对应的按钮执行。(拓展使用:运行指定路径脚本,例:/git -C /scripts/ pull)\n" \ + "/logs 获取logs下的日志文件列表,选择对应名字可以下载日志文件\n" \ + "/env 获取系统环境变量列表。(拓展使用:设置系统环境变量,例:/env export JD_DEBUG=true,环境变量只针对当前bot进程生效) \n" \ + "/bash 执行执行命令。参考:/bash ls -l 未完善不开放使用\n" \ + "/gen_long_code 长期活动互助码提交消息生成\n" \ + "/gen_temp_code 短期临时活动互助码提交消息生成\n" \ + "/gen_daily_code 每天变化互助码活动提交消息生成\n\n%s" % spnode_readme, + parse_mode=ParseMode.HTML) + else: + update.message.reply_text(text='此为私人使用bot,不能执行您的指令!') + + +def error(update, context): + """Log Errors caused by Updates.""" + logger.warning('Update "%s" caused error "%s"', update, context.error) + context.bot.send_message( + 'Update "%s" caused error "%s"', update, context.error) + + +def main(): + global admin_id, bot_token, crontab_list_file + + if 'TG_BOT_TOKEN' in os.environ: + bot_token = os.getenv('TG_BOT_TOKEN') + + if 'TG_USER_ID' in os.environ: + admin_id = os.getenv('TG_USER_ID') + + if 'CRONTAB_LIST_FILE' in os.environ: + crontab_list_file = os.getenv('CRONTAB_LIST_FILE') + else: + crontab_list_file = 'crontab_list.sh' + + logger.info('CRONTAB_LIST_FILE=' + crontab_list_file) + + # 创建更新程序并参数为你Bot的TOKEN。 + updater = Updater(bot_token, use_context=True) + + # 获取调度程序来注册处理程序 + dp = updater.dispatcher + + # 通过 start 函数 响应 '/start' 命令 + dp.add_handler(CommandHandler('start', start)) + + # 通过 start 函数 响应 '/help' 命令 + dp.add_handler(CommandHandler('help', start)) + + # 通过 node 函数 响应 '/node' 命令 + dp.add_handler(CommandHandler('node', node)) + + # 通过 node 函数 响应 '/spnode' 命令 + dp.add_handler(CommandHandler('spnode', spnode)) + + # 通过 git 函数 响应 '/git' 命令 + dp.add_handler(CommandHandler('git', git)) + + # 通过 logs 函数 响应 '/logs' 命令 + dp.add_handler(CommandHandler('crontab', crontab)) + + # 通过 logs 函数 响应 '/logs' 命令 + dp.add_handler(CommandHandler('logs', logs)) + + # 通过 callback_run 函数 响应相关按钮命令 + dp.add_handler(CallbackQueryHandler(callback_run)) + + # 通过 env 函数 响应 '/env' 命令 + dp.add_handler(CommandHandler('env', env)) + + # 通过 gen_long_code 函数 响应 '/gen_long_code' 命令 + dp.add_handler(CommandHandler('gen_long_code', gen_long_code)) + + # 通过 gen_temp_code 函数 响应 '/gen_temp_code' 命令 + dp.add_handler(CommandHandler('gen_temp_code', gen_temp_code)) + + # 通过 gen_daily_code 函数 响应 '/gen_daily_code' 命令 + dp.add_handler(CommandHandler('gen_daily_code', gen_daily_code)) + + # 通过 get_jd_cookie 函数 响应 '/eikooc_dj_teg' 命令 #别问为啥这么写,有意为之的 + dp.add_handler(CommandHandler('eikooc_dj_teg', get_jd_cookie)) + + # 没找到对应指令 + dp.add_handler(MessageHandler(Filters.command, unknown)) + + # 响应普通文本消息 + dp.add_handler(MessageHandler(Filters.text, resp_text)) + + dp.add_error_handler(error) + + updater.start_polling() + updater.idle() + + +# 生成依赖安装列表 +# pip3 freeze > requirements.txt +# 或者使用pipreqs +# pip3 install pipreqs +# 在当前目录生成 +# pipreqs . --encoding=utf8 --force +# 使用requirements.txt安装依赖 +# pip3 install -r requirements.txt +if __name__ == '__main__': + main() diff --git a/docker/bot/requirements.txt b/docker/bot/requirements.txt new file mode 100644 index 00000000..b1c0bea6 --- /dev/null +++ b/docker/bot/requirements.txt @@ -0,0 +1,4 @@ +python_telegram_bot==13.0 +requests==2.23.0 +MyQR==2.3.1 +telegram==0.0.1 diff --git a/docker/bot/setup.py b/docker/bot/setup.py new file mode 100644 index 00000000..a1be8e0d --- /dev/null +++ b/docker/bot/setup.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# @Author : iouAkira(lof) +# @mail : e.akimoto.akira@gmail.com +# @CreateTime: 2020-11-02 +# @UpdateTime: 2021-03-21 + +from setuptools import setup + +setup( + name='jd-scripts-bot', + version='0.2', + scripts=['jd_bot', ], +) diff --git a/docker/default_task.sh b/docker/default_task.sh index f4ddb2b3..ec7bf48e 100644 --- a/docker/default_task.sh +++ b/docker/default_task.sh @@ -1,6 +1,117 @@ #!/bin/sh set -e +# 放在这个初始化python3环境,目的减小镜像体积,一些不需要使用bot交互的用户可以不用拉体积比较大的镜像 +# 在这个任务里面还有初始化还有目的就是为了方便bot更新了新功能的话只需要重启容器就完成更新 +function initPythonEnv() { + echo "开始安装运行jd_bot需要的python环境及依赖..." + apk add --update python3-dev py3-pip py3-cryptography py3-numpy py-pillow + echo "开始安装jd_bot依赖..." + #测试 + #cd /jd_docker/docker/bot + #合并 + cd /scripts/docker/bot + pip3 install --upgrade pip + pip3 install -r requirements.txt + python3 setup.py install +} + +#启动tg bot交互前置条件成立,开始安装配置环境 +if [ "$1" == "True" ]; then + initPythonEnv + if [ -z "$DISABLE_SPNODE" ]; then + echo "增加命令组合spnode ,使用该命令spnode jd_xxxx.js 执行js脚本会读取cookies.conf里面的jd cokie账号来执行脚本" + ( + cat </usr/local/bin/spnode + chmod +x /usr/local/bin/spnode + fi + + echo "spnode需要使用的到,cookie写入文件,该文件同时也为jd_bot扫码获自动取cookies服务" + if [ -z "$JD_COOKIE" ]; then + if [ ! -f "$COOKIES_LIST" ]; then + echo "" >"$COOKIES_LIST" + echo "未配置JD_COOKIE环境变量,$COOKIES_LIST文件已生成,请将cookies写入$COOKIES_LIST文件,格式每个Cookie一行" + fi + else + if [ -f "$COOKIES_LIST" ]; then + echo "cookies.conf文件已经存在跳过,如果需要更新cookie请修改$COOKIES_LIST文件内容" + else + echo "环境变量 cookies写入$COOKIES_LIST文件,如果需要更新cookie请修改cookies.conf文件内容" + echo $JD_COOKIE | sed "s/\( &\|&\)/\\n/g" >$COOKIES_LIST + fi + fi + + CODE_GEN_CONF=/scripts/logs/code_gen_conf.list + echo "生成互助消息需要使用的到的 logs/code_gen_conf.list 文件,后续需要自己根据说明维护更新删除..." + if [ ! -f "$CODE_GEN_CONF" ]; then + ( + cat <$CODE_GEN_CONF + else + echo "logs/code_gen_conf.list 文件已经存在跳过初始化操作" + fi + + echo "容器jd_bot交互所需环境已配置安装已完成..." + curl -sX POST "https://api.telegram.org/bot$TG_BOT_TOKEN/sendMessage" -d "chat_id=$TG_USER_ID&text=恭喜🎉你获得feature\n容器jd_bot交互所需环境已配置安装已完成,并启用。请发送 /help 查看使用帮助。如需禁用请在docker-compose.yml配置 DISABLE_BOT_COMMAND=True" >>/dev/null + +fi + echo "定义定时任务合并处理用到的文件路径..." defaultListFile="/scripts/docker/$DEFAULT_LIST_FILE" echo "默认文件定时任务文件路径为 ${defaultListFile}" @@ -12,128 +123,127 @@ cat $defaultListFile >$mergedListFile echo "第2步判断是否存在自定义任务任务列表并追加..." if [ $CUSTOM_LIST_FILE ]; then - echo "您配置了自定义任务文件:$CUSTOM_LIST_FILE,自定义任务类型为:$CUSTOM_LIST_MERGE_TYPE..." - # 无论远程还是本地挂载, 均复制到 $customListFile - customListFile="/scripts/docker/custom_list_file.sh" - echo "自定义定时任务文件临时工作路径为 ${customListFile}" - if expr "$CUSTOM_LIST_FILE" : 'http.*' &>/dev/null; then - echo "自定义任务文件为远程脚本,开始下载自定义远程任务。" - wget -O $customListFile $CUSTOM_LIST_FILE - echo "下载完成..." - elif [ -f /scripts/docker/$CUSTOM_LIST_FILE ]; then - echo "自定义任务文件为本地挂载。" - cp /scripts/docker/$CUSTOM_LIST_FILE $customListFile - fi + echo "您配置了自定义任务文件:$CUSTOM_LIST_FILE,自定义任务类型为:$CUSTOM_LIST_MERGE_TYPE..." + # 无论远程还是本地挂载, 均复制到 $customListFile + customListFile="/scripts/docker/custom_list_file.sh" + echo "自定义定时任务文件临时工作路径为 ${customListFile}" + if expr "$CUSTOM_LIST_FILE" : 'http.*' &>/dev/null; then + echo "自定义任务文件为远程脚本,开始下载自定义远程任务。" + wget -O $customListFile $CUSTOM_LIST_FILE + echo "下载完成..." + elif [ -f /scripts/docker/$CUSTOM_LIST_FILE ]; then + echo "自定义任务文件为本地挂载。" + cp /scripts/docker/$CUSTOM_LIST_FILE $customListFile + fi - if [ -f "$customListFile" ]; then - if [ $CUSTOM_LIST_MERGE_TYPE == "append" ]; then - echo "合并默认定时任务文件:$DEFAULT_LIST_FILE 和 自定义定时任务文件:$CUSTOM_LIST_FILE" - echo -e "" >>$mergedListFile - cat $customListFile >>$mergedListFile - elif [ $CUSTOM_LIST_MERGE_TYPE == "overwrite" ]; then - echo "配置了自定义任务文件:$CUSTOM_LIST_FILE,自定义任务类型为:$CUSTOM_LIST_MERGE_TYPE..." - cat $customListFile >$mergedListFile - else - echo "配置配置了错误的自定义定时任务类型:$CUSTOM_LIST_MERGE_TYPE,自定义任务类型为只能为append或者overwrite..." - fi + if [ -f "$customListFile" ]; then + if [ $CUSTOM_LIST_MERGE_TYPE == "append" ]; then + echo "合并默认定时任务文件:$DEFAULT_LIST_FILE 和 自定义定时任务文件:$CUSTOM_LIST_FILE" + echo -e "" >>$mergedListFile + cat $customListFile >>$mergedListFile + elif [ $CUSTOM_LIST_MERGE_TYPE == "overwrite" ]; then + echo "配置了自定义任务文件:$CUSTOM_LIST_FILE,自定义任务类型为:$CUSTOM_LIST_MERGE_TYPE..." + cat $customListFile >$mergedListFile else - echo "配置的自定义任务文件:$CUSTOM_LIST_FILE未找到,使用默认配置$DEFAULT_LIST_FILE..." + echo "配置配置了错误的自定义定时任务类型:$CUSTOM_LIST_MERGE_TYPE,自定义任务类型为只能为append或者overwrite..." fi + else + echo "配置的自定义任务文件:$CUSTOM_LIST_FILE未找到,使用默认配置$DEFAULT_LIST_FILE..." + fi else - echo "当前只使用了默认定时任务文件 $DEFAULT_LIST_FILE ..." + echo "当前只使用了默认定时任务文件 $DEFAULT_LIST_FILE ..." fi - - - - echo "第3步判断是否配置了随机延迟参数..." if [ $RANDOM_DELAY_MAX ]; then - if [ $RANDOM_DELAY_MAX -ge 1 ]; then - echo "已设置随机延迟为 $RANDOM_DELAY_MAX , 设置延迟任务中..." - sed -i "/\(jd_bean_sign.js\|jd_blueCoin.js\|jd_joy_reward.js\|jd_joy_steal.js\|jd_joy_feedPets.js\|jd_car_exchange.js\)/!s/node/sleep \$((RANDOM % \$RANDOM_DELAY_MAX)); node/g" $mergedListFile - fi + if [ $RANDOM_DELAY_MAX -ge 1 ]; then + echo "已设置随机延迟为 $RANDOM_DELAY_MAX , 设置延迟任务中..." + sed -i "/\(jd_bean_sign.js\|jd_blueCoin.js\|jd_joy_reward.js\|jd_joy_steal.js\|jd_joy_feedPets.js\|jd_car_exchange.js\)/!s/node/sleep \$((RANDOM % \$RANDOM_DELAY_MAX)); node/g" $mergedListFile + fi else - echo "未配置随机延迟对应的环境变量,故不设置延迟任务..." + echo "未配置随机延迟对应的环境变量,故不设置延迟任务..." fi echo "第4步判断是否配置自定义shell执行脚本..." if [ 0"$CUSTOM_SHELL_FILE" = "0" ]; then - echo "未配置自定shell脚本文件,跳过执行。" + echo "未配置自定shell脚本文件,跳过执行。" else - if expr "$CUSTOM_SHELL_FILE" : 'http.*' &>/dev/null; then - echo "自定义shell脚本为远程脚本,开始下载自定义远程脚本。" - wget -O /scripts/docker/shell_script_mod.sh $CUSTOM_SHELL_FILE - echo "下载完成,开始执行..." - echo "#远程自定义shell脚本追加定时任务" >>$mergedListFile - sh -x /scripts/docker/shell_script_mod.sh - echo "自定义远程shell脚本下载并执行结束。" + if expr "$CUSTOM_SHELL_FILE" : 'http.*' &>/dev/null; then + echo "自定义shell脚本为远程脚本,开始下载自定义远程脚本。" + wget -O /scripts/docker/shell_script_mod.sh $CUSTOM_SHELL_FILE + echo "下载完成,开始执行..." + echo "#远程自定义shell脚本追加定时任务" >>$mergedListFile + sh -x /scripts/docker/shell_script_mod.sh + echo "自定义远程shell脚本下载并执行结束。" + else + if [ ! -f $CUSTOM_SHELL_FILE ]; then + echo "自定义shell脚本为docker挂载脚本文件,但是指定挂载文件不存在,跳过执行。" else - if [ ! -f $CUSTOM_SHELL_FILE ]; then - echo "自定义shell脚本为docker挂载脚本文件,但是指定挂载文件不存在,跳过执行。" - else - echo "docker挂载的自定shell脚本,开始执行..." - echo "#docker挂载自定义shell脚本追加定时任务" >>$mergedListFile - sh -x $CUSTOM_SHELL_FILE - echo "docker挂载的自定shell脚本,执行结束。" - fi + echo "docker挂载的自定shell脚本,开始执行..." + echo "#docker挂载自定义shell脚本追加定时任务" >>$mergedListFile + sh -x $CUSTOM_SHELL_FILE + echo "docker挂载的自定shell脚本,执行结束。" fi + fi fi - - echo "第5步删除不运行的脚本任务..." if [ $DO_NOT_RUN_SCRIPTS ]; then - echo "您配置了不运行的脚本:$DO_NOT_RUN_SCRIPTS" - arr=${DO_NOT_RUN_SCRIPTS//&/ } - for item in $arr; do - sed -ie '/'"${item}"'/d' ${mergedListFile} - done + echo "您配置了不运行的脚本:$DO_NOT_RUN_SCRIPTS" + arr=${DO_NOT_RUN_SCRIPTS//&/ } + for item in $arr; do + sed -ie '/'"${item}"'/d' ${mergedListFile} + done fi - echo "第6步设定下次运行docker_entrypoint.sh时间..." echo "删除原有docker_entrypoint.sh任务" sed -ie '/'docker_entrypoint.sh'/d' ${mergedListFile} # 12:00前生成12:00后的cron,12:00后生成第二天12:00前的cron,一天只更新两次代码 if [ $(date +%-H) -lt 12 ]; then - random_h=$(($RANDOM % 12 + 12)) + random_h=$(($RANDOM % 12 + 12)) else - random_h=$(($RANDOM % 12)) + random_h=$(($RANDOM % 12)) fi random_m=$(($RANDOM % 60)) echo "设定 docker_entrypoint.sh cron为:" -echo -e "\n# 必须要的默认定时任务请勿删除" >> $mergedListFile +echo -e "\n# 必须要的默认定时任务请勿删除" >>$mergedListFile echo -e "${random_m} ${random_h} * * * docker_entrypoint.sh >> /scripts/logs/default_task.log 2>&1" | tee -a $mergedListFile - echo "第7步 自动助力" -if [ $ENABLE_AUTO_HELP = "true" ]; then +if [ -n "$ENABLE_AUTO_HELP" ]; then + #直接判断变量,如果未配置,会导致sh抛出一个错误,所以加了上面一层 + if [ "$ENABLE_AUTO_HELP" = "true" ]; then echo "开启自动助力" - #在所有脚本执行前,先执行助力码导出 sed -i 's/node/ . \/scripts\/docker\/auto_help.sh export > \/scripts\/logs\/auto_help_export.log \&\& node /g' ${mergedListFile} -else + else echo "未开启自动助力" + fi fi - echo "第8步增加 |ts 任务日志输出时间戳..." sed -i "/\( ts\| |ts\|| ts\)/!s/>>/\|ts >>/g" $mergedListFile echo "第9步执行proc_file.sh脚本任务..." -sh -x /scripts/docker/proc_file.sh +sh /scripts/docker/proc_file.sh echo "第10步加载最新的定时任务文件..." +if [[ "$1" == "True" && -z "$DISABLE_SPNODE" ]]; then + echo "bot交互与spnode 前置条件成立,替换任务列表的node指令为spnode" + sed -i "s/ node / spnode /g" $mergedListFile + #conc每个cookies独立并行执行脚本示例,cookies数量多使用该功能可能导致内存爆掉,默认不开启 有需求,请在自定义shell里面实现 + #sed -i "/\(jd_xtg.js\|jd_car_exchange.js\)/s/spnode/spnode conc/g" $mergedListFile +fi crontab $mergedListFile echo "第11步将仓库的docker_entrypoint.sh脚本更新至系统/usr/local/bin/docker_entrypoint.sh内..." cat /scripts/docker/docker_entrypoint.sh >/usr/local/bin/docker_entrypoint.sh echo "发送通知" -export NOTIFY_CONTENT="2021-03-10更新 新版docker单容器多账号自动互助.开启方式:docker-compose.yml 中添加环境变量 - ENABLE_AUTO_HELP=true,详见Readme.md" +export NOTIFY_CONTENT="2021-03-21更新 增加bot交互,spnode指令,功能是否开启自动根据你的配置判断,详见 https://gitee.com/lxk0301/jd_docker/pulls/18" cd /scripts/docker node notify_docker_user.js \ No newline at end of file diff --git a/docker/docker_entrypoint.sh b/docker/docker_entrypoint.sh index 811b8556..ae5340aa 100644 --- a/docker/docker_entrypoint.sh +++ b/docker/docker_entrypoint.sh @@ -2,27 +2,53 @@ set -e #获取配置的自定义参数 -if [ $1 ]; then - run_cmd=$1 +if [ -n "$1" ]; then + run_cmd=$1 fi echo "设定远程仓库地址..." cd /scripts -git remote set-url origin $REPO_URL +git remote set-url origin "$REPO_URL" git reset --hard echo "git pull拉取最新代码..." git -C /scripts pull --rebase echo "npm install 安装最新依赖" npm install --prefix /scripts +echo "设定远程仓库地址..." +git -C /jd_docker pull --rebase + +# 默认启动telegram交互机器人的条件 +# 确认容器启动时调用的docker_entrypoint.sh +# 必须配置TG_BOT_TOKEN、TG_USER_ID, +# 且未配置DISABLE_BOT_COMMAND禁用交互, +# 且未配置自定义TG_API_HOST,因为配置了该变量,说明该容器环境可能并能科学的连到telegram服务器 +if [[ -n "$run_cmd" && -n "$TG_BOT_TOKEN" && -n "$TG_USER_ID" && -z "$DISABLE_BOT_COMMAND" && -z "$TG_API_HOST" ]]; then + ENABLE_BOT_COMMAND=True +else + ENABLE_BOT_COMMAND=False +fi + echo "------------------------------------------------执行定时任务任务shell脚本------------------------------------------------" -sh -x /scripts/docker/default_task.sh +#测试 +# sh /jd_docker/docker/default_task.sh "$ENABLE_BOT_COMMAND" "$run_cmd" +#合并 +sh /scripts/docker/default_task.sh "$ENABLE_BOT_COMMAND" "$run_cmd" echo "--------------------------------------------------默认定时任务执行完成---------------------------------------------------" -if [ $run_cmd ]; then - echo "Start crontab task main process..." - echo "启动crondtab定时任务主进程..." +if [ -n "$run_cmd" ]; then + # 增加一层jd_bot指令已经正确安装成功校验 + # 以上条件都满足后会启动jd_bot交互,否还是按照以前的模式启动,最大程度避免现有用户改动调整 + if [[ "$ENABLE_BOT_COMMAND" == "True" && -f /usr/bin/jd_bot ]]; then + echo "启动crontab定时任务主进程..." + crond + echo "启动telegram bot指令交主进程..." + jd_bot + else + echo "启动crontab定时任务主进程..." crond -f + fi + else - echo "默认定时任务执行结束。" + echo "默认定时任务执行结束。" fi