├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── __init__.py ├── docs ├── README.txt └── v3 │ └── ver.json ├── scripts ├── install-docker.sh ├── yobot-gocqhttp-auto.ps1 └── yobot-gocqhttp-auto.sh └── src └── client ├── README.md ├── logo.ico ├── main.py ├── main.spec ├── nonebot_plugin.py ├── packedfiles ├── default_BossIdAndName.json ├── default_boss.json ├── default_config.json └── default_pool.json ├── public ├── libs │ ├── axios@0.19.2 │ │ └── dist │ │ │ └── axios.min.js │ ├── canvas-nest.js@1.0.1 │ │ └── dist │ │ │ └── canvas-nest.min.js │ ├── echarts@4.7.0 │ │ └── dist │ │ │ └── echarts.min.js │ ├── element-ui@2.13.0 │ │ └── lib │ │ │ ├── index.js │ │ │ └── theme-chalk │ │ │ ├── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ │ └── index.css │ ├── github-buttons@2.7.0 │ │ └── dist │ │ │ └── buttons.min.js │ ├── github-markdown-css@3.0.1 │ │ └── github-markdown.css │ ├── jquery@2.1.1 │ │ ├── jquery.min.js │ │ └── jquery.min.map │ ├── vue@2.6.11 │ │ └── dist │ │ │ └── vue.min.js │ └── yocool@final │ │ └── princessadventure │ │ ├── banner.png │ │ ├── boss_icon │ │ ├── 0.webp │ │ ├── 300100.webp │ │ ├── 300200.webp │ │ ├── 300300.webp │ │ ├── 300400.webp │ │ ├── 300600.webp │ │ ├── 300700.webp │ │ ├── 300800.webp │ │ ├── 301000.webp │ │ ├── 301100.webp │ │ ├── 301300.webp │ │ ├── 301400.webp │ │ ├── 301500.webp │ │ ├── 301800.webp │ │ ├── 302000.webp │ │ ├── 302100.webp │ │ ├── 302600.webp │ │ ├── 302700.webp │ │ ├── 302800.webp │ │ ├── 302900.webp │ │ ├── 303000.webp │ │ ├── 303300.webp │ │ ├── 303500.webp │ │ ├── 303606.webp │ │ ├── 303900.webp │ │ ├── 304000.webp │ │ ├── 304100.webp │ │ ├── 304500.webp │ │ ├── 304600.webp │ │ ├── 304800.webp │ │ ├── 305100.webp │ │ ├── 305700.webp │ │ ├── 305800.webp │ │ ├── 305900.webp │ │ ├── 306100.webp │ │ ├── 306200.webp │ │ ├── 306900.webp │ │ ├── 307202.webp │ │ ├── 309000.webp │ │ ├── 309200.webp │ │ ├── 312000.webp │ │ ├── 312501.webp │ │ ├── 313400.webp │ │ └── 316600.webp │ │ ├── box-gift.png │ │ ├── clanbg.png │ │ ├── error.png │ │ ├── flag.png │ │ ├── icon-loading.png │ │ ├── jquery.sliderBar.js │ │ ├── karin_q.png │ │ ├── karyl_q.png │ │ ├── kokkoro_q.png │ │ ├── logo.png │ │ ├── pecorine_q.png │ │ ├── scheme-01.css │ │ ├── scheme-02.css │ │ ├── scheme-03.css │ │ ├── sprite01.png │ │ ├── sprite02.png │ │ ├── sprite03.png │ │ ├── style.css │ │ ├── text-loading.png │ │ ├── yocool.js │ │ └── yui_q.png ├── static │ ├── admin │ │ ├── groups.js │ │ ├── pool-setting.js │ │ ├── setting.js │ │ └── users.js │ ├── chara_marks.png │ ├── clan │ │ ├── clan-rank.js │ │ ├── panel.js │ │ ├── progress.js │ │ ├── setting.js │ │ ├── statistics.js │ │ ├── statistics │ │ │ ├── deviation.png │ │ │ ├── many.png │ │ │ ├── order.png │ │ │ ├── pie.png │ │ │ └── statistics2.js │ │ ├── subscribers.js │ │ └── user.js │ ├── gacha.js │ ├── gongan.png │ ├── marionette.js │ ├── password.js │ └── small.ico └── template │ ├── 404.html │ ├── about.html │ ├── admin │ ├── groups.html │ ├── setting.html │ └── users.html │ ├── clan │ ├── clan-rank.html │ ├── panel.html │ ├── progress.html │ ├── setting.html │ ├── statistics.html │ ├── statistics │ │ ├── statistics1.html │ │ └── statistics2.html │ ├── subscribers.html │ ├── unauthorized.html │ └── user.html │ ├── help.html │ ├── homepage.html │ ├── login-code.html │ ├── login.html │ ├── manual.html │ ├── marionette.html │ ├── password.html │ ├── unauthorized.html │ ├── user-info.html │ └── user.html ├── requirements.txt ├── ybplugins ├── __init__.py ├── clan_battle │ ├── __init__.py │ ├── battle.py │ ├── components │ │ ├── define.py │ │ ├── fonts │ │ │ └── msyh.ttf │ │ ├── handler.py │ │ ├── image_engine.py │ │ ├── kernel.py │ │ ├── multi_cq_utils.py │ │ ├── realize.py │ │ ├── score.py │ │ └── web_operation.py │ ├── exception.py │ ├── typing.py │ └── util.py ├── custom.py ├── group_leave.py ├── homepage.py ├── login.py ├── marionette.py ├── settings.py ├── shorten_url.py ├── switcher.py ├── templating.py ├── web_util.py ├── ybdata.py ├── yobot_exceptions.py └── yobot_msg.py └── yobot.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | .vscode/ 3 | src/client/yobot_data/* 4 | src/client/public/template/help/ 5 | src/client/public/static/aya.ico 6 | src/client/venv 7 | src/client/dist 8 | src/client/build -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim-buster 2 | LABEL maintainer="yobot" 3 | 4 | ENV PYTHONIOENCODING=utf-8 5 | 6 | ADD src/client/ /yobot 7 | 8 | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ 9 | && echo 'Asia/Shanghai' >/etc/timezone \ 10 | && apt update \ 11 | && apt upgrade -y \ 12 | && apt install build-essential -y \ 13 | && cd /yobot \ 14 | && pip3 install aiocqhttp==1.4.3 Quart==0.18.3 --no-cache-dir \ 15 | && pip3 install -r requirements.txt --no-cache-dir \ 16 | && python3 main.py \ 17 | && chmod +x yobotg.sh 18 | 19 | WORKDIR /yobot 20 | 21 | EXPOSE 9222 22 | 23 | VOLUME /yobot/yobot_data 24 | 25 | ENTRYPOINT /yobot/yobotg.sh 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yobot_remix 2 | 3 | yobot魔改版,支持新版公会战。
4 | 删除了除会战外的功能(肯定有没删干净的地方)
5 | 没实战测试过,可能存在未知bug
6 | 多来点测试工程师\_(:з)∠)_
7 | 8 | 现已将全部在线CDN以及资源改为本地静态资源。注意不要在设置中开启 web_gzip 压缩,保持为0即为关闭状态,否则开启后网页资源会加载不全。 9 | 10 | 现在已支持多CQ适配(部分),网页端主动消息已经可以正常发送。 11 | 新创建的公会自动添加多CQ记录,以前创建的公会请发送“手动添加群记录”来修复多CQ适配。 12 | 13 | ## 指令表 14 | > 大部分和原版yobot相同,只标出和原版yobot不同的命令 15 | 16 | | 指令 | 说明 | 17 | | :---------------: | :----------------------------------------------------------: | 18 | |申请出刀 [指定boss] [是否为补偿] [@某人]|如:**申请出刀 1** 或 **申请出刀 2b**(b为指定补偿)
开始挑战boss,进入**出刀状态**
必须指定boss,可以不指定补偿,当完整刀使用完后自动使用补偿
(新增申请出刀方式,发送 **进x** 即可,x为指定boss)| 19 | |取消(出刀/申请) [@某人]/[all]|**出刀状态**下可用
退出出刀状态。@某人为取消指定成员的出刀申请,加all则取消所有人的出刀申请| 20 | |报伤害 ?s?w [@某人]|**出刀状态**下可用
如:**报伤害 2s200w**
申请出刀后暂停报伤害,例子意为剩2秒打了200w伤害。可@某人为其报伤害| 21 | |报刀 [-指定boss] [具体伤害] [是否是补偿] [@某人] [昨日] [:留言]|例1:**报刀 -1 100w**
例2:**报刀 100w@xxx**
例3:**报刀 -1 100w b** (b代表指定补偿)
对boss造成伤害但未击败时用,记录伤害,并退出**出刀状态**。
**指定boss需要在前面加个-(横杠)**,若在**出刀状态**,可以不指定boss
一般情况下不需要指定为补偿,会自动选择
如果有at则为代报,有冒号则为留言
如果有“昨日”则将记录添加到前一天| 22 | |尾刀 [指定boss] [是否是补偿] [@某人] [昨日] [:留言]|同上,不需要具体伤害,指定boss时前面不需要加-(横杠)| 23 | |状态|显示所有boss的当前状态,且可看到当前正在挑战的成员、报伤害、挂树情况| 24 | |挂树|**出刀状态**下可用| 25 | |手动添加群记录|修复多CQ适配(修复网页催刀报刀不在群里通知)| 26 | > 关于指定补偿问题,建议普通公会直接无视指定补偿这个功能,可出刀总数是固定的,不指定也不会有任何影响。
27 | > 有指挥的公会才需要严格遵守。
28 | > 指定补偿时不止可以用b,还可以用 (补偿/补/b/bc)
29 | > ps:用补偿刀收尾会影响补偿判断,如果用补偿刀收尾需要报补偿 30 | 31 | [源码](./src/client) 32 | 33 | [介绍](https://yobot.win) 34 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .src.client import nonebot_plugin 2 | -------------------------------------------------------------------------------- /docs/README.txt: -------------------------------------------------------------------------------- 1 | 文档移动至 https://github.com/pcrbot/yobot-docs -------------------------------------------------------------------------------- /docs/v3/ver.json: -------------------------------------------------------------------------------- 1 | { 2 | "stable": { 3 | "version": 99999, 4 | "url": "http://download.yobot.win/scyb/yobot210.zip" 5 | } 6 | } -------------------------------------------------------------------------------- /scripts/install-docker.sh: -------------------------------------------------------------------------------- 1 | installDocker() { 2 | curl -fsSL "https://get.docker.com" | /bin/bash 3 | sudo usermod -aG docker $(whoami) 4 | } 5 | 6 | changeDockerSource() { 7 | sudo cat>/etc/docker/daemon.json<Dockerfile 74 | docker build . -t gocqhttp 75 | rm Dockerfile -f 76 | 77 | echo "initializing gocqhttp configure file" 78 | docker run --rm \ 79 | -v ${PWD}/gocqhttp_data:/bot \ 80 | gocqhttp >/dev/null 2>&1 81 | 82 | echo "writing configure files" 83 | docker run --rm -v ${PWD}:/work -w /work -e qqid -e qqpassword python:3.7-slim-buster python3 -c " 84 | import json, os, random, string 85 | access_token = ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + string.digits, k=16)) 86 | with open('yobot_data/yobot_config.json', 'w') as f: 87 | json.dump({'access_token': access_token}, f, indent=4) 88 | with open('gocqhttp_data/config.json', 'r+') as f: 89 | config = json.load(f) 90 | config['uin'] = int(os.environ['qqid']) 91 | config['password'] = os.environ['qqpassword'] 92 | config['access_token'] = access_token 93 | config['enable_db'] = False 94 | config['web_ui']['enabled'] = False 95 | config['http_config']['enabled'] = False 96 | config['ws_config']['enabled'] = False 97 | config['ws_reverse_servers'] = [{ 98 | 'enabled': True, 99 | 'reverse_url': 'ws://yobot:9222/ws/', 100 | 'reverse_api_url': '', 101 | 'reverse_event_url': '', 102 | 'reverse_reconnect_interval': 3000 103 | }] 104 | f.seek(0) 105 | f.truncate() 106 | json.dump(config, f, indent=4) 107 | " 108 | 109 | echo "starting yobot" 110 | docker run -d \ 111 | --name yobot \ 112 | -p 9222:9222 \ 113 | --network qqbot \ 114 | -v ${PWD}/yobot_data:/yobot/yobot_data \ 115 | yobot/yobot 116 | 117 | echo "starting gocqhttp" 118 | docker run -it \ 119 | --name gocqhttp \ 120 | --network qqbot \ 121 | -v ${PWD}/gocqhttp_data:/bot \ 122 | gocqhttp 123 | -------------------------------------------------------------------------------- /src/client/README.md: -------------------------------------------------------------------------------- 1 | # 使用方法 2 | 3 | ## 运行环境 4 | 5 | python最低要求为 `python3.6` 6 | 7 | ## 打包 8 | 9 | (一般不建议对 python 项目打包) 10 | 11 | 安装 `pyinstaller` 12 | 13 | ```sh 14 | pip install pyinstaller 15 | ``` 16 | 17 | 打包程序 18 | 19 | ```sh 20 | pyinstaller main.spec 21 | ``` 22 | 23 | 在 `dist` 中找到目标文件 24 | 25 | ## 扩展 26 | 27 | 见[custom.py](./ybplugins/custom.py)文件 28 | 29 | ## 移植 30 | 31 | 经过多次迭代,yobot与[cq-http-api](https://github.com/richardchien/coolq-http-api/)的耦合越来越深,不再适合移植了 32 | -------------------------------------------------------------------------------- /src/client/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/logo.ico -------------------------------------------------------------------------------- /src/client/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | 实例1:利用aiocqhttp作为httpapi的服务端 3 | """ 4 | 5 | import platform 6 | import os 7 | import sys 8 | import random 9 | 10 | if platform.system() == "Linux": 11 | if "-g" not in sys.argv[1:]: 12 | with open("yobotg.sh", "w") as g: 13 | g.write(""" 14 | echo $$ > yobotg.pid 15 | loop=true 16 | while $loop 17 | do 18 | loop=false 19 | {} -g 20 | if [ -f .YOBOT_RESTART ] 21 | then 22 | loop=true 23 | rm .YOBOT_RESTART 24 | fi 25 | done 26 | """.format('./yobot' if '_MEIPASS' in dir(sys) else 'python3 main.py')) 27 | print('请通过"sh yobotg.sh"启动') 28 | sys.exit() 29 | if os.path.exists('.YOBOT_RESTART'): 30 | os.remove('.YOBOT_RESTART') 31 | 32 | import asyncio 33 | import json 34 | import time 35 | 36 | import tzlocal 37 | from aiocqhttp import CQHttp 38 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 39 | 40 | import yobot 41 | 42 | 43 | def insert_seq(seq, x): 44 | for i in seq: 45 | if '\u4e00' <= i <= '\u9fa5': 46 | if random.random() < 0.2: 47 | yield x 48 | yield i 49 | 50 | 51 | def insert_zwsp(x: str) -> str: 52 | zwsp = '\ufeff' 53 | m = insert_seq(x, zwsp) 54 | return ''.join(m) 55 | 56 | 57 | def main(): 58 | print("""============================== 59 | _ _ 60 | | | | | 61 | _ _ ___ | |__ ___ | |_ 62 | | | | |/ _ \| '_ \ / _ \| __| 63 | | |_| | (_) | |_) | (_) | |_ 64 | \__, |\___/|_.__/ \___/ \__| 65 | __/ | 66 | |___/ --Remix 67 | ==============================""") 68 | print("正在初始化...") 69 | 70 | if os.path.exists('yobot_config.json'): 71 | basedir = "." 72 | else: 73 | basedir = "./yobot_data" 74 | if os.path.exists(os.path.join(basedir, "yobot_config.json")): 75 | try: 76 | with open(os.path.join(basedir, "yobot_config.json"), "r", encoding="utf-8-sig") as f: 77 | config = json.load(f) 78 | except json.JSONDecodeError as e: 79 | print('配置文件格式错误,请检查配置文件。三秒后关闭') 80 | time.sleep(3) 81 | raise e from e 82 | token = config.get("access_token", None) 83 | if token is None: 84 | print("警告:没有设置access_token,这会直接暴露机器人接口") 85 | print("详见https://yobot.win/usage/access-token/") 86 | else: 87 | token = None 88 | 89 | try: 90 | tzlocal.get_localzone() 91 | except: 92 | print("无法获取系统时区,请将系统时区设置为北京/上海时区") 93 | sys.exit() 94 | 95 | cqbot = CQHttp(access_token=token, 96 | enable_http_post=False) 97 | sche = AsyncIOScheduler() 98 | bot = yobot.Yobot(data_path=basedir, 99 | scheduler=sche, 100 | quart_app=cqbot.server_app, 101 | bot_api=cqbot._api, 102 | ) 103 | host = bot.glo_setting.get("host", "0.0.0.0") 104 | port = bot.glo_setting.get("port", 9222) 105 | 106 | @cqbot.on_message 107 | async def handle_msg(context): 108 | if context["message_type"] == "group" or context["message_type"] == "private": 109 | reply = await bot.proc_async(context) 110 | else: 111 | reply = None 112 | if isinstance(reply, str) and reply != "": 113 | return {'reply': insert_zwsp(reply), 114 | 'at_sender': False} 115 | else: 116 | return None 117 | 118 | async def send_it(func): 119 | if asyncio.iscoroutinefunction(func): 120 | to_sends = await func() 121 | else: 122 | to_sends = func() 123 | if to_sends is None: 124 | return 125 | for kwargs in to_sends: 126 | await asyncio.sleep(5) 127 | await cqbot.send_msg(**kwargs) 128 | 129 | jobs = bot.active_jobs() 130 | if jobs: 131 | for trigger, job in jobs: 132 | sche.add_job(func=send_it, 133 | args=(job,), 134 | trigger=trigger, 135 | coalesce=True, 136 | max_instances=1, 137 | misfire_grace_time=60) 138 | sche.start() 139 | 140 | print("初始化完成,启动服务...") 141 | 142 | cqbot.run( 143 | host=host, 144 | port=port, 145 | debug=False, 146 | use_reloader=False, 147 | loop=asyncio.get_event_loop(), 148 | ) 149 | 150 | 151 | if __name__ == "__main__": 152 | try: 153 | main() 154 | except KeyboardInterrupt: 155 | print("\nCtrl-C") 156 | sys.exit(0) 157 | -------------------------------------------------------------------------------- /src/client/main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | """ 3 | 打包文件说明(一般不建议对 python 项目打包) 4 | 5 | pip 安装 `pyinstaller` 后,使用 `pyinstaller main.spec`,对项目打包。 6 | """ 7 | 8 | import os 9 | import site 10 | 11 | sitepackages = site.getsitepackages() 12 | 13 | 14 | def sitepackages_location(package_name): 15 | for sp in sitepackages: 16 | if os.path.exists(os.path.join(sp, package_name)): 17 | return sp 18 | raise RuntimeError(f"{package_name} not found") 19 | 20 | 21 | block_cipher = None 22 | 23 | 24 | a = Analysis( 25 | ['main.py'], 26 | pathex=['.'], 27 | binaries=[], 28 | datas=[ 29 | ("packedfiles", "packedfiles"), 30 | ("public", "public"), 31 | ('ybplugins/clan_battle/components/fonts/', 'fonts'), 32 | ], 33 | hiddenimports=[], 34 | hookspath=[], 35 | runtime_hooks=[], 36 | excludes=[], 37 | win_no_prefer_redirects=False, 38 | win_private_assemblies=False, 39 | cipher=block_cipher, 40 | noarchive=False) 41 | pyz = PYZ(a.pure, a.zipped_data, 42 | cipher=block_cipher) 43 | exe = EXE(pyz, 44 | a.scripts, 45 | a.binaries, 46 | a.zipfiles, 47 | a.datas, 48 | [], 49 | name='yobot', 50 | debug=False, 51 | bootloader_ignore_signals=False, 52 | strip=False, 53 | upx=True, 54 | upx_exclude=[], 55 | runtime_tmpdir=None, 56 | console=True, 57 | icon='./logo.ico', 58 | ) 59 | -------------------------------------------------------------------------------- /src/client/nonebot_plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | 实例3:作为nonebot的插件 3 | 4 | 加载方法: 5 | 6 | 将这个项目整个文件夹放在nonebot插件目录下即可 7 | """ 8 | 9 | import sys 10 | 11 | if __name__ == "__main__": 12 | import os 13 | 14 | if len(sys.argv) < 2 or sys.argv[1] != "make_plugin": 15 | raise ValueError("unknown command") 16 | 17 | def makefile(path, content="# doing nothing"): 18 | with open(path, "w") as f: 19 | f.write(content) 20 | 21 | filepath = os.path.abspath(os.path.join(os.getcwd(), "__init__.py")) 22 | makefile(filepath) 23 | filepath = os.path.abspath(os.path.join(os.getcwd(), "../__init__.py")) 24 | makefile(filepath) 25 | filepath = os.path.abspath(os.path.join(os.getcwd(), "../../__init__.py")) 26 | makefile(filepath, "from .src.client import nonebot_plugin") 27 | 28 | sys.exit() 29 | 30 | from .yobot import Yobot 31 | import asyncio 32 | 33 | if "nonebot" in sys.modules: 34 | from nonebot import get_bot, scheduler 35 | else: 36 | raise ValueError("plugin imported before noenbot imported") 37 | 38 | verinfo = { 39 | "run-as": "nonebot-plugin", 40 | "ver_name": "yobot_remix{}插件版".format(Yobot.Version), 41 | } 42 | 43 | cqbot = get_bot() 44 | bot = Yobot( 45 | data_path="./yobot_data", 46 | verinfo=verinfo, 47 | scheduler=scheduler, 48 | quart_app=cqbot.server_app, 49 | bot_api=cqbot._api, 50 | ) 51 | 52 | from hoshino.service import Service 53 | 54 | sv = Service("yobot", enable_on_default=True, visible=True) 55 | 56 | 57 | @sv.on_message() 58 | async def handle_msg(cqbot, context): 59 | if context["message_type"] == "group": 60 | reply = await bot.proc_async(context.copy()) 61 | else: 62 | reply = None 63 | if reply != "" and reply is not None: 64 | """return {'reply': reply,'at_sender': False}""" 65 | await cqbot.send(context, reply, at_sender=False) 66 | else: 67 | return None 68 | 69 | @cqbot.on_message 70 | async def handle_msg(context): 71 | if context["message_type"] == "private": 72 | reply = await bot.proc_async(context.copy()) 73 | else: 74 | reply = None 75 | if reply != "" and reply is not None: 76 | await cqbot.send(context,reply) 77 | # return {"reply": reply, "at_sender": False} 78 | else: 79 | return None 80 | 81 | 82 | async def send_it(func): 83 | if asyncio.iscoroutinefunction(func): 84 | to_sends = await func() 85 | else: 86 | to_sends = func() 87 | if to_sends is None: 88 | return 89 | for kwargs in to_sends: 90 | await asyncio.sleep(5) 91 | await cqbot.send_msg(**kwargs) 92 | 93 | 94 | jobs = bot.active_jobs() 95 | if jobs: 96 | for trigger, job in jobs: 97 | scheduler.add_job( 98 | func=send_it, 99 | args=(job,), 100 | trigger=trigger, 101 | coalesce=True, 102 | max_instances=1, 103 | misfire_grace_time=60, 104 | ) 105 | 106 | __plugin_name__ = "yobot" 107 | __plugin_usage__ = "pcr assistant bot" 108 | -------------------------------------------------------------------------------- /src/client/packedfiles/default_BossIdAndName.json: -------------------------------------------------------------------------------- 1 | { 2 | "1":{ 3 | "302100":"双足飞龙", 4 | "305700":"巨型哥布林", 5 | "312501":"マダムエレクトラ" 6 | }, 7 | "2":{ 8 | "302000":"野性狮鹫", 9 | "304600":"雷雷", 10 | "309000":"陆生树懒", 11 | "316600":"ゴブリンライダー" 12 | }, 13 | "3":{ 14 | "300600":"针刺攀缘花", 15 | "300700":"兽人头目", 16 | "301000":"海龙", 17 | "303500":"怒蛇", 18 | "304500":"雷电", 19 | "304800":"幽灵领主", 20 | "305100":"巨型凶暴兔", 21 | "305900":"极彩鸟", 22 | "306100":"偷盗豺狼", 23 | "309200":"天空女武神", 24 | "307202":"巴吉里斯克", 25 | "303606":"バーンサウルス" 26 | }, 27 | "4":{ 28 | "300800":"灵魂角鹿", 29 | "301100":"狂乱魔熊", 30 | "301500":"魔界人狼", 31 | "303300":"暗黑滴水嘴兽", 32 | "303900":"独眼巨人", 33 | "304000":"暗黑独角兽", 34 | "304100":"泰坦陆龟", 35 | "305800":"海蛞蝓", 36 | "306200":"狸猫首领", 37 | "306900":"三战士图腾", 38 | "312000":"水晶闪耀之龙", 39 | "313400":"剑尾蛇" 40 | }, 41 | "5":{ 42 | "300100":"米诺陶诺斯", 43 | "300200":"利蝎巴鲁托", 44 | "300300":"梦魇杜羊", 45 | "300400":"摩羯教主", 46 | "301300":"暴食魔兽", 47 | "301400":"美杜莎", 48 | "301800":"人马射手", 49 | "302600":"双子魔猪", 50 | "302700":"巨钳魔蟹", 51 | "302800":"炎吼狮王", 52 | "302900":"阿克艾利欧斯", 53 | "303000":"托尔佩顿" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/client/packedfiles/default_boss.json: -------------------------------------------------------------------------------- 1 | { 2 | "jp": [ 3 | [6000000, 8000000, 10000000, 12000000, 15000000], 4 | [6000000, 8000000, 10000000, 12000000, 15000000], 5 | [7000000, 9000000, 13000000, 15000000, 20000000], 6 | [15000000, 16000000, 18000000, 19000000, 20000000] 7 | ], 8 | "cn": [ 9 | [6000000, 8000000, 10000000, 12000000, 20000000], 10 | [6000000, 8000000, 10000000, 12000000, 20000000], 11 | [6000000, 8000000, 10000000, 12000000, 20000000] 12 | ], 13 | "tw": [ 14 | [6000000, 8000000, 10000000, 12000000, 15000000], 15 | [6000000, 8000000, 10000000, 12000000, 15000000], 16 | [7000000, 9000000, 13000000, 15000000, 20000000], 17 | [15000000, 16000000, 18000000, 19000000, 20000000] 18 | ], 19 | "eff": [ 20 | [1.2, 1.2, 1.3, 1.4, 1.5], 21 | [1.6, 1.6, 1.8, 1.9, 2.0], 22 | [2.0, 2.0, 2.4, 2.4, 2.6], 23 | [2.4, 2.4, 2.6, 2.6, 3.0] 24 | ] 25 | } -------------------------------------------------------------------------------- /src/client/packedfiles/default_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "0.0.0.0", 3 | "port": 9222, 4 | "access_token": "", 5 | "client_salt": null, 6 | "public_address": null, 7 | "public_basepath": "/", 8 | "web_mode_hint": true, 9 | "super-admin": [], 10 | "black-list": [], 11 | "white_list_mode": false, 12 | "black-list-group": [], 13 | "white-list-group": [], 14 | "allow_bulk_private": false, 15 | "clan_battle_mode": "web", 16 | "notify_groups": [], 17 | "notify_privates": [], 18 | "preffix_on": false, 19 | "preffix_string": "", 20 | "zht_in": false, 21 | "zht_out": false, 22 | "zht_out_style": "s2t", 23 | "show_icp": false, 24 | "icp_info": "", 25 | "gongan_info": "", 26 | "web_gzip": 0, 27 | 28 | "boss":{ 29 | "jp": [ 30 | [6000000, 8000000, 10000000, 12000000, 15000000], 31 | [6000000, 8000000, 10000000, 12000000, 15000000], 32 | [7000000, 9000000, 13000000, 15000000, 20000000], 33 | [15000000, 16000000, 18000000, 19000000, 20000000] 34 | ], 35 | "cn": [ 36 | [6000000, 8000000, 10000000, 12000000, 20000000], 37 | [6000000, 8000000, 10000000, 12000000, 20000000], 38 | [6000000, 8000000, 10000000, 12000000, 20000000] 39 | ], 40 | "tw": [ 41 | [6000000, 8000000, 10000000, 12000000, 15000000], 42 | [6000000, 8000000, 10000000, 12000000, 15000000], 43 | [6000000, 8000000, 10000000, 12000000, 15000000] 44 | ] 45 | }, 46 | "level_by_cycle":{ 47 | "cn":[[1,3],[4,10],[11,999]], 48 | "jp":[[1,3],[4,10],[11,45],[46,999]], 49 | "tw":[[1,3],[4,10],[11,999]] 50 | }, 51 | "boss_id":{ 52 | "cn":["302100","302000","300600","300800","300100"], 53 | "jp":["302100","302000","300600","300800","300100"], 54 | "tw":["302100","302000","300600","300800","300100"] 55 | } 56 | } -------------------------------------------------------------------------------- /src/client/packedfiles/default_pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "info":{ 3 | "name":"默认卡池" 4 | }, 5 | "settings":{ 6 | "combo": 10, 7 | "day_limit": 2, 8 | "auto_update": false, 9 | "shuffle": false 10 | }, 11 | "pool": { 12 | "star3": { 13 | "prop": 25, 14 | "prop_last": 25, 15 | "prefix":"★★★", 16 | "pool": [ 17 | "初音","真琴","姬塔","咲恋","望","璃乃","妮侬","伊绪","秋乃","莫妮卡","静流", 18 | "杏奈","纯","真步","亚里沙","镜华","伊利亚","智","流夏","香澄","安","古蕾雅", 19 | "空花(大江户)","妮侬(大江户)","碧(插班生)","克萝伊", "美美(万圣节)", 20 | "露娜","卡娅","伊利亚(圣诞节)","霞(魔法少女)","优妮","琪爱儿","铃(游侠)", 21 | "真阳(游侠)", 22 | 23 | "佩可莉姆(夏日)","铃莓(夏日)","凯留(夏日)","珠希(夏日)","忍(万圣节)", 24 | "美咲(万圣节)","千歌(圣诞节)","绫音(圣诞节)","日和莉(新年)","优衣(新年)", 25 | "静流(情人节)","蕾姆","艾米莉亚","玲奈(夏日)","咲恋(夏日)","真琴(夏日)", 26 | "真步(夏日)","镜华(万圣节)","克里斯蒂娜(圣诞节)","可可萝(新年)", 27 | "凯露(新年)","岛村卯月(偶像大师)","涉谷凛(偶像大师)" 28 | ] 29 | }, 30 | "star2": { 31 | "prop": 180, 32 | "prop_last": 975, 33 | "prefix":"★★", 34 | "pool": [ 35 | "空花","美冬","雪","茜里","珠希","美美","真阳","忍","香织","千歌","深月", 36 | "惠理子","宫子","栞","铃奈","铃","绫音","美里","纺希","茉莉","娜娜卡" 37 | ] 38 | }, 39 | "star1": { 40 | "prop": 795, 41 | "prop_last": 0, 42 | "prefix":"★", 43 | "pool": [ 44 | "怜","尤加莉","碧","依里","未奏希","莉玛","铃莓","美咲","日和莉","胡桃","步未" 45 | ] 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/client/public/libs/canvas-nest.js@1.0.1/dist/canvas-nest.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 hustcc 3 | * License: MIT 4 | * Version: v1.0.1 5 | * GitHub: https://github.com/hustcc/canvas-nest.js 6 | **/ 7 | !function(){function n(n,e,t){return n.getAttribute(e)||t}function e(n){return document.getElementsByTagName(n)}function t(){var t=e("script"),o=t.length,i=t[o-1];return{l:o,z:n(i,"zIndex",-1),o:n(i,"opacity",.5),c:n(i,"color","0,0,0"),n:n(i,"count",99)}}function o(){a=m.width=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,c=m.height=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight}function i(){r.clearRect(0,0,a,c);var n,e,t,o,m,l;s.forEach(function(i,x){for(i.x+=i.xa,i.y+=i.ya,i.xa*=i.x>a||i.x<0?-1:1,i.ya*=i.y>c||i.y<0?-1:1,r.fillRect(i.x-.5,i.y-.5,1,1),e=x+1;e=n.max/2&&(i.x-=.03*o,i.y-=.03*m),t=(n.max-l)/n.max,r.beginPath(),r.lineWidth=t/2,r.strokeStyle="rgba("+d.c+","+(t+.2)+")",r.moveTo(i.x,i.y),r.lineTo(n.x,n.y),r.stroke()))}),x(i)}var a,c,u,m=document.createElement("canvas"),d=t(),l="c_n"+d.l,r=m.getContext("2d"),x=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(n){window.setTimeout(n,1e3/45)},w=Math.random,y={x:null,y:null,max:2e4};m.id=l,m.style.cssText="position:fixed;top:0;left:0;z-index:"+d.z+";opacity:"+d.o,e("body")[0].appendChild(m),o(),window.onresize=o,window.onmousemove=function(n){n=n||window.event,y.x=n.clientX,y.y=n.clientY},window.onmouseout=function(){y.x=null,y.y=null};for(var s=[],f=0;d.n>f;f++){var h=w()*a,g=w()*c,v=2*w()-1,p=2*w()-1;s.push({x:h,y:g,xa:v,ya:p,max:6e3})}u=s.concat([y]),setTimeout(function(){i()},100)}(); -------------------------------------------------------------------------------- /src/client/public/libs/element-ui@2.13.0/lib/theme-chalk/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/element-ui@2.13.0/lib/theme-chalk/fonts/element-icons.ttf -------------------------------------------------------------------------------- /src/client/public/libs/element-ui@2.13.0/lib/theme-chalk/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/element-ui@2.13.0/lib/theme-chalk/fonts/element-icons.woff -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/banner.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/0.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/300100.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/300100.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/300200.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/300200.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/300300.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/300300.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/300400.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/300400.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/300600.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/300600.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/300700.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/300700.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/300800.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/300800.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/301000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/301000.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/301100.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/301100.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/301300.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/301300.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/301400.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/301400.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/301500.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/301500.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/301800.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/301800.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/302000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/302000.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/302100.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/302100.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/302600.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/302600.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/302700.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/302700.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/302800.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/302800.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/302900.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/302900.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/303000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/303000.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/303300.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/303300.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/303500.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/303500.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/303606.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/303606.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/303900.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/303900.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/304000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/304000.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/304100.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/304100.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/304500.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/304500.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/304600.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/304600.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/304800.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/304800.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/305100.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/305100.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/305700.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/305700.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/305800.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/305800.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/305900.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/305900.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/306100.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/306100.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/306200.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/306200.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/306900.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/306900.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/307202.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/307202.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/309000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/309000.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/309200.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/309200.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/312000.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/312000.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/312501.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/312501.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/313400.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/313400.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/boss_icon/316600.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/boss_icon/316600.webp -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/box-gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/box-gift.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/clanbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/clanbg.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/error.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/flag.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/icon-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/icon-loading.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/jquery.sliderBar.js: -------------------------------------------------------------------------------- 1 | /*************************************************************************************** 2 | 侧边栏插件 3 | @autor iProg 4 | @date 2016-01-25 5 | @version 1.0 6 | 7 | 使用方法: 8 | 在页面建立html标签如下: 9 |
10 |
通知消息
11 |
12 | 无消息 13 |
14 |
15 | 16 | 说明:上面的class属性值,除了sliderbar-container1可以随意更改,其它的如title,body都 17 | 不能更改哦! 18 | 19 | 然后加入js代码如下,就可以了: 20 | 32 | ****************************************************************************************/ 33 | ;(function ($) { 34 | $.fn.extend({ 35 | "sliderBar": function (options) { 36 | // 使用jQuery.extend 覆盖插件默认参数 37 | var opts = $.extend( 38 | {} , 39 | $.fn.sliderBar.defalutPublic , 40 | options 41 | ); 42 | 43 | // 这里的this 就是 jQuery对象,遍历页面元素对象 44 | // 加个return可以链式调用 45 | return this.each(function () { 46 | //获取当前元素 的this对象 47 | var $this = $(this); 48 | 49 | $this.data('open', opts.open); 50 | 51 | privateMethods.initSliderBarCss($this, opts); 52 | 53 | switch(opts.position){ 54 | case 'right' : privateMethods.showAtRight($this, opts); break; 55 | case 'left' : privateMethods.showAtLeft($this, opts); break; 56 | } 57 | 58 | }); 59 | } 60 | }); 61 | 62 | // 默认公有参数 63 | $.fn.sliderBar.defalutPublic = { 64 | open : true, // 默认是否打开,true打开,false关闭 65 | top : 200, // 距离顶部多高 66 | width : 260, // body内容宽度 67 | height : 200, // body内容高度 68 | position : 'left' // 显示位置,有left和right两种 69 | } 70 | 71 | var privateMethods = { 72 | initSliderBarCss : function(obj, opts){ 73 | obj.css({ 74 | 'width': opts.width+20+'px', 75 | 'height' : opts.height+20+'px', 76 | 'top' : opts.top+'px', 77 | 'position':'fixed', 78 | 'font-family':'Microsoft Yahei', 79 | 'z-index': '9999' 80 | }).find('.body').css({ 81 | 'width': opts.width+'px', 82 | 'height' : opts.height+'px', 83 | 'position':'relative', 84 | 'padding':'15px', 85 | 'overflow-x':'hidden', 86 | 'overflow-y':'auto', 87 | 'font-family':'Microsoft Yahei', 88 | 'font-size' : '15px' 89 | }); 90 | 91 | var titleCss = { 92 | 'width':'15px', 93 | 'height':'105px', 94 | 'position':'absolute', 95 | 'top':'-1px', 96 | 'display':'block', 97 | 'font-size': '13px', 98 | 'padding':'8px 4px 8px 5px', 99 | 'color':'#fff', 100 | 'cursor': 'pointer', 101 | 'font-family':'Microsoft Yahei' 102 | } 103 | 104 | obj.find('.title').css(titleCss).find('i').css({ 105 | 'font-size': '15px' 106 | }); 107 | } 108 | }; 109 | })(jQuery) 110 | -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/karin_q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/karin_q.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/karyl_q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/karyl_q.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/kokkoro_q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/kokkoro_q.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/logo.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/pecorine_q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/pecorine_q.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/scheme-01.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color-1: #ffa84e;/* 主色调1 */ 3 | --primary-color-2: #ffd34e;/* 主色调2 */ 4 | --secondary-color-1: #ffba54;/* 副色调1 */ 5 | --secondary-color-2: #FF9800;/* 副色调2 */ 6 | --background-color: #fbebd3;/* 背景颜色 */ 7 | --font-color: #FF9800;/* 文字颜色 */ 8 | } 9 | 10 | /* CharacterspriteAnime */ 11 | 12 | .character-container { 13 | width: 200px; 14 | height: 200px; 15 | overflow: hidden; 16 | background: url("/yobot-depencency/yocool@final/princessadventure/sprite01.png"); 17 | background-size: 100%; 18 | } 19 | 20 | .character-container.char { 21 | animation: spriteAnimechar 700ms steps(18) infinite; 22 | } 23 | 24 | @keyframes spriteAnimechar { 25 | 0% { 26 | background-position: 0 0; 27 | } 28 | 29 | 100% { 30 | background-position: 0 -3600px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/scheme-02.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color-1: #3f51b5;/* 主色调1 */ 3 | --primary-color-2: #626ead;/* 主色调2 */ 4 | --secondary-color-1: #344080;/* 副色调1 */ 5 | --secondary-color-2: #3a177b;/* 副色调2 */ 6 | --background-color: #e8ebff;/* 背景色 */ 7 | --font-color: #032584;/* 文字颜色 */ 8 | } 9 | 10 | /* CharacterspriteAnime */ 11 | 12 | .character-container { 13 | width: 200px; 14 | height: 200px; 15 | overflow: hidden; 16 | background: url("/yobot-depencency/yocool@final/princessadventure/sprite02.png"); 17 | background-size: 100%; 18 | } 19 | 20 | .character-container.char { 21 | animation: spriteAnimechar 700ms steps(19) infinite; 22 | } 23 | 24 | @keyframes spriteAnimechar { 25 | 0% { 26 | background-position: 0 0; 27 | } 28 | 29 | 100% { 30 | background-position: 0 -3800px; 31 | } 32 | } -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/scheme-03.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color-1: #56c596;/* 主色调1 */ 3 | --primary-color-2: #73c088;/* 主色调2 */ 4 | --secondary-color-1: #397d54;/* 副色调1 */ 5 | --secondary-color-2: #4dd252;/* 副色调2 */ 6 | --background-color: #d5ffd0;/* 背景色 */ 7 | --font-color: #117c6f;/* 文字颜色 */ 8 | } 9 | 10 | /* CharacterspriteAnime */ 11 | 12 | .character-container { 13 | width: 200px; 14 | height: 200px; 15 | overflow: hidden; 16 | background: url("/yobot-depencency/yocool@final/princessadventure/sprite03.png"); 17 | background-size: 100%; 18 | } 19 | 20 | .character-container.char { 21 | animation: spriteAnimechar 700ms steps(19) infinite; 22 | } 23 | 24 | @keyframes spriteAnimechar { 25 | 0% { 26 | background-position: 0 0; 27 | } 28 | 29 | 100% { 30 | background-position: 0 -3800px; 31 | } 32 | } -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/sprite01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/sprite01.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/sprite02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/sprite02.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/sprite03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/sprite03.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/text-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/text-loading.png -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/yocool.js: -------------------------------------------------------------------------------- 1 | var link = []; 2 | link[0] = "/yobot-depencency/yocool@final/princessadventure/scheme-01.css"; 3 | link[1] = "/yobot-depencency/yocool@final/princessadventure/scheme-02.css"; 4 | link[2] = "/yobot-depencency/yocool@final/princessadventure/scheme-03.css"; 5 | $(function() { 6 | var style = link[Math.floor(Math.random() * link.length)]; 7 | if (document.createStyleSheet) { 8 | document.createStyleSheet(style) 9 | } else { 10 | $('', { 11 | rel: 'stylesheet', 12 | href: style 13 | }).appendTo('head') 14 | } 15 | }); 16 | document.writeln("
Powered by Yobot    |    Themes by YoCool
"); 17 | $(function() { 18 | $(window).scroll(function() { 19 | var topToolbar = $("#topToolbar"); 20 | var headerH = $("#header").outerHeight(); 21 | var scrollTop = $(document).scrollTop() 22 | }) 23 | }); 24 | ;(function ($) { 25 | $.fn.extend({ 26 | "sliderBar": function (options) { 27 | var opts = $.extend( 28 | {} , 29 | $.fn.sliderBar.defalutPublic , 30 | options 31 | ); 32 | return this.each(function () { 33 | var $this = $(this); 34 | 35 | $this.data('open', opts.open); 36 | 37 | privateMethods.initSliderBarCss($this, opts); 38 | 39 | switch(opts.position){ 40 | case 'right' : privateMethods.showAtRight($this, opts); break; 41 | case 'left' : privateMethods.showAtLeft($this, opts); break; 42 | } 43 | 44 | }); 45 | } 46 | }); 47 | 48 | $.fn.sliderBar.defalutPublic = { 49 | open : false, 50 | top : 200, 51 | width : 260, 52 | height : 200, 53 | position : 'left' 54 | } 55 | 56 | var privateMethods = { 57 | initSliderBarCss : function(obj, opts){ 58 | obj.css({ 59 | 'width': opts.width+20+'px', 60 | 'height' : opts.height+20+'px', 61 | 'top' : opts.top+'px', 62 | 'position':'fixed', 63 | 'font-family':'Microsoft Yahei', 64 | 'z-index': '9999' 65 | }).find('.body').css({ 66 | 'width': opts.width+'px', 67 | 'height' : opts.height+'px', 68 | 'position':'relative', 69 | 'padding':'10px', 70 | 'overflow-x':'hidden', 71 | 'overflow-y':'auto', 72 | 'font-family':'Microsoft Yahei', 73 | 'font-size' : '14px' 74 | }); 75 | 76 | var titleCss = { 77 | 'width':'15px', 78 | 'position':'absolute', 79 | 'top':'-1px', 80 | 'display':'block', 81 | 'font-size': '13px', 82 | 'padding':'8px 4px 8px 5px', 83 | 'color':'#fff', 84 | 'cursor': 'pointer', 85 | 'font-family':'Microsoft Yahei' 86 | } 87 | 88 | obj.find('.title').css(titleCss).find('i').css({ 89 | 'font-size': '15px' 90 | }); 91 | }, 92 | showAtLeft : function(obj, opts){ 93 | if(opts.open){ 94 | obj.css({left:'0px'}); 95 | obj.find('.title').css('right','-25px').find('i').attr('class','fa fa-chevron-circle-left'); 96 | }else{ 97 | obj.css({left:-opts.width-22+'px'}); 98 | obj.find('.title').css('right','-25px').find('i').attr('class','fa fa-chevron-circle-right'); 99 | } 100 | 101 | obj.find('.title').click(function(){ 102 | if(obj.data('open')){ 103 | obj.animate({left:-opts.width-22+'px'}, 500); 104 | $(this).find('i').attr('class','fa fa-chevron-circle-right'); 105 | }else{ 106 | obj.animate({left:'0px'}, 500); 107 | $(this).find('i').attr('class','fa fa-chevron-circle-left'); 108 | } 109 | obj.data('open',obj.data('open') == true ? false : true); 110 | }); 111 | }, 112 | showAtRight : function(obj, opts){ 113 | if(opts.open){ 114 | obj.css({right:'0px'}); 115 | obj.find('.title').css('right', opts.width+20+'px').find('i').attr('class','fa fa-chevron-circle-right'); 116 | }else{ 117 | obj.css({right:'25px'}); 118 | obj.find('.title').css('right', opts.width+20+'px').find('i').attr('class','fa fa-chevron-circle-left'); 119 | } 120 | 121 | obj.find('.title').click(function(){ 122 | if(obj.data('open')){ 123 | obj.animate({right:-opts.width-22+'px'}, 500); 124 | $(this).find('i').attr('class','fa fa-chevron-circle-left'); 125 | }else{ 126 | obj.animate({right:'0px'}, 500); 127 | $(this).find('i').attr('class','fa fa-chevron-circle-right'); 128 | } 129 | obj.data('open',obj.data('open') == true ? false : true); 130 | }); 131 | } 132 | }; 133 | })(jQuery) -------------------------------------------------------------------------------- /src/client/public/libs/yocool@final/princessadventure/yui_q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/libs/yocool@final/princessadventure/yui_q.png -------------------------------------------------------------------------------- /src/client/public/static/admin/groups.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | groupData: [], 5 | }, 6 | mounted() { 7 | this.refresh(); 8 | }, 9 | methods: { 10 | refresh: function (event) { 11 | var thisvue = this; 12 | axios.post(api_path, { 13 | action: 'get_data', 14 | csrf_token: csrf_token, 15 | }).then(function (res) { 16 | if (res.data.code == 0) { 17 | thisvue.groupData = res.data.data; 18 | } else { 19 | thisvue.$alert(res.data.message, '加载数据错误'); 20 | } 21 | }).catch(function (error) { 22 | thisvue.$alert(error, '加载数据错误'); 23 | }); 24 | }, 25 | delete_group: function (scope) { 26 | var thisvue = this; 27 | thisvue.$confirm('是否删除' + scope.row.group_name, '提示', { 28 | confirmButtonText: '确定', 29 | cancelButtonText: '取消', 30 | type: 'danger' 31 | }).then(() => { 32 | axios.post(api_path, { 33 | action: 'drop_group', 34 | csrf_token: csrf_token, 35 | group_id: scope.row.group_id, 36 | }).then(function (res) { 37 | if (res.data.code == 0) { 38 | thisvue.$message({ 39 | message: '删除成功', 40 | type: 'success', 41 | }); 42 | } else { 43 | thisvue.$message.error('删除失败' + res.data.message); 44 | } 45 | }).catch(function (error) { 46 | thisvue.$message.error(error); 47 | }); 48 | }).catch(() => { 49 | thisvue.$message({ 50 | type: 'info', 51 | message: '已取消删除' 52 | }); 53 | }); 54 | }, 55 | }, 56 | delimiters: ['[[', ']]'], 57 | }) -------------------------------------------------------------------------------- /src/client/public/static/admin/pool-setting.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | settings: null, 5 | }, 6 | mounted() { 7 | var thisvue = this; 8 | axios.get(api_path).then(function (res) { 9 | if (res.data.code == 0) { 10 | thisvue.settings = res.data.settings; 11 | } else { 12 | alert(res.data.message, '加载数据错误'); 13 | } 14 | }).catch(function (error) { 15 | alert(error, '加载数据错误'); 16 | }); 17 | }, 18 | methods: { 19 | addpool: function () { 20 | let newname = "奖池" + (Object.keys(this.settings.pool).length+1); 21 | this.$set(this.settings.pool, newname, { 22 | prop: 0, 23 | prop_last: 0, 24 | prefix: "★★★", 25 | pool: ["请输入内容"], 26 | }); 27 | }, 28 | update: function () { 29 | var thisvue = this; 30 | axios.put(api_path, { 31 | setting: thisvue.settings, 32 | csrf_token: csrf_token, 33 | }).then(function (res) { 34 | if (res.data.code == 0) { 35 | alert('设置成功,重启后生效'); 36 | } else { 37 | alert('设置失败:' + res.data.message); 38 | } 39 | }).catch(function (error) { 40 | alert(error); 41 | }); 42 | }, 43 | }, 44 | delimiters: ['[[', ']]'], 45 | }) -------------------------------------------------------------------------------- /src/client/public/static/admin/setting.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | setting: {}, 5 | activeNames: [], 6 | bossSetting: false, 7 | domain: '', 8 | domainApply: false, 9 | applyName: '', 10 | loading: false, 11 | boss_id_name: {}, 12 | }, 13 | mounted() { 14 | var thisvue = this; 15 | axios.get(api_path).then(function (res) { 16 | if (res.data.code == 0) { 17 | thisvue.setting = res.data.settings; 18 | thisvue.boss_id_name = res.data.boss_id_name; 19 | } else { 20 | alert(res.data.message); 21 | } 22 | }).catch(function (error) { 23 | alert(error); 24 | }); 25 | }, 26 | methods: { 27 | update: function (event) { 28 | var [flag, msg] = this.check_level_by_cycle() 29 | if (!flag) { 30 | alert(msg); 31 | return 32 | } 33 | this.setting.web_mode_hint = false; 34 | axios.put( 35 | api_path, 36 | { 37 | setting: this.setting, 38 | csrf_token: csrf_token, 39 | }, 40 | ).then(function (res) { 41 | if (res.data.code === 0) { 42 | alert('设置成功,重启机器人后生效'); 43 | } else { 44 | alert('设置失败:' + res.data.message); 45 | } 46 | }).catch(function (error) { 47 | alert(error); 48 | }); 49 | }, 50 | auto_get_boss_data: function() { 51 | var thisvue = this; 52 | thisvue.$message({ 53 | message:'自动获取更新中,请稍后......', 54 | type:'info', 55 | }) 56 | axios.post("./auto_get_boss_data/", { 57 | csrf_token: csrf_token 58 | }).then(function (res) { 59 | if (res.data.code == 0) { 60 | thisvue.$alert(res.data.message + '

刷新页面查看获取结果,重启机器人后生效。', '获取成功', { 61 | dangerouslyUseHTMLString: true, 62 | confirmButtonText: '好的', 63 | type: 'success', 64 | }); 65 | } else { 66 | thisvue.$message({ 67 | dangerouslyUseHTMLString: true, 68 | message:res.data.message + '
自动获取失败', 69 | type:'error', 70 | }) 71 | } 72 | }).catch(function (error) { 73 | thisvue.$message({ 74 | dangerouslyUseHTMLString: true, 75 | message:error + '
自动获取失败', 76 | type:'error', 77 | }) 78 | }); 79 | }, 80 | sendApply: function (api) { 81 | if (this.domain === '') { 82 | alert('请选择后缀'); 83 | return; 84 | } 85 | if (/^[0-9a-z]{1,16}$/.test(this.applyName)) { 86 | ; 87 | } else { 88 | alert('只能包含字母、数字'); 89 | return; 90 | } 91 | var thisvue = this; 92 | this.loading = true; 93 | axios.get( 94 | api + '?name=' + thisvue.applyName + thisvue.domain 95 | ).then(function (res) { 96 | thisvue.domainApply = false; 97 | if (res.data.code == 0) { 98 | alert('申请成功,请等待1分钟左右解析生效'); 99 | thisvue.setting.public_address = thisvue.setting.public_address.replace(/\/\/([^:\/]+)/, '//' + thisvue.applyName + thisvue.domain); 100 | thisvue.update(null); 101 | } else if (res.data.code == 1) { 102 | alert('申请失败,此域已被占用'); 103 | } else { 104 | alert('申请失败,' + res.data.message); 105 | } 106 | thisvue.loading = false; 107 | }).catch(function (error) { 108 | thisvue.loading = false; 109 | alert(error); 110 | }); 111 | }, 112 | comfirm_change_clan_mode: function (event) { 113 | this.$alert('修改模式后,公会战数据会重置。请不要在公会战期间修改!', '警告', { 114 | confirmButtonText: '知道了', 115 | type: 'warning', 116 | }); 117 | }, 118 | add_level: function (area) { 119 | this.setting.boss[area].push([0, 0, 0, 0, 0]); 120 | this.setting.level_by_cycle[area].push([0, 0]); 121 | }, 122 | remove_level: function (area) { 123 | this.setting.boss[area].pop(); 124 | this.setting.level_by_cycle[area].pop(); 125 | }, 126 | check_level_by_cycle: function () { 127 | const regionMap = {cn: "国服", jp: "日服", tw: "台服"}; 128 | for (const area in this.setting.level_by_cycle) { 129 | var last_level_max = this.setting.level_by_cycle[area][0][0]-1; 130 | var has_level_starting_with_1 = false; 131 | for (const level_info of this.setting.level_by_cycle[area]) { 132 | if (level_info[0] !== last_level_max + 1 || level_info[0] > level_info[1]) 133 | return [false,`${regionMap[area]}阶段对应周目错误。\n不同阶段的周目范围不能重叠,且下阶段开始周目必须等于上阶段结束周目加一`]; 134 | if (level_info[0] === 1) 135 | has_level_starting_with_1 = true; 136 | last_level_max = level_info[1] 137 | } 138 | if (!has_level_starting_with_1) 139 | return [false, `${regionMap[area]}阶段对应周目错误。\n至少要有一个阶段以1周目开始`]; 140 | } 141 | return [true,''] 142 | } 143 | }, 144 | delimiters: ['[[', ']]'], 145 | }) -------------------------------------------------------------------------------- /src/client/public/static/admin/users.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | isLoading: true, 5 | moreLoading: false, 6 | userData: [], 7 | querys: { 8 | page: 1, 9 | page_size: 50, 10 | qqid: null, 11 | clan_group_id: null, 12 | authority_group: null, 13 | }, 14 | query_input: { 15 | qqid: null, 16 | clan_group_id: null, 17 | authority_group: null, 18 | }, 19 | has_more: true, 20 | authtype: [{ 21 | value: 100, 22 | label: '成员', 23 | }, { 24 | value: 10, 25 | label: '公会战管理员', 26 | }, { 27 | value: 1, 28 | label: '主人', 29 | }], 30 | }, 31 | mounted() { 32 | this.load_more(); 33 | }, 34 | methods: { 35 | datestr: function (ts) { 36 | if (ts == 0) { 37 | return null; 38 | } 39 | var nd = new Date(); 40 | nd.setTime(ts * 1000); 41 | return nd.toLocaleString('chinese', { hour12: false, timeZone: 'asia/shanghai' }); 42 | }, 43 | search: function (event) { 44 | Object.assign(this.querys, this.query_input); 45 | this.querys.page = 1; 46 | this.isLoading = true; 47 | this.userData = []; 48 | this.load_more(); 49 | }, 50 | load_more: function (event) { 51 | this.moreLoading = true; 52 | var thisvue = this; 53 | axios.post(api_path, { 54 | action: 'get_data', 55 | querys: thisvue.querys, 56 | csrf_token: csrf_token, 57 | }).then(function (res) { 58 | if (res.data.code == 0) { 59 | thisvue.userData.push(...res.data.data); 60 | thisvue.isLoading = false; 61 | thisvue.moreLoading = false; 62 | if (res.data.data.length < thisvue.querys.page_size) { 63 | thisvue.has_more = false; 64 | } else { 65 | thisvue.querys.page += 1; 66 | } 67 | } else { 68 | thisvue.$alert(res.data.message, '加载数据错误'); 69 | } 70 | }).catch(function (error) { 71 | thisvue.$alert(error, '加载数据错误'); 72 | }); 73 | }, 74 | modify: function (scope) { 75 | var thisvue = this; 76 | axios.post(api_path, { 77 | action: 'modify_user', 78 | csrf_token: csrf_token, 79 | data: { 80 | qqid: scope.row.qqid, 81 | authority_group: scope.row.authority_group, 82 | }, 83 | }).then(function (res) { 84 | if (res.data.code == 0) { 85 | thisvue.$message({ 86 | message: '修改成功', 87 | type: 'success', 88 | }); 89 | } else { 90 | thisvue.$message.error('修改失败' + res.data.message); 91 | } 92 | }).catch(function (error) { 93 | thisvue.$message.error(error); 94 | }); 95 | }, 96 | delete_user: function (scope) { 97 | var thisvue = this; 98 | thisvue.$confirm('是否删除' + scope.row.nickname, '提示', { 99 | confirmButtonText: '确定', 100 | cancelButtonText: '取消', 101 | type: 'danger' 102 | }).then(() => { 103 | axios.post(api_path, { 104 | action: 'delete_user', 105 | csrf_token: csrf_token, 106 | data: { 107 | qqid: scope.row.qqid, 108 | }, 109 | }).then(function (res) { 110 | if (res.data.code == 0) { 111 | thisvue.$message({ 112 | message: '删除成功', 113 | type: 'success', 114 | }); 115 | } else { 116 | thisvue.$message.error('删除失败' + res.data.message); 117 | } 118 | }).catch(function (error) { 119 | thisvue.$message.error(error); 120 | }); 121 | }).catch(() => { 122 | thisvue.$message({ 123 | type: 'info', 124 | message: '已取消删除' 125 | }); 126 | }); 127 | 128 | 129 | 130 | 131 | }, 132 | }, 133 | delimiters: ['[[', ']]'], 134 | }) -------------------------------------------------------------------------------- /src/client/public/static/chara_marks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/static/chara_marks.png -------------------------------------------------------------------------------- /src/client/public/static/clan/clan-rank.js: -------------------------------------------------------------------------------- 1 | if (!Object.defineProperty) { 2 | alert('浏览器版本过低'); 3 | } 4 | var vm = new Vue({ 5 | el: '#app', 6 | data: { 7 | activeIndex: "6", 8 | clanRankUrl : "", 9 | iframeHeight : 800, 10 | server: "cn" 11 | }, 12 | mounted() { 13 | var thisvue = this; 14 | axios.post("../api/", { 15 | action: 'get_data', 16 | csrf_token: csrf_token, 17 | }).then(function (res) { 18 | if (res.data.code == 0) { 19 | thisvue.server = res.data.groupData.game_server; 20 | thisvue.switchServer() 21 | } else { 22 | thisvue.$alert(res.data.message, '加载数据错误'); 23 | } 24 | }).catch(function (error) { 25 | thisvue.$alert(error, '加载数据错误'); 26 | }); 27 | 28 | window.addEventListener("resize", () => { 29 | thisvue.iframeHeight = document.documentElement.clientHeight - 65; 30 | }); 31 | 32 | thisvue.iframeHeight = document.documentElement.clientHeight - 65; 33 | }, 34 | 35 | methods: { 36 | handleSelect(key, keyPath) { 37 | switch (key) { 38 | case '1': 39 | window.location = '../'; 40 | break; 41 | case '2': 42 | window.location = '../subscribers/'; 43 | break; 44 | case '3': 45 | window.location = '../progress/'; 46 | break; 47 | case '4': 48 | window.location = '../statistics/'; 49 | break; 50 | case '5': 51 | window.location = `../my/`; 52 | break; 53 | case '6': 54 | window.location = `../clan-rank/`; 55 | break; 56 | } 57 | }, 58 | switchServer() { 59 | if(this.server == "cn") { 60 | this.clanRankUrl = "https://kyouka.kengxxiao.com/rank/clan"; 61 | }else if(this.server == "tw"){ 62 | this.clanRankUrl = "https://rank.layvtwt.top/"; 63 | } 64 | }, 65 | }, 66 | delimiters: ['[[', ']]'], 67 | }) -------------------------------------------------------------------------------- /src/client/public/static/clan/setting.js: -------------------------------------------------------------------------------- 1 | if (!Object.defineProperty) { 2 | alert('浏览器版本过低'); 3 | } 4 | var vm = new Vue({ 5 | el: '#app', 6 | data: { 7 | activeIndex: null, 8 | groupData: {}, 9 | battle_id: null, 10 | data_slot_record_count: [], 11 | form: { 12 | game_server: null, 13 | privacy: { 14 | allow_guest: false, 15 | allow_statistics_api: false, 16 | }, 17 | notify: { 18 | challenge: false, 19 | undo: false, 20 | apply: false, 21 | cancelapply: false, 22 | subscribe: false, 23 | cancelsubscribe: false, 24 | suspend: false, 25 | cancelsuspend: false, 26 | modify: false, 27 | sl: false, 28 | }, 29 | }, 30 | switchVisible: false, 31 | confirmVisible: false, 32 | }, 33 | mounted() { 34 | var thisvue = this; 35 | axios.post('./api/', { 36 | action: 'get_setting', 37 | csrf_token: csrf_token, 38 | }).then(function (res) { 39 | if (res.data.code == 0) { 40 | thisvue.groupData = res.data.groupData; 41 | thisvue.battle_id = res.data.groupData.battle_id; 42 | thisvue.form.game_server = res.data.groupData.game_server; 43 | thisvue.form.privacy.allow_guest = Boolean(res.data.privacy & 0x1); 44 | thisvue.form.privacy.allow_statistics_api = Boolean(res.data.privacy & 0x2); 45 | document.title = res.data.groupData.group_name + ' - 公会战设置'; 46 | var notify_code = res.data.notification; 47 | for (key in thisvue.form.notify) { 48 | thisvue.form.notify[key] = Boolean(notify_code & 1); 49 | notify_code >>= 1; 50 | } 51 | } else { 52 | thisvue.$alert(res.data.message, '加载数据失败'); 53 | } 54 | }).catch(function (error) { 55 | thisvue.$alert(error, '加载数据失败'); 56 | }); 57 | }, 58 | methods: { 59 | submit: function (event) { 60 | var thisvue = this; 61 | var privacy = (thisvue.form.privacy.allow_guest * 0x1) + (thisvue.form.privacy.allow_statistics_api * 0x2); 62 | var notify_code = 0; 63 | var magnitude = 1; 64 | for (key in thisvue.form.notify) { 65 | notify_code += thisvue.form.notify[key] * magnitude; 66 | magnitude <<= 1; 67 | } 68 | axios.post('./api/', { 69 | action: 'put_setting', 70 | csrf_token: csrf_token, 71 | game_server: thisvue.form.game_server, 72 | privacy: privacy, 73 | notification: notify_code, 74 | }).then(function (res) { 75 | if (res.data.code == 0) { 76 | thisvue.$notify({ 77 | title: '通知', 78 | message: '设置成功', 79 | }); 80 | } else { 81 | thisvue.$alert(res.data.message, '保存设置失败'); 82 | } 83 | }).catch(function (error) { 84 | thisvue.$alert(error, '保存设置失败'); 85 | }); 86 | }, 87 | export_data: function (event) { 88 | window.location = '../statistics/api/'; 89 | }, 90 | call_api: function (payload) { 91 | var thisvue = this; 92 | payload.csrf_token = csrf_token; 93 | axios.post('./api/', payload).then(function (res) { 94 | if (res.data.code == 0) { 95 | thisvue.$notify({ 96 | title: '通知', 97 | message: '成功', 98 | }); 99 | } else { 100 | thisvue.$alert(res.data.message, '失败'); 101 | } 102 | }).catch(function (error) { 103 | thisvue.$alert(error, '失败'); 104 | }); 105 | }, 106 | clear_data_slot: function (event) { 107 | this.call_api({ 108 | action: 'clear_data_slot', 109 | }); 110 | this.confirmVisible = false; 111 | }, 112 | // new_data_slot: function (event) { 113 | // this.call_api({ 114 | // action: 'new_data_slot', 115 | // }); 116 | // }, 117 | switch_data_slot: function (event) { 118 | this.call_api({ 119 | action: 'switch_data_slot', 120 | battle_id: this.battle_id, 121 | }); 122 | this.switchVisible = false; 123 | }, 124 | get_data_slot_record_count: function () { 125 | if (this.data_slot_record_count.length !== 0) { 126 | return 127 | } 128 | var thisvue = this; 129 | axios.post('./api/', { 130 | action: 'get_data_slot_record_count', 131 | csrf_token: csrf_token, 132 | }).then(function (res) { 133 | if (res.data.code == 0) { 134 | thisvue.data_slot_record_count = res.data.counts; 135 | } else { 136 | thisvue.$alert(res.data.message, '失败'); 137 | } 138 | }).catch(function (error) { 139 | thisvue.$alert(error, '失败'); 140 | }); 141 | }, 142 | handleSelect(key, keyPath) { 143 | switch (key) { 144 | case '1': 145 | window.location = '../'; 146 | break; 147 | case '2': 148 | window.location = '../subscribers/'; 149 | break; 150 | case '3': 151 | window.location = '../progress/'; 152 | break; 153 | case '4': 154 | window.location = '../statistics/'; 155 | break; 156 | case '5': 157 | window.location = `../my/`; 158 | break; 159 | case '6': 160 | window.location = `../clan-rank/`; 161 | break; 162 | } 163 | }, 164 | }, 165 | delimiters: ['[[', ']]'], 166 | }) -------------------------------------------------------------------------------- /src/client/public/static/clan/statistics.js: -------------------------------------------------------------------------------- 1 | if (!Object.defineProperty) { 2 | alert('浏览器版本过低'); 3 | } 4 | var vm = new Vue({ 5 | el: '#app', 6 | data: { 7 | progressData: [], 8 | members: [], 9 | tailsData: [], 10 | tailsDataVisible: false, 11 | group_name: null, 12 | reportDate: null, 13 | activeIndex: '4', 14 | multipleSelection: [], 15 | sendRemindVisible: false, 16 | send_via_private: false, 17 | dropMemberVisible: false, 18 | today: 0, 19 | }, 20 | methods: { 21 | handleTitleSelect(key, keyPath) { 22 | switch (key) { 23 | case '1': 24 | window.location = '../'; 25 | break; 26 | case '2': 27 | window.location = '../subscribers/'; 28 | break; 29 | case '3': 30 | window.location = '../progress/'; 31 | break; 32 | case '4': 33 | window.location = '../statistics/'; 34 | break; 35 | case '5': 36 | window.location = `../my/`; 37 | break; 38 | case '6': 39 | window.location = `../clan-rank/`; 40 | break; 41 | } 42 | }, 43 | } 44 | }) 45 | 46 | -------------------------------------------------------------------------------- /src/client/public/static/clan/statistics/deviation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/static/clan/statistics/deviation.png -------------------------------------------------------------------------------- /src/client/public/static/clan/statistics/many.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/static/clan/statistics/many.png -------------------------------------------------------------------------------- /src/client/public/static/clan/statistics/order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/static/clan/statistics/order.png -------------------------------------------------------------------------------- /src/client/public/static/clan/statistics/pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/static/clan/statistics/pie.png -------------------------------------------------------------------------------- /src/client/public/static/clan/subscribers.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#app', 3 | data: { 4 | bosstag: ['预约1', '预约2', '预约3', '预约4', '预约5'], 5 | subscribers: [ 6 | [], [], [], [], [], 7 | ], 8 | members: [], 9 | group_name: null, 10 | activeIndex: '2', 11 | }, 12 | mounted() { 13 | var thisvue = this; 14 | axios.post('../api/', { 15 | action: 'get_subscribers', 16 | csrf_token: csrf_token, 17 | }).then(function (res) { 18 | if (res.data.code == 0) { 19 | for (sub of res.data.subscribers) { 20 | thisvue.subscribers[sub.boss-1].push(sub); 21 | } 22 | thisvue.group_name = res.data.group_name; 23 | document.title = res.data.group_name + ' - 公会战设置'; 24 | } else { 25 | thisvue.$alert(res.data.message, '获取数据失败'); 26 | } 27 | }).catch(function (error) { 28 | thisvue.$alert(error, '获取数据失败'); 29 | }); 30 | axios.post('../api/', { 31 | action: 'get_member_list', 32 | csrf_token: csrf_token, 33 | }).then(function (res) { 34 | if (res.data.code == 0) { 35 | thisvue.members = res.data.members; 36 | } else { 37 | thisvue.$alert(res.data.message, '获取成员失败'); 38 | } 39 | }).catch(function (error) { 40 | thisvue.$alert(error, '获取成员失败'); 41 | }); 42 | }, 43 | methods: { 44 | find_name: function (qqid) { 45 | for (m of this.members) { 46 | if (m.qqid == qqid) { 47 | return m.nickname; 48 | } 49 | }; 50 | return qqid; 51 | }, 52 | handleSelect(key, keyPath) { 53 | switch (key) { 54 | case '1': 55 | window.location = '../'; 56 | break; 57 | case '2': 58 | window.location = '../subscribers/'; 59 | break; 60 | case '3': 61 | window.location = '../progress/'; 62 | break; 63 | case '4': 64 | window.location = '../statistics/'; 65 | break; 66 | case '5': 67 | window.location = `../my/`; 68 | break; 69 | case '6': 70 | window.location = `../clan-rank/`; 71 | break; 72 | } 73 | }, 74 | }, 75 | delimiters: ['[[', ']]'], 76 | }) -------------------------------------------------------------------------------- /src/client/public/static/clan/user.js: -------------------------------------------------------------------------------- 1 | var gs_offset = { 2 | jp: 4, 3 | tw: 5, 4 | kr: 4, 5 | cn: 5 6 | }; 7 | 8 | function pad2(num) { 9 | return String(num).padStart(2, '0'); 10 | } 11 | 12 | function ts2ds(timestamp) { 13 | var d = new Date(); 14 | d.setTime(timestamp * 1000); 15 | return d.getFullYear() + '/' + pad2(d.getMonth() + 1) + '/' + pad2(d.getDate()); 16 | } 17 | var vm = new Vue({ 18 | el: '#app', 19 | data: { 20 | isLoading: true, 21 | challengeData: [], 22 | activeIndex: '5', 23 | qqid: 0, 24 | nickname: '', 25 | tempList: [0, 1, 2, 3, 4, 5], 26 | members: [], 27 | }, 28 | mounted() { 29 | var thisvue = this; 30 | var pathname = window.location.pathname.split('/'); 31 | thisvue.qqid = parseInt(pathname[pathname.length - 2]); 32 | axios.all([ 33 | axios.post('../api/', { 34 | action: 'get_user_challenge', 35 | csrf_token: csrf_token, 36 | qqid: thisvue.qqid, 37 | }), 38 | axios.post('../api/', { 39 | action: 'get_member_list', 40 | csrf_token: csrf_token, 41 | }), 42 | ]).then(axios.spread(function (res, memres) { 43 | if (res.data.code != 0) { 44 | thisvue.$alert(res.data.message, '获取记录失败'); 45 | return; 46 | } 47 | if (memres.data.code != 0) { 48 | thisvue.$alert(memres.data.message, '获取成员失败'); 49 | return; 50 | } 51 | thisvue.members = memres.data.members; 52 | thisvue.nickname = res.data.user_info.nickname; 53 | thisvue.refresh(res.data.challenges, res.data.game_server); 54 | thisvue.isLoading = false; 55 | })).catch(function (error) { 56 | thisvue.$alert(error, '获取数据失败'); 57 | }); 58 | }, 59 | methods: { 60 | find_name: function (qqid) { 61 | for (m of this.members) { 62 | if (m.qqid == qqid) { 63 | return m.nickname; 64 | } 65 | }; 66 | return qqid; 67 | }, 68 | csummary: function (cha) { 69 | if (cha == undefined) { 70 | return ''; 71 | } 72 | return `(${cha.cycle}-${cha.boss_num}) ${cha.damage}`; 73 | }, 74 | behalf: function (cha) { 75 | if (cha == undefined) { 76 | return ''; 77 | } 78 | if (cha.behalf) { 79 | return `${this.find_name(cha.behalf)} 代刀`; 80 | } 81 | }, 82 | cdetail: function (cha) { 83 | if (cha == undefined) { 84 | return ''; 85 | } 86 | var nd = new Date(); 87 | nd.setTime(cha.challenge_time * 1000); 88 | var detailstr = nd.toLocaleString('chinese', { 89 | hour12: false, 90 | timeZone: 'asia/shanghai' 91 | }) + '\n'; 92 | detailstr += cha.cycle + '周目' + cha.boss_num + '号boss\n'; 93 | detailstr += (cha.health_remain + cha.damage).toLocaleString(options = { 94 | timeZone: 'asia/shanghai' 95 | }) + '→' + cha.health_remain.toLocaleString(options = { 96 | timeZone: 'asia/shanghai' 97 | }); 98 | if (cha.message) { 99 | detailstr += '\n留言:' + cha.message; 100 | } 101 | return detailstr; 102 | }, 103 | arraySpanMethod: function ({ 104 | row, 105 | column, 106 | rowIndex, 107 | columnIndex 108 | }) { 109 | if (columnIndex >= 2) { 110 | if (columnIndex % 2 == 0) { 111 | var detail = row.detail[columnIndex - 2]; 112 | if (detail != undefined && detail.health_remain != 0) { 113 | return [1, 2]; 114 | } 115 | } else { 116 | var detail = row.detail[columnIndex - 3]; 117 | if (detail != undefined && detail.health_remain != 0) { 118 | return [0, 0]; 119 | } 120 | } 121 | } 122 | }, 123 | refresh: function (challenges, game_server) { 124 | var thisvue = this; 125 | var m = { 126 | pcrdate: -1 127 | }; 128 | for (c of challenges) { 129 | var pcrdate = ts2ds(c.challenge_time - (gs_offset[game_server] * 3600)); 130 | if (m.pcrdate != pcrdate) { 131 | if (m.pcrdate != -1) { 132 | thisvue.challengeData.push(m); 133 | } 134 | m = { 135 | pcrdate: pcrdate, 136 | finished: 0, 137 | detail: [], 138 | } 139 | } 140 | 141 | for (id of this.tempList) { 142 | if (!m.detail[id]) { 143 | if (id % 2 == 1 && c.is_continue && m.detail[id - 1] && m.detail[id - 1].health_remain == 0) { 144 | m.detail[id] = c; 145 | break; 146 | } else if (id % 2 == 0) { 147 | m.detail[id] = c; 148 | break; 149 | } 150 | } 151 | } 152 | 153 | if (c.is_continue) { 154 | m.finished += 0.5; 155 | } else { 156 | if (c.health_remain != 0) { 157 | m.finished += 1; 158 | } else { 159 | m.finished += 0.5; 160 | } 161 | } 162 | } 163 | if (m.pcrdate != -1) { 164 | thisvue.challengeData.push(m); 165 | } 166 | }, 167 | viewInExcel: function () { 168 | var icons = document.getElementsByTagName('span'); 169 | while (icons[0]) { 170 | icons[0].remove(); 171 | } 172 | var uri = 'data:application/vnd.ms-excel;base64,'; 173 | var ctx = '' + document.getElementsByTagName('thead')[0].innerHTML + document.getElementsByTagName('tbody')[0].innerHTML + '
'; 174 | window.location.href = uri + window.btoa(unescape(encodeURIComponent(ctx))); 175 | document.documentElement.innerHTML = '请在Excel中查看(如果无法打开,请安装最新版本Excel)\n或者将整个表格复制,粘贴到Excel中使用'; 176 | }, 177 | handleTitleSelect(key, keyPath) { 178 | switch (key) { 179 | case '1': 180 | window.location = '../'; 181 | break; 182 | case '2': 183 | window.location = '../subscribers/'; 184 | break; 185 | case '3': 186 | window.location = '../progress/'; 187 | break; 188 | case '4': 189 | window.location = '../statistics/'; 190 | break; 191 | case '5': 192 | window.location = `../my/`; 193 | break; 194 | case '6': 195 | window.location = `../clan-rank/`; 196 | break; 197 | } 198 | }, 199 | }, 200 | delimiters: ['[[', ']]'], 201 | }) -------------------------------------------------------------------------------- /src/client/public/static/gacha.js: -------------------------------------------------------------------------------- 1 | var pool = [ 2 | [1071, 1061, 1070, 1804, 1012, 1043, 1057, 1028, 1029, 1011, 1030, 1018, 1032, 1053, 1049, 1009, 1047, 1010, 1063, 1036, 1044, 1037, 1056, 1014, 1092, 1094, 1095, 1096, 1107, 1108, 1113, 1114, 1065, 1117, 1122, 1109, 1110, 1075, 1077, 1078, 1079, 1081, 1083, 1084, 1086, 1087, 1088, 1091, 1097, 1099, 1100, 1103, 1104, 1106, 1111, 1115, 1119, 1120, 1124, 1125, 1127, 1128], 3 | [1045, 1048, 1008, 1006, 1046, 1020, 1033, 1031, 1017, 1042, 1051, 1027, 1007, 1038, 1016, 1026, 1023, 1015, 1054, 1005, 1013], 4 | [1003, 1034, 1040, 1022, 1004, 1052, 1025, 1050, 1001, 1021, 1055], 5 | ]; 6 | var experience = { 7 | "star3": 0, 8 | "star2": 0, 9 | "star1": 0, 10 | "diamond": 0, 11 | }; 12 | var progress = false; 13 | (function () { 14 | var h = localStorage['gacha_experience']; 15 | if (h) { 16 | [experience.star3, experience.star2, experience.star1, experience.diamond] = h.split(',').map(x => +x); 17 | } 18 | })(); 19 | function randarr(arr) { 20 | return String(arr[Math.floor(Math.random() * arr.length)]); 21 | } 22 | function pick(i) { 23 | var x = Math.random(); 24 | if (x < 0.025) { 25 | experience.star3 += 1; 26 | return randarr(pool[0]) + '31'; 27 | } else if (x < 0.205) { 28 | experience.star2 += 1; 29 | return randarr(pool[1]) + '11'; 30 | } else if (i == 9) { 31 | experience.star2 += 1; 32 | return randarr(pool[1]) + '11'; 33 | } 34 | experience.star1 += 1; 35 | return randarr(pool[2]) + '11'; 36 | } 37 | function sleep(time) { 38 | return new Promise((resolve) => setTimeout(resolve, time)); 39 | } 40 | async function reload() { 41 | var container = document.getElementById('container'); 42 | container.innerHTML = ''; 43 | for (i of Array(10).keys()) { 44 | await sleep(200); 45 | let chara = String(pick(i)); 46 | let result = document.createElement('img'); 47 | result.src = sourcebase + chara + '.jpg'; 48 | container.appendChild(result); 49 | if (i === 4) { 50 | container.appendChild(document.createElement('br')); 51 | } 52 | } 53 | experience.diamond += 1500; 54 | } 55 | async function gacha() { 56 | if (progress) { 57 | return; 58 | } 59 | progress = true; 60 | await reload(); 61 | document.getElementById('result').innerHTML = `★3: ${experience.star3}
★2: ${experience.star2}
★1: ${experience.star1}
总耗钻: ${experience.diamond}`; 62 | localStorage['gacha_experience'] = [experience.star3, experience.star2, experience.star1, experience.diamond].join(','); 63 | progress = false; 64 | } -------------------------------------------------------------------------------- /src/client/public/static/gongan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/static/gongan.png -------------------------------------------------------------------------------- /src/client/public/static/marionette.js: -------------------------------------------------------------------------------- 1 | var vm = new Vue({ 2 | el: '#sending', 3 | data: { 4 | message_type: 'group', 5 | user_id: 0, 6 | group_id: 0, 7 | message: '', 8 | }, 9 | mounted() { 10 | if (localStorage.message_type) { 11 | this.message_type = localStorage.message_type; 12 | this.user_id = localStorage.user_id; 13 | this.group_id = localStorage.group_id; 14 | this.message = localStorage.message; 15 | } 16 | }, 17 | watch: { 18 | message_type: function (newmessage_type) { 19 | localStorage.message_type = newmessage_type; 20 | }, 21 | user_id: function (newuser_id) { 22 | localStorage.user_id = newuser_id; 23 | }, 24 | group_id: function (newgroup_id) { 25 | localStorage.group_id = newgroup_id; 26 | }, 27 | message: function (newmessage) { 28 | localStorage.message = newmessage; 29 | }, 30 | }, 31 | methods: { 32 | send_msg: function (event) { 33 | axios.post( 34 | api_path, 35 | this.$data, 36 | ).then(function (res) { 37 | if (res.data.code == 0) { 38 | alert('已发送'); 39 | } else { 40 | alert('发送失败:' + res.data.message); 41 | } 42 | }).catch(function (error) { 43 | alert(error); 44 | }); 45 | }, 46 | }, 47 | delimiters: ['[[', ']]'], 48 | }) -------------------------------------------------------------------------------- /src/client/public/static/small.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/public/static/small.ico -------------------------------------------------------------------------------- /src/client/public/template/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 Not Found 5 | 6 | 7 | 8 | 9 | 30 | 31 | 32 | 33 | 34 |

404: not found

35 |

这个{{ item }}不存在

36 |
37 |
38 |
39 | loading... 41 | loading... 43 |
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /src/client/public/template/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 关于 5 | 6 | 7 | 8 | 9 |

10 | 返回 11 |

12 |
13 |

14 | 关于yobot 15 |

16 |

17 | 版本:
18 | {{ verinfo | replace("\n", "
") }} 19 |

20 |

21 | 主页:
22 | https://yobot.win/ 23 |

24 |

25 | 源码:
26 | yobot 27 |

28 |

29 | 联系邮箱:
30 | yobot@pcrbot.com 31 |

32 |

33 | 交流群:
1群:770947581
2群:1044314369
4群:1067699252
5群:774394459 34 |

35 |

36 | 其他 37 |

38 |

39 | 本项目没有开启赞助、打赏的渠道,所有付款行为与本项目无关。
40 | 如果你付费获取了本工具,可能是第三方收取的服务器费用和托管费用。 41 |

42 |
43 |
44 |

45 | 关于YoCool 46 |

47 |

48 | :版本
49 | YoCool-v1.1.1-PrincessAdventure 50 |

51 |

52 | :源码
53 | YoCool 55 |

56 |

57 | :交流群
1143518690 58 |

59 |

60 | :更新日志
点我查看 61 |

62 |

63 | :了解更多
YoCool WIKI 64 |

65 |

66 | PcrLink推广 67 |

68 |

69 | 如需了解更多,请添加 QQ:1575547452
70 |

71 | 72 |
73 | 74 | -------------------------------------------------------------------------------- /src/client/public/template/admin/groups.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | yobot公会管理 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 28 | 29 | 30 |
31 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/client/public/template/admin/users.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | yobot用户管理 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 搜索 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 加载更多 69 | 已加载全部 70 | 71 |
72 | 73 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/client/public/template/clan/clan-rank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 会战排名查询 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 | 排名 40 | 我的 41 | 统计 42 | 查刀 43 | 预约 44 | 面板 45 | 46 |
47 | 48 | 49 | 50 |
51 | 52 | 53 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/client/public/template/clan/setting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 公会战设置 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 | 排名 30 | 我的 31 | 统计 32 | 查刀 33 | 预约 34 | 面板 35 | 36 |
37 |

设置

38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 出刀表无需登录 49 | 允许api获取数据 50 | 51 | 52 | 伤害上报 53 | 撤销上报 54 | 申请出刀 55 | 取消申请 56 | 预约boss 57 | 取消预约 58 | 挂树 59 | 取消挂树 60 | 修改状态 61 | 使用SL 62 | 63 | 64 | 65 | 66 | 确定 67 | 返回 68 | 69 | 70 | 现在档案编号:[[ battle_id ]]
71 | 导出数据 72 | {#- 新建档案 73 | -#} 74 | 切换档案 76 | 删除数据 77 | 78 | 79 | 80 | 81 | 82 |
    83 |
  • 84 | [[ item.battle_id ]]号存档:[[ item.record_count ]]条记录 85 |
  • 86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 取消 94 | 切换 95 | 96 |
97 | 98 |

此操作会删除 [[ battle_id ]] 号存档中所有数据

99 | 100 | 取消 101 | 确定 102 | 103 |
104 |
105 |
106 |
107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/client/public/template/clan/statistics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 数据分析 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 106 | 107 | 108 | 109 |
110 |
111 | 112 | 113 | 114 | 排名 115 | 我的 116 | 统计 117 | 查刀 118 | 预约 119 | 面板 120 | 121 |
122 | 123 |
124 |
125 | 126 |
127 | 128 |
129 | 130 |
131 |

出刀顺序

132 |
成员出刀顺序记录
133 | 立即查看 134 |
135 |
136 |
137 | 138 |
139 | 140 |
141 |

数据图表

142 |
多种数据图表分析
143 | 立即查看 144 |
145 |
146 | 147 | 148 |
149 | 150 |
151 |

均值偏差

152 |
公会战偏差值计算
153 | 立即查看 154 |
155 | 156 | 157 |
158 | 159 |
160 |

多维分析

161 |
会战成员数据分析
162 | 立即查看 163 |
164 |
165 |
166 |
167 |
168 |

原始数据


169 | 171 | 172 | 173 | 174 |


175 |
176 | {% if allow_api -%} 177 | 178 | 179 | {% else -%} 180 | api访问已禁用,如需开启请前往 181 | 公会设置 182 | 183 | {% endif -%} 184 |
185 | 186 | 187 | 188 | 201 | 202 | 203 | 222 | -------------------------------------------------------------------------------- /src/client/public/template/clan/statistics/statistics1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 总统计 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 31 | 32 | 33 |
34 | 35 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/client/public/template/clan/subscribers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 公会战预约 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 | 排名 27 | 我的 28 | 统计 29 | 查刀 30 | 预约 31 | 面板 32 | 33 |
34 |

预约名单

35 | 36 | 37 | 38 | [[ bosstag[bn-1] ]] 39 | 40 |

41 | [[ find_name(m.qqid) ]]([[ m.qqid ]]) 42 | 45 |

46 |

47 | 没有记录 48 |

49 |
50 |
51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/client/public/template/clan/unauthorized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 权限不足 5 | 6 | 7 | 8 | 9 | 30 | 31 | 32 | 33 | 34 |

权限不足

35 |

这个公会的信息是私密的,只有其成员可以查看

36 |
37 |
38 |
39 | loading... 40 | loading... 41 |
42 |
43 |
44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/client/public/template/clan/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 个人出刀记录 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 28 | 29 | 30 | 31 |
32 |
33 | 34 | 35 | 36 | 排名 37 | 我的 38 | 统计 39 | 查刀 40 | 预约 41 | 面板 42 | 43 |
44 |

[[ nickname ]]的出刀记录

45 | 112 | 115 | 查看用户:[[ nickname ]] 116 |
117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/client/public/template/homepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | YoCool Link Start 6 | 7 | 8 | 9 | 10 | 11 | 12 | 36 | 37 | 38 | 39 |
40 |
41 |

Hi!YoCool!

42 |
43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |

56 |

—yobot is running—

57 | 66 | 68 | {% if show_icp -%} 69 | {{ icp_info }} 70 |   71 | {% if gongan_info -%} 72 | 74 | 75 | {{ gongan_info }} 76 | 77 |   78 | {% endif -%} 79 | {% endif -%} 80 | 81 | 82 | 85 | 86 | -------------------------------------------------------------------------------- /src/client/public/template/login-code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | yobot登录 5 | 6 | 7 | 8 |

正在登录中……

9 | 17 | 18 | -------------------------------------------------------------------------------- /src/client/public/template/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 会战系统登录 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 32 | 33 | 34 | 35 |
36 |
37 |

会战系统登录

38 |
39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |
54 | {% if reason -%} 55 | 56 | 57 | {% endif -%} 58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 登录 70 | 71 | 72 |
73 |
74 | 75 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /src/client/public/template/manual.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 使用手册 5 | 6 | 7 | 8 | 27 | 28 | 29 | 30 |

公会战功能使用手册

31 | 32 |

使用 Bot 管理公会战,需要所有成员遵守使用规则
Bot 只是辅助作用,与成员多沟通才能提高分数

33 | 34 |

具体指令请以查看帮助页面

35 | 36 |

开始前

37 | 38 |
    39 |
  1. 机器人管理员进入后台设置,确认设置中的 boss 生命值符合当期公会战
  2. 40 |
  3. 在群聊中发送创建日服公会(日、韩、台、国)
  4. 41 |
  5. 所有公会战成员在群聊中发送加入公会,或者由群管理员发送加入全部成员
  6. 42 |
  7. 成员向 bot 私聊发送登录重置密码,进入后台确认,同时修改登录密码
  8. 43 |
  9. 成员进入公会战面板,并将网页地址保存到桌面快捷方式(手机、电脑均可),以便使用网页报刀和查看数据
  10. 44 |
  11. 管理员在公会设置中,新建一个空白档案用来存放公会战数据
  12. 45 |
46 | 47 |

进行中:成员

48 | 49 | 59 | 60 |

进行中:管理员

61 | 62 | 67 | 68 |

特殊情况

69 | 70 | 75 | 76 |

查看数据

77 | 78 |

公会战面板“查刀”页面中能查看记录的所有数据,并进行快速的筛选、过滤。如果需要原始数据,可以从“统计”页面导出。

79 | 80 | 81 | -------------------------------------------------------------------------------- /src/client/public/template/marionette.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 人偶模式 6 | 7 | 8 | 9 | 10 | 11 |
12 |

操纵人偶

13 | 发送: 14 | 18 |
19 | QQ号: 20 |
21 |
22 | 群号: 23 |
24 | 消息: 25 | 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/client/public/template/password.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 修改密码 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 52 | 53 | 54 | 55 |
56 | 57 |
58 | {% if error -%} 59 | 62 | 63 | {% endif -%} 64 | {% if success -%} 65 | 68 | 69 | {% endif -%} 70 | 71 | 72 |
73 |

修改密码

74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 修改 83 | 84 | 85 |
86 |
87 | 88 | 153 | 154 | -------------------------------------------------------------------------------- /src/client/public/template/unauthorized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | yobot权限不足 6 | 7 | 8 | 9 |

权限不足

10 |

浏览这个页面需要权限:{{ limit }}

11 |

你的权限:{{ uath }}

12 |

13 | 返回 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/client/public/template/user-info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 个人中心 6 | 7 | 8 | 9 | 10 | 32 | 33 | 34 | 35 | 36 |
37 | 38 | 62 | 67 |
68 | 69 | 132 | 133 | -------------------------------------------------------------------------------- /src/client/public/template/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 公主连结公会战面板 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 52 | 53 | 54 | 55 |
56 |
57 |

会战管理面板

58 |
59 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |

72 |
73 | 74 |

欢迎,{{ user.nickname }}

75 | {% if user.authority_group < 10 -%} 76 | 77 | 78 | 设置项 79 | 80 | 81 | 用户管理 82 | 83 | 84 | 群管理 85 | 86 | 87 | {%- endif %} 88 | 89 | {% if not clan_groups -%} 90 | 91 |
你还没有加入公会
请在你的公会群内发送“加入公会”来加入一个公会
92 | 无公会 93 |
94 | {%- else -%} 95 | {% for group in clan_groups -%} 96 | 97 | 公会:{{ group['group_name'] }} 98 | 99 |

100 | {% endfor -%} 101 | {%- endif %} 102 |
103 |
104 | 105 | 106 | 137 | 138 | -------------------------------------------------------------------------------- /src/client/requirements.txt: -------------------------------------------------------------------------------- 1 | aiocqhttp==1.4.3 2 | aiohttp==3.7.4 3 | APScheduler==3.7.0 4 | expiringdict==1.2.1 5 | Jinja2==3.1.2 6 | nonebot==1.9.1 7 | peewee==3.14.1 8 | Pillow==9.4.0 9 | quart==0.18.3 10 | requests==2.25.1 11 | tzlocal==2.1 12 | werkzeug>=2.2.0,<3.0.0 13 | -------------------------------------------------------------------------------- /src/client/ybplugins/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | 'ybdata', 3 | 'web_util', 4 | 'templating', 5 | 'switcher', 6 | 'yobot_msg', 7 | 'homepage', 8 | 'clan_battle', 9 | 'login', 10 | 'marionette', 11 | 'settings', 12 | 'custom', 13 | ] 14 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/__init__.py: -------------------------------------------------------------------------------- 1 | from .battle import ClanBattle 2 | 3 | __all__ = [ 4 | 'ClanBattle', 5 | ] 6 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/battle.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Any, Dict 3 | from aiocqhttp.api import Api 4 | 5 | from .components.web_operation import register_routes 6 | from .components.kernel import init, execute, jobs, match 7 | from .components.score import score_table 8 | from .components.realize import * 9 | from .components.realize import (_level_by_cycle, _get_nickname_by_qqid, 10 | _get_group_previous_challenge, _update_group_list_async, 11 | _fetch_member_list_async, _update_all_group_members_async, 12 | _update_user_nickname_async, _boss_data_dict, _get_available_empty_battle_id, 13 | _update_user_profile_image) 14 | 15 | 16 | class ClanBattle: 17 | Passive = True 18 | Active = True 19 | Request = True 20 | 21 | #### 核心 22 | init = init #初始化 23 | execute = execute #执行 24 | jobs = jobs #验证 25 | match = match #匹配 26 | #### 核心 27 | 28 | #构造函数/初始化 29 | def __init__(self, glo_setting:Dict[str, Any], bot_api:Api, boss_id_name:Dict, *args, **kwargs): 30 | # data initialize 31 | self._boss_status:Dict[str, asyncio.Future] = {} 32 | self.init(glo_setting, bot_api, boss_id_name, args, kwargs) 33 | 34 | 35 | register_routes = register_routes #网页端操作 36 | 37 | score_table = score_table #业绩 38 | text_2_pic = text_2_pic #文字转图片 39 | 40 | _level_by_cycle = _level_by_cycle ##等级周目 41 | _get_nickname_by_qqid = _get_nickname_by_qqid ##通过qq号获取成员名字 42 | _get_group_previous_challenge = _get_group_previous_challenge ##获取上一个出刀记录 43 | _update_group_list_async = _update_group_list_async ##更新群列表 44 | _fetch_member_list_async = _fetch_member_list_async ##获取群成员列表 45 | _update_all_group_members_async = _update_all_group_members_async ##更新所有群成员 46 | _update_user_nickname_async = _update_user_nickname_async ##更新成员名字 47 | _boss_data_dict = _boss_data_dict ##获取boss当前数据 48 | _get_available_empty_battle_id = _get_available_empty_battle_id ##获取公会最靠前的空白档案号 49 | _update_user_profile_image = _update_user_profile_image ##刷新用户头像 50 | 51 | create_group = create_group ##创建公会 52 | bind_group = bind_group ##加入公会 53 | drop_member = drop_member ##删除成员 54 | boss_status_summary = boss_status_summary ##当前的boss状态 55 | challenge = challenge ##报刀 56 | undo = undo ##撤销上一刀的伤害/删除上一刀的记录 57 | modify = modify ##修改boss状态 58 | change_game_server = change_game_server ##修改服务器 59 | get_data_slot_record_count = get_data_slot_record_count ##获取当期会战数据记录档案的编号 60 | clear_data_slot = clear_data_slot ##清空会战数据记录档案 61 | switch_data_slot = switch_data_slot ##切换会战数据记录档案 62 | send_private_remind = send_private_remind ##向个人私聊发送出刀提醒 63 | behelf_remind = behelf_remind ##代刀提醒 64 | send_remind = send_remind ##发送出刀提醒 65 | 66 | apply_for_challenge = apply_for_challenge ##申请出刀 67 | cancel_blade = cancel_blade ##取消申请出刀 68 | save_slot = save_slot ##SL 69 | report_hurt = report_hurt ##报伤害/记录伤害 70 | challenger_info = challenger_info ##当前出刀信息 71 | challenger_info_small = challenger_info_small ##单个boss出刀信息 72 | check_blade = check_blade ##检查是否已申请出刀 73 | put_on_the_tree = put_on_the_tree ##挂树 74 | check_tree = check_tree ##检查此用户在不在树上 75 | take_it_of_the_tree = take_it_of_the_tree ##下树 76 | get_in_boss_num = get_in_boss_num ##获取boss_num 77 | subscribe = subscribe ##预约 78 | subscribe_cancel = subscribe_cancel ##取消预约 79 | get_subscribe_list = get_subscribe_list ##获取预约列表 80 | challenge_record = challenge_record ##出刀记录 81 | 82 | get_report = get_report ##获取报告 83 | get_battle_member_list = get_battle_member_list ##从会战记录里获取成员列表 84 | get_member_list = get_member_list ##获取所有成员列表 85 | 86 | query_tree = query_tree ##查树 87 | get_clan_group = get_clan_group -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/components/define.py: -------------------------------------------------------------------------------- 1 | Commands = { 2 | '创建': 1, 3 | '加入': 2, 4 | '状态': 3, 5 | '报刀': 4, 6 | '刀1': 4, 7 | '刀2': 4, 8 | '刀3': 4, 9 | '刀4': 4, 10 | '刀5': 4, 11 | '尾刀': 5, 12 | '尾1': 5, 13 | '尾2': 5, 14 | '尾3': 5, 15 | '尾4': 5, 16 | '尾5': 5, 17 | '撤销': 6, 18 | '预约': 7, 19 | '业绩': 8, 20 | '出刀':9, 21 | '挂树': 11, 22 | '申请': 12, 23 | '进1': 12, 24 | '进2': 12, 25 | '进3': 12, 26 | '进4': 12, 27 | '进5': 12, 28 | '取消': 13, 29 | '不打': 14, 30 | '不进': 14, 31 | '面板': 15, 32 | '查刀': 15, 33 | '后台': 15, 34 | 'sl': 16, 35 | 'SL': 16, 36 | '报伤':17, 37 | '打了':17, 38 | '权限':18, 39 | '重置':20, 40 | '刷新':21, 41 | '查树': 30, 42 | '查1': 30, 43 | '查2': 30, 44 | '查3': 30, 45 | '查4': 30, 46 | '查5': 30, 47 | } 48 | 49 | Server = { 50 | '日': 'jp', 51 | '台': 'tw', 52 | '韩': 'kr', 53 | '国': 'cn', 54 | } 55 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/components/fonts/msyh.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggggi/yobot_remix/2238675b0d8fc66b9556bac2a674d76c69fec46e/src/client/ybplugins/clan_battle/components/fonts/msyh.ttf -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/components/handler.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | import json 3 | 4 | from ...ybdata import Clan_group 5 | 6 | 7 | class SubscribeHandler: 8 | def __init__(self, group: Clan_group) -> None: 9 | """ 10 | 预约系统处理核心 11 | 12 | :param group: Clan_group公会实例 13 | """ 14 | """ 15 | 转换为方便处理内部的类型 16 | 原始类型: Dict[str, Dict[str, str]] = {Boss编号: {预约QQ号: 留言}} 17 | 目标类型: Dict[int, Dict[int, str]] = {Boss编号: {预约QQ号: 留言}} 18 | """ 19 | if group.subscribe_list: 20 | data: Dict[str, Dict[str, str]] = json.loads(group.subscribe_list) 21 | else: 22 | data = {} 23 | new_data: Dict[int, Dict[int, str]] = {} 24 | for boss_no, boss_subscribe_data in data.items(): 25 | new_boss_subscribe_data = {} 26 | for subscribe_qq, subscribe_note in boss_subscribe_data.items(): 27 | new_boss_subscribe_data[int(subscribe_qq)] = subscribe_note 28 | new_data[int(boss_no)] = new_boss_subscribe_data 29 | self._data: Dict[int, Dict[int, str]] = new_data 30 | self._clan_group: Clan_group = group 31 | 32 | def subscribe(self, user_id: int, boss_id: int, note: str = "") -> None: 33 | """ 34 | 预约Boss 35 | 36 | :param user_id: QQ号 37 | :param boss_id: Boss编号 38 | :param note: 留言 39 | """ 40 | if boss_id not in self._data: 41 | self._data[boss_id] = {} 42 | self._data[boss_id][user_id] = note 43 | 44 | def is_subscribed(self, user_id: int, boss_id: int) -> bool: 45 | """ 46 | 检查是否已预约过特定Boss 47 | 48 | :param user_id: QQ号 49 | :param boss_id: Boss编号 50 | :return: 是否预约了该Boss 51 | """ 52 | if boss_id not in self._data: 53 | return False 54 | return user_id in self._data[boss_id] 55 | 56 | def unsubscribe(self, user_id: int, boss_id: int) -> None: 57 | self._data[boss_id].pop(user_id) 58 | if not self._data[boss_id]: # 删除没有预约的Boss 59 | self._data.pop(boss_id) 60 | 61 | def unsubscribe_all(self, boss_id: int) -> None: 62 | """ 63 | 取消某个Boss的所有预约 64 | 65 | :param boss_id: Boss编号 66 | """ 67 | if boss_id not in self._data: 68 | return 69 | self._data.pop(boss_id) 70 | 71 | def get_subscribe_list(self, boss_id: int) -> List[int]: 72 | """ 73 | 获取预约Boss的用户列表 74 | 75 | :param boss_id: Boss编号 76 | :return: 预约Boss的用户列表 77 | """ 78 | if boss_id not in self._data: 79 | return [] 80 | return list(self._data[boss_id].keys()) 81 | 82 | def get_note(self, user_id: int, boss_id: int) -> str: 83 | if not self.is_subscribed(user_id, boss_id): 84 | return "" 85 | return self._data[boss_id][user_id] 86 | 87 | @property 88 | def have_subscribe(self) -> bool: 89 | """ 90 | 是否包含任何预约记录 91 | 92 | :return: 预约记录查询结果 93 | """ 94 | return True if self._data else False 95 | 96 | @property 97 | def data(self) -> Dict[int, Dict[int, str]]: 98 | """ 99 | 获取预约数据 100 | 101 | :return: 预约数据 102 | """ 103 | return dict(sorted(self._data.items(), key=lambda i: i[0])) 104 | 105 | def save(self) -> None: 106 | self._clan_group.subscribe_list = json.dumps(self._data) 107 | self._clan_group.save() 108 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/components/multi_cq_utils.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | from pathlib import Path 3 | import os 4 | import sys 5 | 6 | ginipath = Path.cwd().resolve().joinpath("./yobot_data/groups.ini") if "_MEIPASS" in dir(sys) else Path(os.path.dirname(__file__)).parents[2] / 'yobot_data' / 'groups.ini' 7 | config = configparser.ConfigParser() 8 | config.read(str(ginipath), encoding='utf-8') 9 | 10 | 11 | def who_am_i(GID): 12 | '''Gimme a GID(int), return you a selfID(int) :)''' 13 | # config = configparser.ConfigParser() 14 | # config.read(str(ginipath)) 15 | global config 16 | sid = config.get('GROUPS', str(GID)) 17 | return int(sid) 18 | 19 | 20 | def refresh(): 21 | global config 22 | config.read(str(ginipath), encoding='utf-8') 23 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/components/score.py: -------------------------------------------------------------------------------- 1 | import os 2 | import string 3 | 4 | from ..exception import GroupNotExist 5 | from ...ybdata import Clan_challenge, Clan_group, Clan_member 6 | 7 | 8 | FILE_PATH = os.path.dirname(__file__) 9 | 10 | def is_Chinese(word): 11 | for ch in word: 12 | if '\u4e00' <= ch <= '\u9fff': return True 13 | 14 | #业绩表 15 | def score_table(self, group_id): 16 | ''' 17 | 通过当期数据给成员打分 18 | ''' 19 | group:Clan_group = self.get_clan_group(group_id=group_id) 20 | if group is None:raise GroupNotExist 21 | 22 | members = Clan_member.select().where( 23 | Clan_member.group_id == group_id, 24 | ) 25 | 26 | member_score_dict = {} 27 | for member in members: 28 | challenges = Clan_challenge.select().where( 29 | Clan_challenge.gid == group_id, 30 | Clan_challenge.bid == group.battle_id, 31 | Clan_challenge.qqid == member.qqid 32 | ).order_by(Clan_challenge.challenge_pcrdate) 33 | challenges = list(challenges) 34 | if member.qqid not in member_score_dict: 35 | member_score_dict[member.qqid] = { 36 | 'score' : 0, 37 | 'full_blade' : 0, 38 | 'end_blade' : 0, 39 | 'small_end_blade' : 0, 40 | } 41 | 42 | full_blade = sum(bool(c.boss_health_remain and not c.is_continue) for c in challenges) 43 | for info in challenges: 44 | score, full_blade, end_blade, small_end_blade = 0, 0, 0, 0 45 | if info.boss_health_remain > 0 and not info.is_continue: 46 | full_blade += 1 47 | score += 1 48 | elif info.boss_health_remain == 0 and not info.is_continue: 49 | end_blade += 1 50 | if info.challenge_damage >= group.threshold: score += 1 51 | else: score += 0.5 52 | elif info.is_continue: 53 | small_end_blade += 1 54 | if info.challenge_damage >= group.threshold: score += 1 55 | else: score += 0.5 56 | score_member = info.behalf and info.behalf or member.qqid 57 | if score_member not in member_score_dict: 58 | member_score_dict[score_member] = { 59 | 'score' : score, 60 | 'full_blade' : full_blade, 61 | 'end_blade' : end_blade, 62 | 'small_end_blade' : small_end_blade, 63 | } 64 | else: 65 | member_score_dict[score_member]['score'] += score 66 | member_score_dict[score_member]['full_blade'] += full_blade 67 | member_score_dict[score_member]['end_blade'] += end_blade 68 | member_score_dict[score_member]['small_end_blade'] += small_end_blade 69 | 70 | member_score_dict = dict(sorted(member_score_dict.items(), key=lambda item: item[1]['score'], reverse=True)) 71 | back_msg = [] 72 | for qqid, info in member_score_dict.items(): 73 | name:string = list(self._get_nickname_by_qqid(qqid)) 74 | while len(name) > 5:name.pop() 75 | a = '' 76 | if len(name) < 5: 77 | for i in range(5 - len(name)): a += ' ' 78 | name.append(a) 79 | a = '' 80 | for i in name: 81 | if not is_Chinese(i): a += ' ' 82 | back_msg.append(f"{''.join(name)}{a} \ 83 | 分数:{info['score']} \ 84 | 整刀:{info['full_blade']} \ 85 | 尾刀:{info['end_blade']} \ 86 | 小尾刀:{info['small_end_blade']}") 87 | 88 | return self.text_2_pic('\n'.join(back_msg), 450, len(back_msg)*20 + 10, (255, 255, 255), "#000000", 15, (10, 5)) 89 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/exception.py: -------------------------------------------------------------------------------- 1 | class ClanBattleError(ValueError):... 2 | class UserError(ClanBattleError):... 3 | class GroupError(ClanBattleError):... 4 | class InputError(ClanBattleError):... 5 | class UserNotInGroup(UserError): 6 | def __init__(self, msg='未加入公会,请先发送“加入公会”', *args): 7 | super().__init__(msg, *args) 8 | class GroupNotExist(GroupError): 9 | def __init__(self, msg='本群未初始化,请发送“创建X服公会”', *args): 10 | super().__init__(msg, *args) 11 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/typing.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any, Dict, List, NewType, Optional 3 | 4 | Pcr_date = NewType('Pcr_date', int) 5 | Pcr_time = NewType('Pcr_time', int) 6 | QQid = NewType('QQid', int) 7 | Groupid = NewType('Groupid', int) 8 | 9 | 10 | 11 | @dataclass 12 | class BossChallenge: 13 | date: Pcr_date 14 | time: Pcr_time 15 | cycle: int 16 | num: int 17 | health_remain: int 18 | damage: int 19 | is_continue: bool 20 | team: Optional[List[int]] 21 | message: Optional[str] 22 | 23 | 24 | ClanBattleReport = NewType( 25 | 'ClanBattleReport', 26 | List[Dict[str, Any]] 27 | ) 28 | -------------------------------------------------------------------------------- /src/client/ybplugins/clan_battle/util.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | from functools import lru_cache 4 | from typing import Tuple, Union 5 | 6 | from expiringdict import ExpiringDict 7 | 8 | from .typing import Pcr_date, Pcr_time 9 | 10 | pcr_time_offset = { 11 | "jp": 4, 12 | "tw": 3, 13 | "kr": 4, 14 | "cn": 3, 15 | } 16 | 17 | 18 | @lru_cache(4) 19 | def pcr_tzinfo(area): 20 | return datetime.timezone(datetime.timedelta(hours=pcr_time_offset[area])) 21 | 22 | 23 | def pcr_datetime(area, dt: Union[int, datetime.datetime, None] = None) -> Tuple[Pcr_date, Pcr_time]: 24 | if dt is None: 25 | ts = int(time.time()) 26 | elif isinstance(dt, int): 27 | ts = dt 28 | elif isinstance(dt, datetime.datetime): 29 | ts = dt.timestamp() 30 | else: 31 | raise ValueError(f'cannot parse {type(dt)} to pcrdatetime') 32 | ts += pcr_time_offset[area]*3600 33 | return divmod(ts, 86400) 34 | 35 | 36 | def pcr_timestamp(d: Pcr_date, t: Pcr_time, area) -> int: 37 | return 86400*d + t - (pcr_time_offset[area]*3600) 38 | 39 | 40 | def atqq(qqid): 41 | return '[CQ:at,qq={}]'.format(qqid) 42 | 43 | 44 | def timed_cached_func(max_len, max_age_seconds, ignore_self=False): 45 | cache = ExpiringDict(max_len, max_age_seconds) 46 | 47 | def decorator(fn): 48 | def wrapper(*args, nocache=False): # args must be hashable 49 | if ignore_self: 50 | key = tuple(args[1:]) 51 | else: 52 | key = tuple(args) 53 | value = cache.get(key) 54 | if nocache or value is None: 55 | value = fn(*args) 56 | cache[key] = value 57 | return value 58 | return wrapper 59 | return decorator 60 | -------------------------------------------------------------------------------- /src/client/ybplugins/custom.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 自定义功能: 3 | 4 | 在这里可以编写自定义的功能, 5 | 编写完毕后记得 git commit, 6 | 7 | 这个模块只是为了快速编写小功能,如果想编写完整插件可以使用: 8 | https://github.com/richardchien/python-aiocqhttp 9 | 或者 10 | https://github.com/richardchien/nonebot 11 | 12 | 关于PR: 13 | 如果基于此文件的PR,请在此目录下新建一个`.py`文件,并修改类名 14 | 然后在`yobot.py`中添加`import`(这一步可以交给仓库管理者做) 15 | ''' 16 | 17 | import asyncio 18 | from typing import Any, Dict, Union 19 | 20 | from aiocqhttp.api import Api 21 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 22 | from quart import Quart 23 | 24 | from pathlib import Path 25 | import os 26 | import sys 27 | import configparser 28 | from .clan_battle.components.multi_cq_utils import refresh 29 | 30 | 31 | class Custom: 32 | def __init__(self, 33 | glo_setting: Dict[str, Any], 34 | scheduler: AsyncIOScheduler, 35 | app: Quart, 36 | bot_api: Api, 37 | *args, **kwargs): 38 | ''' 39 | 初始化,只在启动时执行一次 40 | 41 | 参数: 42 | glo_setting 包含所有设置项,具体见default_config.json 43 | bot_api 是调用机器人API的接口,具体见 44 | scheduler 是与机器人一同启动的AsyncIOScheduler实例 45 | app 是机器人后台Quart服务器实例 46 | ''' 47 | # 注意:这个类加载时,asyncio事件循环尚未启动,且bot_api没有连接 48 | # 此时不要调用bot_api 49 | # 此时没有running_loop,不要直接使用await,请使用asyncio.ensure_future并指定loop=asyncio.get_event_loop() 50 | 51 | # 如果需要启用,请注释掉下面一行 52 | # return 53 | 54 | # 这是来自yobot_config.json的设置,如果需要增加设置项,请修改default_config.json文件 55 | self.setting = glo_setting 56 | 57 | # 这是cqhttp的api,详见cqhttp文档 58 | self.api = bot_api 59 | 60 | # # 注册定时任务,详见apscheduler文档 61 | # @scheduler.scheduled_job('cron', hour=8) 62 | # async def good_morning(): 63 | # await self.api.send_group_msg(group_id=123456, message='早上好') 64 | 65 | # # 注册web路由,详见flask与quart文档 66 | # @app.route('/is-bot-running', methods=['GET']) 67 | # async def check_bot(): 68 | # return 'yes, bot is running' 69 | self.inipath = Path.cwd().resolve().joinpath("./yobot_data/groups.ini") if "_MEIPASS" in dir(sys) else Path(os.path.dirname(__file__)).parent / 'yobot_data' / 'groups.ini' 70 | 71 | async def execute_async(self, ctx: Dict[str, Any]) -> Union[None, bool, str]: 72 | ''' 73 | 每次bot接收有效消息时触发 74 | 75 | 参数ctx 具体格式见:https://cqhttp.cc/docs/#/Post 76 | ''' 77 | # 注意:这是一个异步函数,禁止使用阻塞操作(比如requests) 78 | 79 | # 如果需要使用,请注释掉下面一行 80 | 81 | # 多CQ适配:触发写入ini,群号=bot号 82 | cmd = ctx['raw_message'] 83 | if cmd == '手动添加群记录' or cmd == '修复网页催刀': 84 | # print(ctx) 85 | config=configparser.RawConfigParser() 86 | config.read(str(self.inipath)) 87 | config.set('GROUPS', str(ctx['group_id']), str(ctx['self_id'])) 88 | with open(str(self.inipath),'w') as f: 89 | config.write(f) 90 | refresh() 91 | return '群记录添加成功!' 92 | 93 | return 94 | 95 | cmd = ctx['raw_message'] 96 | if cmd == '你好': 97 | 98 | # 调用api发送消息,详见cqhttp文档 99 | await self.api.send_private_msg( 100 | user_id=123456, message='收到问好') 101 | 102 | # 返回字符串:发送消息并阻止后续插件 103 | return '世界' 104 | 105 | # 返回布尔值:是否阻止后续插件(返回None视作False) 106 | return False 107 | -------------------------------------------------------------------------------- /src/client/ybplugins/group_leave.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Union 2 | 3 | from aiocqhttp.api import Api 4 | from .web_util import rand_string 5 | 6 | 7 | class GroupLeave: 8 | def __init__(self, 9 | glo_setting: Dict[str, Any], 10 | bot_api: Api, 11 | *args, **kwargs): 12 | self.setting = glo_setting 13 | self.api = bot_api 14 | self.verification = {} 15 | 16 | async def execute_async(self, ctx: Dict[str, Any]): 17 | cmd = ctx['raw_message'] 18 | if cmd.startswith('退出此群'): 19 | if ctx['message_type'] != 'group': 20 | return '此功能仅可用于群聊' 21 | if ctx['sender']['role'] == 'member': 22 | return '只有群管理员可以这么做' 23 | code = cmd[4:] 24 | if code == self.verification.get(ctx['group_id']): 25 | await self.api.send_group_msg( 26 | group_id=ctx['group_id'], 27 | message='正在退群', 28 | ) 29 | await self.api.set_group_leave( 30 | group_id=ctx['group_id'], 31 | is_dismiss=False, 32 | ) 33 | else: 34 | code = rand_string(4) 35 | self.verification[ctx['group_id']] = code 36 | return f'警告:如果你确定要执行退群,请发送“退出此群{code}”' 37 | -------------------------------------------------------------------------------- /src/client/ybplugins/homepage.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urljoin 2 | 3 | from quart import Quart, send_from_directory 4 | 5 | from .templating import render_template, static_folder, template_folder 6 | 7 | 8 | class Index: 9 | Passive = False 10 | Active = False 11 | Request = True 12 | 13 | def __init__(self, glo_setting, *args, **kwargs): 14 | self.setting = glo_setting 15 | self.public_basepath = glo_setting["public_basepath"] 16 | 17 | def register_routes(self, app: Quart): 18 | 19 | @app.route(self.public_basepath, methods=["GET"]) 20 | async def yobot_homepage(): 21 | return await render_template( 22 | "homepage.html", 23 | verinfo=self.setting["verinfo"]["ver_name"], 24 | show_icp=self.setting["show_icp"], 25 | icp_info=self.setting["icp_info"], 26 | gongan_info=self.setting["gongan_info"], 27 | ) 28 | 29 | @app.route( 30 | urljoin(self.public_basepath, 'about/'), 31 | methods=['GET']) 32 | async def yobot_about(): 33 | return await render_template( 34 | "about.html", 35 | verinfo=self.setting["verinfo"]["ver_name"], 36 | ) 37 | 38 | @app.route("/favicon.ico", methods=["GET"]) 39 | async def yobot_favicon(): 40 | return await send_from_directory(static_folder, "small.ico") 41 | 42 | @app.route( 43 | urljoin(self.public_basepath, 'help/'), 44 | methods=['GET']) 45 | async def yobot_help(): 46 | return await send_from_directory(template_folder, "help.html") 47 | 48 | @app.route( 49 | urljoin(self.public_basepath, 'manual/'), 50 | methods=['GET']) 51 | async def yobot_manual(): 52 | return await send_from_directory(template_folder, "manual.html") 53 | -------------------------------------------------------------------------------- /src/client/ybplugins/marionette.py: -------------------------------------------------------------------------------- 1 | """ 2 | 后台模式的实验性功能,验证后台交互的可行性 3 | """ 4 | import time 5 | from urllib.parse import urljoin 6 | 7 | from aiocqhttp.api import Api 8 | from quart import Quart, jsonify, make_response, request 9 | 10 | from .templating import render_template 11 | from .web_util import rand_string 12 | from .ybdata import Admin_key 13 | 14 | 15 | class Marionette: 16 | Passive = True 17 | Active = False 18 | Request = True 19 | 20 | def __init__(self, 21 | glo_setting, 22 | bot_api: Api, 23 | *args, **kwargs): 24 | self.setting = glo_setting 25 | self.api = bot_api 26 | 27 | def _gen_key(self): 28 | newkey = rand_string(6) 29 | Admin_key.create( 30 | key=newkey, 31 | valid=True, 32 | key_used=False, 33 | cookie=rand_string(32), 34 | create_time=int(time.time()), 35 | ) 36 | newurl = urljoin( 37 | self.setting['public_address'], 38 | '{}marionette/?key={}'.format(self.setting['public_basepath'], newkey)) 39 | return newurl 40 | 41 | @staticmethod 42 | def match(cmd: str): 43 | if cmd == '人偶': 44 | return 1 45 | return 0 46 | 47 | def execute(self, match_num: int, ctx: dict) -> dict: 48 | if ctx['user_id'] not in self.setting['super-admin']: 49 | return { 50 | 'reply': '只有主人可以使用这个功能', 51 | 'block': True 52 | } 53 | if ctx['message_type'] != 'private': 54 | return { 55 | 'reply': '请私聊使用', 56 | 'block': True 57 | } 58 | newurl = self._gen_key() 59 | reply = '点击链接开始使用我:'+newurl 60 | if self.setting['web_mode_hint']: 61 | reply += '\n\n如果无法打开,请仔细阅读教程中《链接无法打开》的说明' 62 | return { 63 | 'reply': reply, 64 | 'block': True 65 | } 66 | 67 | def register_routes(self, app: Quart): 68 | 69 | @app.route( 70 | urljoin(self.setting['public_basepath'], 'marionette/'), 71 | methods=['GET']) 72 | async def yobot_marionette(): 73 | new_cookie = None 74 | key_used = False 75 | key = request.args.get('key') 76 | if key is not None: 77 | user = Admin_key.get_or_none(Admin_key.key == key) 78 | if user is None: 79 | return '403 Forbidden', 403 80 | if user.key_used: 81 | key = None 82 | key_used = True 83 | else: 84 | user.key_used = True 85 | user.save() 86 | new_cookie = user.cookie 87 | if key is None: 88 | auth = request.cookies.get('yobot_auth') 89 | if auth is None: 90 | if key_used: 91 | return '链接已过期', 410 92 | else: 93 | return '403 Forbidden', 403 94 | user = Admin_key.get_or_none(Admin_key.cookie == auth) 95 | if user is None: 96 | return '403 Forbidden', 403 97 | if not user.valid: 98 | return '登录已过期', 410 99 | 100 | res = await make_response(await render_template('marionette.html')) 101 | if new_cookie is not None: 102 | res.set_cookie('yobot_auth', new_cookie, max_age=604800) 103 | return res 104 | 105 | @app.route( 106 | urljoin(self.setting['public_basepath'], 'marionette/api/'), 107 | methods=['POST']) 108 | async def yobot_marionette_api(): 109 | auth = request.cookies.get('yobot_auth') 110 | if auth is None: 111 | return '403 Forbidden', 403 112 | user = Admin_key.get_or_none(Admin_key.cookie == auth) 113 | if user is None: 114 | return '403 Forbidden', 403 115 | req = await request.get_json() 116 | if req is None: 117 | return '406 Not Acceptable', 406 118 | try: 119 | await self.api.send_msg(**req) 120 | except Exception as e: 121 | return jsonify(code=1, message=str(e)) 122 | return jsonify(code=0, message='success') 123 | -------------------------------------------------------------------------------- /src/client/ybplugins/shorten_url.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import requests 3 | 4 | _shorten_api = 'http://go.yobot.monster/yourls-api.php' 5 | _header = { 6 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36', 7 | } 8 | 9 | 10 | def shorten(url: str) -> str: 11 | data = { 12 | 'signature': '52cd522abf', 13 | 'action': 'shorturl', 14 | 'url': url, 15 | 'format': 'simple' 16 | } 17 | try: 18 | resp = requests.post(_shorten_api, data=data, headers=_header) 19 | except requests.exceptions.ConnectionError: 20 | pass 21 | else: 22 | if resp.status_code == 200: 23 | url = resp.text 24 | return url 25 | 26 | 27 | async def shorten_async(url: str) -> str: 28 | data = { 29 | 'signature': '52cd522abf', 30 | 'action': 'shorturl', 31 | 'url': url, 32 | 'format': 'simple' 33 | } 34 | try: 35 | async with aiohttp.request('POST', url=_shorten_api, data=data, headers=_header) as response: 36 | if response.status == 200: 37 | url = await response.text() 38 | except aiohttp.ClientError: 39 | pass 40 | return url 41 | -------------------------------------------------------------------------------- /src/client/ybplugins/switcher.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from urllib.parse import urljoin 4 | 5 | 6 | class Switcher: 7 | Passive = True 8 | Active = False 9 | Request = False 10 | 11 | def __init__(self, glo_setting: dict, *args, **kwargs): 12 | self.setting = glo_setting 13 | 14 | def save_settings(self) -> None: 15 | save_setting = self.setting.copy() 16 | del save_setting["dirname"] 17 | del save_setting["verinfo"] 18 | config_path = os.path.join( 19 | self.setting["dirname"], "yobot_config.json") 20 | with open(config_path, "w", encoding="utf-8") as f: 21 | json.dump(save_setting, f, indent=4) 22 | 23 | @staticmethod 24 | def match(cmd: str) -> int: 25 | if cmd == "设置": 26 | f = 0x300 27 | elif cmd.startswith("设置码"): 28 | f = 0x400 29 | elif cmd.startswith("设置"): 30 | if len(cmd) < 7: 31 | f = 0x500 32 | else: 33 | f = 0 34 | else: 35 | f = 0 36 | return f 37 | 38 | 39 | def execute(self, match_num: int, msg: dict) -> dict: 40 | return urljoin( 41 | self.setting['public_address'], 42 | '{}admin/setting/'.format(self.setting['public_basepath']) 43 | ) 44 | -------------------------------------------------------------------------------- /src/client/ybplugins/templating.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import jinja2 4 | from quart import session, url_for 5 | 6 | static_folder = os.path.abspath(os.path.join( 7 | os.path.dirname(__file__), '../public/static')) 8 | template_folder = os.path.abspath(os.path.join( 9 | os.path.dirname(__file__), '../public/template')) 10 | 11 | Ver = 'unknown' 12 | 13 | 14 | def _vertioned_url_for(endpoint, *args, **kwargs): 15 | if endpoint == 'yobot_static': 16 | kwargs['v'] = Ver 17 | return url_for(endpoint, *args, **kwargs) 18 | 19 | 20 | _env = jinja2.Environment( 21 | loader=jinja2.FileSystemLoader(template_folder), 22 | enable_async=True, 23 | ) 24 | _env.globals['session'] = session 25 | _env.globals['url_for'] = _vertioned_url_for 26 | 27 | 28 | async def render_template(template, **context): 29 | t = _env.get_template(template) 30 | return await t.render_async(**context) 31 | -------------------------------------------------------------------------------- /src/client/ybplugins/web_util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import string 4 | from urllib.parse import urljoin 5 | 6 | import aiohttp 7 | import requests 8 | from quart import Quart, jsonify, request, send_file, session 9 | 10 | from .yobot_exceptions import ServerError 11 | 12 | _rand_string_chaset = (string.ascii_uppercase + 13 | string.ascii_lowercase + 14 | string.digits) 15 | 16 | 17 | def rand_string(n=16): 18 | return ''.join( 19 | random.choice(_rand_string_chaset) 20 | for _ in range(n) 21 | ) 22 | 23 | 24 | def async_cached_func(maxsize=64): 25 | cache = {} 26 | 27 | def decorator(fn): 28 | async def wrapper(*args, nocache=False): # args must be hashable 29 | key = tuple(args) 30 | if nocache or (key not in cache): 31 | if len(cache) >= maxsize: 32 | del cache[cache.keys().next()] 33 | cache[key] = await fn(*args) 34 | return cache[key] 35 | return wrapper 36 | return decorator 37 | 38 | 39 | @async_cached_func(128) 40 | async def _ip_location(ip): 41 | async with aiohttp.request("GET", url=f'http://freeapi.ipip.net/{ip}') as response: 42 | if response.status != 200: 43 | raise ServerError(f'http code {response.status} from ipip.net') 44 | res = await response.json() 45 | return res 46 | 47 | 48 | class WebUtil: 49 | Passive = False 50 | Active = False 51 | Request = True 52 | 53 | def __init__(self, 54 | glo_setting, 55 | *args, **kwargs): 56 | self.setting = glo_setting 57 | self.resource_path = os.path.join( 58 | glo_setting['dirname'], 'output', 'resource') 59 | if not os.path.exists(self.resource_path): 60 | os.makedirs(self.resource_path) 61 | 62 | if not os.path.exists(os.path.join(self.resource_path, 'background.jpg')): 63 | try: 64 | r = requests.get('https://i.loli.net/2020/05/31/IirkP9TpnV7Ks6q.jpg') 65 | assert r.status_code == 200 66 | with open(os.path.join(self.resource_path, 'background.jpg'), 'wb') as f: 67 | f.write(r.content) 68 | except Exception as e: 69 | print(e) 70 | 71 | def register_routes(self, app: Quart): 72 | 73 | @app.route( 74 | urljoin(self.setting['public_basepath'], 'api/ip-location/'), 75 | methods=['GET']) 76 | async def yobot_api_iplocation(): 77 | if 'yobot_user' not in session: 78 | return jsonify(['unauthorized']) 79 | ip = request.args.get('ip') 80 | if ip is None: 81 | return jsonify(['unknown']) 82 | try: 83 | location = await _ip_location(ip) 84 | except: 85 | location = ['unknown'] 86 | return jsonify(location) 87 | 88 | @app.route( 89 | urljoin(self.setting['public_basepath'], 'api/get-domain/'), 90 | methods=['GET']) 91 | async def yobot_api_getdomain(): 92 | if 'yobot_user' not in session: 93 | return jsonify(code=400, message='Unauthorized') 94 | name = request.args.get('name') 95 | if name is None: 96 | return jsonify(code=400, message='No name specified') 97 | try: 98 | async with aiohttp.request('GET', url='http://api2.yobot.win/getdomain/?name='+name) as response: 99 | if response.status != 200: 100 | raise ServerError( 101 | f'http code {response.status} from api2.yobot.win') 102 | res = await response.json() 103 | except: 104 | return jsonify(code=401, message='Fail: Connect to Server') 105 | return jsonify(res) 106 | 107 | @app.route( 108 | urljoin(self.setting["public_basepath"], 109 | "resource/"), 110 | methods=["GET"]) 111 | async def yobot_resource(filename): 112 | localfile = os.path.join(self.resource_path, filename) 113 | if not os.path.exists(localfile): 114 | if filename.endswith('.jpg'): 115 | filename = filename[:-4] + '.webp@w400' 116 | try: 117 | async with aiohttp.request( 118 | "GET", 119 | url=f'https://redive.estertion.win/{filename}' 120 | ) as response: 121 | res = await response.read() 122 | if response.status != 200: 123 | return res, response.status 124 | except aiohttp.ClientError as e: 125 | print(e) 126 | return '404: Not Found', 404 127 | if not os.path.exists(os.path.dirname(localfile)): 128 | os.makedirs(os.path.dirname(localfile)) 129 | with open(localfile, 'wb') as f: 130 | f.write(res) 131 | return await send_file(localfile) 132 | -------------------------------------------------------------------------------- /src/client/ybplugins/ybdata.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from playhouse.migrate import SqliteMigrator, migrate 3 | import json 4 | 5 | from .web_util import rand_string 6 | 7 | db_mode = True # True为本地(原),Flase为为改为mysql(需要在第15行配置使用) 8 | 9 | _version = 2 # 目前版本 10 | MAX_TRY_TIMES = 5 11 | 12 | if db_mode: 13 | _db = SqliteDatabase(None) 14 | else: 15 | _db = MySQLDatabase( 16 | "", host="", user="", passwd="", port=3306 17 | # 第一个填写你的数据库名 18 | ) 19 | 20 | 21 | class _BaseModel(Model): 22 | class Meta: 23 | database = _db 24 | 25 | 26 | class Admin_key(_BaseModel): 27 | key = CharField(primary_key=True) # if db_mode else TextField(primary_key=True) 28 | valid = BooleanField() 29 | key_used = BooleanField() 30 | cookie = CharField(index=True) # if db_mode else TextField(primary_key=True) 31 | create_time = TimestampField() 32 | 33 | 34 | class User(_BaseModel): 35 | qqid = BigIntegerField(primary_key=True) 36 | nickname = TextField(null=True) 37 | 38 | # 1:主人 10:公会战管理员 100:成员 39 | authority_group = IntegerField(default=100) 40 | 41 | privacy = IntegerField(default=MAX_TRY_TIMES) # 密码错误次数 42 | clan_group_id = BigIntegerField(null=True) 43 | last_login_time = BigIntegerField(default=0) 44 | last_login_ipaddr = TextField(default="0.0.0.0") 45 | password = FixedCharField(max_length=64, null=True) 46 | must_change_password = BooleanField(default=True) 47 | login_code = FixedCharField(max_length=6, null=True) 48 | login_code_available = BooleanField(default=False) 49 | login_code_expire_time = BigIntegerField(default=0) 50 | salt = CharField(max_length=16, default=rand_string) 51 | deleted = BooleanField(default=False) 52 | 53 | 54 | class User_login(_BaseModel): 55 | qqid = BigIntegerField() 56 | auth_cookie = FixedCharField(max_length=64) 57 | auth_cookie_expire_time = BigIntegerField(default=0) 58 | last_login_time = BigIntegerField(default=0) 59 | last_login_ipaddr = TextField(default="0.0.0.0") 60 | 61 | class Meta: 62 | primary_key = CompositeKey("qqid", "auth_cookie") 63 | 64 | 65 | # 原谅我mongodb用习惯了 66 | class Clan_group(_BaseModel): 67 | group_id = BigIntegerField(primary_key=True) 68 | group_name = TextField(null=True) 69 | privacy = IntegerField(default=2) # 0x1:允许游客查看出刀表,0x2:允许api调用出刀表 70 | game_server = CharField(max_length=2, default="cn") 71 | notification = IntegerField(default=0xFFFF) # 需要接收的通知 72 | battle_id = IntegerField(default=0) # 档案号 73 | apikey = CharField(max_length=16, default=rand_string) 74 | threshold = IntegerField(default=4000000) # 伤害阈值,计算分数用 75 | 76 | boss_cycle = SmallIntegerField(default=1) # 现周目数 77 | 78 | now_cycle_boss_health = TextField(default="") # 现周目boss剩余血量(json格式文本) 79 | # 结构 {boss_num:血量, } 80 | next_cycle_boss_health = TextField(default="") # 下周目boss剩余血量(json格式文本) 81 | 82 | # 所有正在出刀的人(json格式文本) 83 | # 结构:{boss_num:{ 84 | # challenger:{ 85 | # is_continue:是否是补偿, 86 | # behalf:代刀人qq, 87 | # s:余秒, 88 | # damage:报伤害, 89 | # tree:是否挂树boolean, 90 | # msg:挂树留言 91 | # }, 92 | # }, } 93 | challenging_member_list = TextField(null=True) 94 | 95 | # 预约表(json格式文本) 结构:{boss_num:{qqid: message, }, } 96 | subscribe_list = TextField(null=True) 97 | 98 | challenging_start_time = BigIntegerField(default=0) 99 | deleted = BooleanField(default=False) 100 | 101 | 102 | class Clan_group_backups(_BaseModel): 103 | group_id = BigIntegerField(index=True) 104 | battle_id = IntegerField(index=True) # 档案号 105 | group_data = TextField(null=True) # 所有数据(json格式文本) 结构同Clan_group 106 | 107 | class Meta: 108 | primary_key = CompositeKey("group_id", "battle_id") 109 | 110 | 111 | class Clan_member(_BaseModel): 112 | group_id = BigIntegerField(index=True) 113 | qqid = BigIntegerField(index=True) 114 | role = IntegerField(default=100) 115 | last_save_slot = IntegerField(null=True) # 上一次sl的日期 116 | remaining_status = TextField(null=True) 117 | 118 | class Meta: 119 | primary_key = CompositeKey("group_id", "qqid") 120 | 121 | 122 | # 每一刀的报刀数据 123 | class Clan_challenge(_BaseModel): 124 | cid = AutoField(primary_key=True) # 自增id 125 | bid = IntegerField(default=0) # 档案号 126 | gid = BigIntegerField() # 公会qq群号 127 | qqid = BigIntegerField(index=True) # 出刀人qq号 128 | challenge_pcrdate = IntegerField() # 出刀时的日期 129 | challenge_pcrtime = IntegerField() # 出刀时的时间 130 | boss_cycle = SmallIntegerField() # 第几周目 131 | boss_num = SmallIntegerField() # 几王 132 | boss_health_remain = BigIntegerField() # boss剩余血量 133 | challenge_damage = BigIntegerField() # 对boss造成的伤害 134 | is_continue = BooleanField() # 是否是补偿刀 135 | message = TextField(null=True) # 信息 136 | behalf = IntegerField(null=True) # 代刀人 137 | 138 | class Meta: 139 | indexes = ( 140 | (("bid", "gid"), False), 141 | (("qqid", "challenge_pcrdate"), False), 142 | (("bid", "gid", "challenge_pcrdate"), False), 143 | ) 144 | 145 | 146 | class Character(_BaseModel): 147 | chid = IntegerField(primary_key=True) 148 | name = CharField(max_length=64) 149 | frequent = BooleanField(default=True) 150 | 151 | 152 | class Chara_nickname(_BaseModel): 153 | name = CharField(max_length=64, primary_key=True) 154 | chid = IntegerField() 155 | 156 | 157 | class DB_schema(_BaseModel): 158 | key = CharField(max_length=64, primary_key=True) 159 | value = TextField() 160 | 161 | 162 | def init(sqlite_filename): 163 | if db_mode: 164 | _db.init( 165 | database=sqlite_filename, 166 | pragmas={ 167 | "journal_mode": "wal", 168 | "cache_size": -1024 * 64, 169 | }, 170 | ) 171 | 172 | old_version = 1 173 | if not DB_schema.table_exists(): 174 | DB_schema.create_table() 175 | DB_schema.create(key="version", value=str(_version)) 176 | else: 177 | old_version = int(DB_schema.get(key="version").value) 178 | 179 | if not User.table_exists(): 180 | Admin_key.create_table() 181 | User.create_table() 182 | User_login.create_table() 183 | Clan_group.create_table() 184 | Clan_member.create_table() 185 | Clan_group_backups.create_table() 186 | Clan_challenge.create_table() 187 | Character.create_table() 188 | old_version = _version 189 | if old_version > _version: 190 | print("数据库版本高于程序版本,请升级yobot") 191 | raise SystemExit() 192 | if old_version < _version: 193 | print("正在升级数据库") 194 | db_upgrade(old_version) 195 | print("数据库升级完毕") 196 | 197 | 198 | def db_upgrade(old_version): 199 | migrator = SqliteMigrator(_db) 200 | if old_version < 2: 201 | pass 202 | if old_version <= 1: 203 | """ 204 | 更新预约表存储结构 205 | 原结构: Dict[str, List[int]] = {Boss编号: [预约QQ号]} 206 | 新结构: Dict[str, Dict[str, str]] = {Boss编号: {预约QQ号: 留言}} 207 | """ 208 | for group in Clan_group.select(): 209 | old_subscribe_list = group.subscribe_list 210 | if not old_subscribe_list: 211 | continue 212 | old_subscribe_list = json.loads(old_subscribe_list) 213 | new_subscribe_list = {} 214 | for old_boss_no, old_qq_list in old_subscribe_list.items(): 215 | if old_boss_no not in new_subscribe_list: 216 | new_subscribe_list[old_boss_no] = {} 217 | for this_old_qq in old_qq_list: 218 | this_old_qq = str(this_old_qq) 219 | new_subscribe_list[old_boss_no][this_old_qq] = None 220 | new_subscribe_list = json.dumps(new_subscribe_list) 221 | group.subscribe_list = new_subscribe_list 222 | group.save() 223 | 224 | DB_schema.replace(key="version", value=str(_version)).execute() 225 | -------------------------------------------------------------------------------- /src/client/ybplugins/yobot_exceptions.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | class FileError(IOError): 3 | def __init__(self, s="file error"): 4 | super().__init__(self) 5 | self.error_msg = s 6 | 7 | def __str__(self): 8 | return self.error_msg 9 | 10 | class ServerError(Exception): 11 | def __init__(self, s="server error"): 12 | super().__init__(self) 13 | self.error_msg = s 14 | 15 | def __str__(self): 16 | return self.error_msg 17 | 18 | class CodingError(IOError): 19 | def __init__(self, s="coding error"): 20 | super().__init__(self) 21 | self.error_msg = s 22 | 23 | class InputError(ValueError): 24 | def __init__(self, s="input error"): 25 | super().__init__(self) 26 | self.error_msg = s 27 | 28 | def __str__(self): 29 | return self.error_msg 30 | -------------------------------------------------------------------------------- /src/client/ybplugins/yobot_msg.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urljoin 2 | 3 | 4 | class Message: 5 | Passive = True 6 | Active = False 7 | Request = False 8 | 9 | def __init__(self, glo_setting: dict, *args, **kwargs): 10 | self.version = glo_setting["verinfo"]["ver_name"] 11 | self.setting = glo_setting 12 | if glo_setting["clan_battle_mode"] != "chat": 13 | self.help_page = urljoin( 14 | glo_setting["public_address"], 15 | '{}help/'.format(glo_setting['public_basepath'])) 16 | if glo_setting['web_mode_hint']: 17 | self.help_page 18 | else: 19 | self.help_page = "https://gitee.com/yobot/yobot/blob/master/documents/features/old.md" 20 | 21 | @staticmethod 22 | def match(cmd: str) -> int: 23 | if cmd == "ver" or cmd == "V" or cmd == "version": 24 | return 99 25 | elif cmd == "帮助" or cmd == "help": 26 | return 98 27 | elif cmd == "手册": 28 | return 97 29 | else: 30 | return 0 31 | 32 | def execute(self, match_num: int, msg: dict) -> dict: 33 | if match_num == 99: 34 | reply = self.version 35 | elif match_num == 98: 36 | reply = f'所有功能帮助网页版:\n{self.help_page}' 37 | elif match_num == 97: 38 | reply = urljoin( 39 | self.setting["public_address"], 40 | '{}manual/'.format(self.setting['public_basepath'])) 41 | elif match_num == 2: 42 | reply = "boss被击败后我会提醒下树" 43 | else: 44 | reply = "此功能已经不再可用,请查看"+self.help_page 45 | return { 46 | "reply": reply, 47 | "block": True 48 | } 49 | --------------------------------------------------------------------------------