├── LittlePaimon_plugin_Abyss ├── geetest │ ├── static │ │ ├── favicon.ico │ │ └── gt.js │ ├── templates │ │ ├── favicon.ico │ │ └── geetest.html │ └── run.py ├── web_api.py ├── config.py ├── static.py ├── ssbq.py ├── web_page.py ├── api.py ├── sign_handle.py ├── sr_api.py ├── coin_handle.py └── main.py ├── __init__.py ├── README.md ├── .gitignore └── LICENSE /LittlePaimon_plugin_Abyss/geetest/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CM-Edelweiss/LittlePaimon-plugin-Abyss/HEAD/LittlePaimon_plugin_Abyss/geetest/static/favicon.ico -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/geetest/templates/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CM-Edelweiss/LittlePaimon-plugin-Abyss/HEAD/LittlePaimon_plugin_Abyss/geetest/templates/favicon.ico -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .LittlePaimon_plugin_Abyss.main import * 2 | from LittlePaimon.utils import logger 3 | 4 | logger.info("人工验证", "➤", "", "请使用pip install flask gevent安装依赖,安装过请忽略", True) 5 | logger.info( 6 | "人工验证", 7 | "➤", 8 | "", 9 | "在根目录请使用python LittlePaimon\plugins\LittlePaimon-plugin-Abyss\geetest\run.py打开人工验证后端", 10 | True, 11 | ) 12 | logger.info("人工验证", "➤", "", f"ip:{config.ip}端口:5000", True) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | LittlePaimon 3 |

4 |

小派蒙|LittlePaimon-plugin-Abyss

5 |

✨为LittlePaimon插件提供实时便签和签到接入接码平台✨

6 | 7 |

8 | license 9 | python 10 | version 11 | QQ guild 12 |

13 | 14 | ## 丨❗注意 15 | 16 | 代码没有经过充分测试,可能有亿点问题,有问题请提issues 17 | 18 | 19 | ## 丨📖 使用 20 | 把`LittlePaimon-plugin-Abyss`文件夹放在`LittlePaimon\LittlePaimon\plugins\`里 21 | 22 | 指令看小派蒙帮助图 23 | 24 | ## 丨⚙️ 配置 25 | 第一次启动时生成文件`LittlePaimon\config\Abyss_config.yml` 26 | 或者看小派蒙webui的加强签到 27 | ## 丨💸鸣谢 28 | - [LittlePaimon](https://github.com/CMHopeSunshine/LittlePaimon)实时便签和米游币获取代码(~~直接开抄~~) 29 | - [GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID/tree/nonebot2-beta1)签到代码(~~直接开抄~~) 30 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/web_api.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from fastapi import APIRouter 3 | from fastapi.responses import JSONResponse 4 | 5 | from LittlePaimon.web.api import BaseApiRouter 6 | from LittlePaimon.web.api.utils import authentication 7 | 8 | from .config import ConfigManager 9 | 10 | route = APIRouter() 11 | 12 | 13 | @route.get( 14 | "/abyss_config_g", response_class=JSONResponse, dependencies=[authentication()] 15 | ) 16 | async def abyss_config_g(): 17 | config = ConfigManager.config.dict(by_alias=True) 18 | config["验证米游社签到开始时间"] = datetime.datetime( 19 | 1970, 1, 1, hour=config["验证米游社签到开始时间(小时)"], minute=config["验证米游社签到开始时间(分钟)"] 20 | ).strftime("%H:%M") 21 | config["验证星铁签到开始时间"] = datetime.datetime( 22 | 1970, 1, 1, hour=config["验证星铁签到开始时间(小时)"], minute=config["验证星铁签到开始时间(分钟)"] 23 | ).strftime("%H:%M") 24 | config["验证米游币开始执行时间"] = datetime.datetime( 25 | 1970, 1, 1, hour=config["验证米游币开始执行时间(小时)"], minute=config["验证米游币开始执行时间(分钟)"] 26 | ).strftime("%H:%M") 27 | return {"status": 0, "msg": "ok", "data": config} 28 | 29 | 30 | @route.post( 31 | "/abyss_config", response_class=JSONResponse, dependencies=[authentication()] 32 | ) 33 | async def abyss_config(data: dict): 34 | if "验证米游社签到开关" in data: 35 | temp_time = datetime.datetime.strptime(data["验证米游社签到开始时间"], "%H:%M") 36 | data["验证米游社签到开始时间(小时)"] = temp_time.hour 37 | data["验证米游社签到开始时间(分钟)"] = temp_time.minute 38 | if "验证米游币获取开关" in data: 39 | temp_time = datetime.datetime.strptime(data["验证米游币开始执行时间"], "%H:%M") 40 | data["验证米游币开始执行时间(小时)"] = temp_time.hour 41 | data["验证米游币开始执行时间(分钟)"] = temp_time.minute 42 | if "验证星铁签到开关" in data: 43 | temp_time = datetime.datetime.strptime(data["验证星铁签到开始时间"], "%H:%M") 44 | data["验证星铁签到开始时间(小时)"] = temp_time.hour 45 | data["验证星铁签到开始时间(分钟)"] = temp_time.minute 46 | ConfigManager.config.update(**data) 47 | ConfigManager.save() 48 | return {"status": 0, "msg": "保存成功"} 49 | 50 | 51 | BaseApiRouter.include_router(route) 52 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/config.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from LittlePaimon.utils.files import load_yaml, save_yaml 3 | from pathlib import Path 4 | from typing import Literal, List 5 | 6 | PAIMON_CONFIG = Path() / "config" / "Abyss_config.yml" 7 | 8 | 9 | class ConfigModel(BaseModel): 10 | enable: bool = Field(True, alias="验证米游社签到开关") 11 | enable_hour: int = Field(7, alias="验证米游社签到开始时间(小时)") 12 | enable_minute: int = Field(5, alias="验证米游社签到开始时间(分钟)") 13 | sr_enable: bool = Field(True, alias="验证星铁签到开关") 14 | sr_enable_hour: int = Field(7, alias="验证星铁签到开始时间(小时)") 15 | sr_enable_minute: int = Field(5, alias="验证星铁签到开始时间(分钟)") 16 | myb: bool = Field(True, alias="验证米游币获取开关") 17 | myb_hour: int = Field(8, alias="验证米游币开始执行时间(小时)") 18 | myb_minute: int = Field(0, alias="验证米游币开始执行时间(分钟)") 19 | appkey: str = Field("", alias="appkey密钥") 20 | ip: str = Field("127.0.0.1:5000", alias="人工验证网站") 21 | whitelist: List[int] = Field([], alias="群聊白名单") 22 | whlist: List[int] = Field([], alias="用户白名单") 23 | vaapikai: Literal["rr", "dsf", "rg"] = Field("rr", alias="打码模式") 24 | vaapikai2: Literal["rr", "dsf"] = Field("rr", alias="自动签到打码模式") 25 | vaapi: str = Field("", alias="第三方过码") 26 | hfu: str = Field("就不给你用~", alias="非白名单回复") 27 | 28 | @property 29 | def alias_dict(self): 30 | return {v.alias: k for k, v in self.__fields__.items()} 31 | 32 | def update(self, **kwargs): 33 | for key, value in kwargs.items(): 34 | if key in self.__fields__: 35 | self.__setattr__(key, value) 36 | elif key in self.alias_dict: 37 | self.__setattr__(self.alias_dict[key], value) 38 | 39 | 40 | class ConfigManager: 41 | if PAIMON_CONFIG.exists(): 42 | config = ConfigModel.parse_obj(load_yaml(PAIMON_CONFIG)) 43 | else: 44 | config = ConfigModel() 45 | save_yaml(config.dict(by_alias=True), PAIMON_CONFIG) 46 | 47 | @classmethod 48 | def save(cls): 49 | save_yaml(cls.config.dict(by_alias=True), PAIMON_CONFIG) 50 | 51 | 52 | config = ConfigManager.config 53 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/static.py: -------------------------------------------------------------------------------- 1 | """"摆""" 2 | import json 3 | import random 4 | from LittlePaimon.utils.api import ( 5 | random_hex, 6 | random_text, 7 | get_old_version_ds, 8 | get_cookie, 9 | ) 10 | from LittlePaimon.utils.requests import aiorequests 11 | from LittlePaimon.utils.logger import logger 12 | from .api import get_validate 13 | 14 | # 验证码 15 | bbs_api = "https://bbs-api.mihoyo.com" 16 | bbs_get_captcha = bbs_api + "/misc/api/createVerification?is_high=true" 17 | bbs_captcha_verify = bbs_api + "/misc/api/verifyVerification" 18 | 19 | 20 | async def get_pass_challenge(user_id, uid) -> str: 21 | cookie = await get_cookie(user_id, uid, True, True) 22 | if not cookie: 23 | return "你尚未绑定Cookie和Stoken,请先用ysb指令绑定!" 24 | if cookie.stoken is None: 25 | return "你绑定Cookie中没有login_ticket,请重新用ysb指令绑定!" 26 | headers = { 27 | "DS": get_old_version_ds(), 28 | "cookie": cookie.stoken, 29 | "x-rpc-client_type": "2", 30 | "x-rpc-app_version": "2.38.1", 31 | "x-rpc-sys_version": "12", 32 | "x-rpc-channel": "miyousheluodi", 33 | "x-rpc-device_id": random_hex(32), 34 | "x-rpc-device_name": random_text(random.randint(1, 10)), 35 | "x-rpc-device_model": "Mi 10", 36 | "Referer": "https://app.mihoyo.com", 37 | "Host": "bbs-api.mihoyo.com", 38 | "User-Agent": "okhttp/4.8.0", 39 | } 40 | req = await aiorequests.get( 41 | url=bbs_get_captcha, 42 | headers=headers, 43 | timeout=30, 44 | ) 45 | data = req.json() 46 | if data["retcode"] != 0: 47 | return "过码失败,请检查stoken是否有效" 48 | gt = data["data"]["gt"] 49 | ch = data["data"]["challenge"] 50 | validate, ch = await get_validate(gt, ch, bbs_get_captcha, uid, False) 51 | if validate: 52 | req = await aiorequests.post( 53 | url=bbs_captcha_verify, 54 | headers=headers, 55 | json={ 56 | "geetest_challenge": data["data"]["challenge"], 57 | "geetest_seccode": validate + "|jordan", 58 | "geetest_validate": validate, 59 | }, 60 | ) 61 | check = req.json() 62 | if check["retcode"] == 0: 63 | logger.info("米游社过码", "➤", "", "成功", True) 64 | return "过码成功,不一定解除" 65 | return "过码失败,请稍后再试" 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/ssbq.py: -------------------------------------------------------------------------------- 1 | from LittlePaimon.database import Player, LastQuery 2 | from LittlePaimon.utils import logger 3 | from LittlePaimon.plugins.Paimon_DailyNote.draw import draw_daily_note_card 4 | from LittlePaimon.utils.api import get_mihoyo_private_data, get_cookie, mihoyo_headers 5 | from .api import tilioc 6 | 7 | 8 | async def handle_ssbq(player: Player): 9 | await LastQuery.update_last_query(player.user_id, player.uid) 10 | data = await get_mihoyo_private_data(player.uid, player.user_id, "daily_note") 11 | if isinstance(data, str): 12 | logger.info( 13 | "原神体力", 14 | "➤", 15 | {"用户": player.user_id, "UID": player.uid}, 16 | f"获取数据失败, {data}", 17 | False, 18 | ) 19 | return f"{player.uid}{data}\n" 20 | elif data["retcode"] == 1034: 21 | logger.info( 22 | "原神体力", 23 | "➤", 24 | {"用户": player.user_id, "UID": player.uid}, 25 | "获取数据失败,状态码为1034,疑似验证码", 26 | False, 27 | ) 28 | return "疑似验证码" 29 | elif data["retcode"] != 0: 30 | logger.info( 31 | "原神体力", 32 | "➤", 33 | {"用户": player.user_id, "UID": player.uid}, 34 | f'获取数据失败,code为{data["retcode"]}, msg为{data["message"]}', 35 | False, 36 | ) 37 | 38 | return f'{player.uid}获取数据失败,msg为{data["message"]}\n' 39 | else: 40 | logger.info( 41 | "原神体力", "➤", {"用户": player.user_id, 42 | "UID": player.uid}, "获取数据成功", True 43 | ) 44 | 45 | try: 46 | img = await draw_daily_note_card(data["data"], player.uid) 47 | logger.info( 48 | "原神体力", "➤➤", {"用户": player.user_id, 49 | "UID": player.uid}, "绘制图片成功", True 50 | ) 51 | 52 | return img 53 | except Exception as e: 54 | logger.info( 55 | "原神体力", 56 | "➤➤", 57 | {"用户": player.user_id, "UID": player.uid}, 58 | f"绘制图片失败,{e}", 59 | False, 60 | ) 61 | 62 | return f"{player.uid}绘制图片失败,{e}\n" 63 | 64 | 65 | async def handle_ssbq2(player: Player): 66 | cookie_info = await get_cookie(player.user_id, player.uid, True, True) 67 | server_id = "cn_qd01" if player.uid[0] == "5" else "cn_gf01" 68 | re, _ = await tilioc( 69 | mihoyo_headers( 70 | q=f"role_id={player.uid}&server={server_id}", cookie=cookie_info.cookie 71 | ), 72 | player.uid, 73 | False, 74 | ) 75 | if not re: 76 | return f"{player.uid}尝试越过验证码失败,请重试" 77 | data = await get_mihoyo_private_data(player.uid, player.user_id, "daily_note") 78 | if isinstance(data, str): 79 | logger.info( 80 | "原神体力", 81 | "➤", 82 | {"用户": player.user_id, "UID": player.uid}, 83 | f"获取数据失败, {data}", 84 | False, 85 | ) 86 | return f"{player.uid}{data}\n" 87 | elif data["retcode"] == 1034: 88 | logger.info( 89 | "原神体力", "➤", {"用户": player.user_id, 90 | "UID": player.uid}, "发生错误,请联系作者", False 91 | ) 92 | return "发生错误" 93 | elif data["retcode"] != 0: 94 | logger.info( 95 | "原神体力", 96 | "➤", 97 | {"用户": player.user_id, "UID": player.uid}, 98 | f'获取数据失败,code为{data["retcode"]}, msg为{data["message"]}', 99 | False, 100 | ) 101 | 102 | return f'{player.uid}获取数据失败,msg为{data["message"]}\n' 103 | else: 104 | logger.info( 105 | "原神体力", "➤", {"用户": player.user_id, 106 | "UID": player.uid}, "获取数据成功", True 107 | ) 108 | 109 | try: 110 | img = await draw_daily_note_card(data["data"], player.uid) 111 | logger.info( 112 | "原神体力", "➤➤", {"用户": player.user_id, 113 | "UID": player.uid}, "绘制图片成功", True 114 | ) 115 | 116 | return img 117 | except Exception as e: 118 | logger.info( 119 | "原神体力", 120 | "➤➤", 121 | {"用户": player.user_id, "UID": player.uid}, 122 | f"绘制图片失败,{e}", 123 | False, 124 | ) 125 | 126 | return f"{player.uid}绘制图片失败,{e}\n" 127 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/geetest/templates/geetest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Edelweiss的验证 8 | 9 | 10 | 11 |

验证后会自动退出网页

12 |

UID{{uid}}的验证

13 |

14 |

15 |

16 |

17 |

18 | 19 | 20 | 99 | 100 | 110 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/web_page.py: -------------------------------------------------------------------------------- 1 | from amis import ( 2 | Form, 3 | LevelEnum, 4 | InputTime, 5 | Divider, 6 | InputText, 7 | Alert, 8 | Html, 9 | PageSchema, 10 | Page, 11 | Switch, 12 | Remark, 13 | InputTag, 14 | Action, 15 | Select, 16 | ) 17 | 18 | from LittlePaimon.web.pages import admin_app 19 | 20 | global_config_form = Form( 21 | title="全局配置", 22 | name="abyss_config", 23 | api="post:/LittlePaimon/api/abyss_config", 24 | body=[ 25 | InputText( 26 | label="appkey密钥", 27 | name="appkey密钥", 28 | value="${appkey密钥}", 29 | labelRemark=Remark(shape="circle", content="人人网key密钥"), 30 | ), 31 | InputText( 32 | label="非白名单回复", 33 | name="非白名单回复", 34 | value="${非白名单回复}", 35 | labelRemark=Remark(shape="circle", content="非白名用户使用失败的回复"), 36 | ), 37 | Select( 38 | label="打码模式", 39 | name="打码模式", 40 | value="${打码模式}", 41 | labelRemark=Remark(shape="circle", content="选择打码模式"), 42 | options=[ 43 | {"label": "人人", "value": "rr"}, 44 | {"label": "第三方", "value": "dsf"}, 45 | {"label": "人工", "value": "rg"}, 46 | ], 47 | ), 48 | InputText( 49 | label="人工验证网站", 50 | name="人工验证网站", 51 | value="${人工验证网站}", 52 | labelRemark=Remark( 53 | shape="circle", 54 | content="自定义人工验证网站(后面不要加/)", 55 | ), 56 | ), 57 | Select( 58 | label="自动签到打码模式", 59 | name="自动签到打码模式", 60 | value="${自动签到打码模式}", 61 | labelRemark=Remark(shape="circle", content="自动签到下打码模式(打码模式选择人工时生效)"), 62 | options=[ 63 | {"label": "人人", "value": "rr"}, 64 | {"label": "第三方", "value": "dsf"}, 65 | ], 66 | ), 67 | InputText( 68 | label="第三方过码", 69 | name="第三方过码", 70 | value="${第三方过码}", 71 | labelRemark=Remark( 72 | shape="circle", 73 | content="使用第三方平台,例子(baidu.com?或者baidu.com?shen=114514&)开启后将放弃人人,人人key不填也没事", 74 | ), 75 | ), 76 | Divider(), 77 | InputTag( 78 | label="群聊白名单", 79 | name="群聊白名单", 80 | value="${群聊白名单}", 81 | enableBatchAdd=True, 82 | placeholder="添加白名单群号,回车添加", 83 | joinValues=False, 84 | extractValue=True, 85 | labelRemark=Remark(shape="circle", content="那些群可以用"), 86 | ), 87 | InputTag( 88 | label="用户白名单", 89 | name="用户白名单", 90 | value="${用户白名单}", 91 | enableBatchAdd=True, 92 | placeholder="添加启用白名单QQ号,回车添加", 93 | joinValues=False, 94 | extractValue=True, 95 | labelRemark=Remark(shape="circle", content="那些人可以用"), 96 | ), 97 | Divider(), 98 | Switch( 99 | label="验证米游社签到开关", 100 | name="验证米游社签到开关", 101 | value="${验证米游社签到开关}", 102 | onText="开启", 103 | offText="关闭", 104 | ), 105 | InputTime( 106 | label="验证米游社自动签到开始时间", 107 | name="验证米游社签到开始时间", 108 | value="${验证米游社签到开始时间}", 109 | labelRemark=Remark(shape="circle", content="会在每天这个时间点进行米游社自动签到任务,修改后重启生效"), 110 | inputFormat="HH时mm分", 111 | format="HH:mm", 112 | ), 113 | Divider(), 114 | Switch( 115 | label="验证星铁签到开关", 116 | name="验证星铁签到开关", 117 | value="${验证星铁签到开关}", 118 | onText="开启", 119 | offText="关闭", 120 | ), 121 | InputTime( 122 | label="验证星铁自动签到开始时间", 123 | name="验证星铁签到开始时间", 124 | value="${验证星铁签到开始时间}", 125 | labelRemark=Remark(shape="circle", content="会在每天这个时间点进行星铁自动签到任务,修改后重启生效"), 126 | inputFormat="HH时mm分", 127 | format="HH:mm", 128 | ), 129 | Divider(), 130 | Switch( 131 | label="验证米游币自动获取开关", 132 | name="验证米游币获取开关", 133 | value="${验证米游币获取开关}", 134 | onText="开启", 135 | offText="关闭", 136 | ), 137 | InputTime( 138 | label="验证米游币自动获取开始时间", 139 | name="验证米游币开始执行时间", 140 | value="${验证米游币开始执行时间}", 141 | labelRemark=Remark(shape="circle", content="会在每天这个时间点进行米游币自动获取任务,修改后重启生效"), 142 | inputFormat="HH时mm分", 143 | format="HH:mm", 144 | ), 145 | ], 146 | actions=[ 147 | Action(label="保存", level=LevelEnum.success, type="submit"), 148 | Action(label="重置", level=LevelEnum.warning, type="reset"), 149 | ], 150 | ) 151 | tips = Alert( 152 | level="info", 153 | body=Html( 154 | html='显示不全请刷新网页,本插件仓库地址有问题请提issues或者加我QQ' 155 | ), 156 | ) 157 | page = PageSchema( 158 | url="/abyss/configs", 159 | icon="fa fa-cube", 160 | label="加强签到", 161 | schema=Page( 162 | title="加强签到", 163 | initApi="/LittlePaimon/api/abyss_config_g", 164 | body=[tips, global_config_form], 165 | ), 166 | ) 167 | admin_app.pages[0].children.append(page) 168 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/geetest/run.py: -------------------------------------------------------------------------------- 1 | """ 2 | LittlePaimon-plugin-Abyss的人工验证后端 3 | """ 4 | 5 | from gevent import monkey 6 | 7 | monkey.patch_all() 8 | 9 | import threading 10 | import time 11 | from flask import Flask, abort, jsonify, request, send_from_directory, render_template 12 | from gevent.pywsgi import WSGIServer 13 | from uuid import uuid4 14 | 15 | app = Flask(__name__) 16 | 17 | cv_put = threading.Condition() 18 | cv_get = threading.Condition() 19 | 20 | 21 | tasks = {} 22 | pending = [] 23 | doing = [] 24 | done = [] 25 | ddd = [] 26 | 27 | 28 | def main(address="0.0.0.0", port=5000): 29 | http_server = WSGIServer((address, port), app) 30 | http_server.serve_forever() 31 | 32 | 33 | @app.route("/") 34 | def root(): 35 | return f"Edelweiss的验证后端./geetest?gt=XXX&challenge=XXX&uid=XXX
人工./validate?uid=XXX
默认超时为30s" 36 | 37 | 38 | @app.route("/geetest") 39 | def crack(): 40 | print(request.args) 41 | if all([key in request.args for key in ["gt", "challenge", "uid"]]): 42 | session = str(uuid4()) 43 | uid = request.args.get("uid") 44 | tasks[session] = { 45 | "code": -1, 46 | "gt": request.args.get("gt"), 47 | "challenge": request.args.get("challenge"), 48 | "success": request.args.get("success", 1), 49 | "validate": "", 50 | "seccode": "", 51 | "uid": uid, 52 | } 53 | print(f"UID{uid}:加入列表") 54 | pending.append(session) 55 | ddd.append(session) 56 | with cv_put: 57 | cv_put.notify_all() 58 | with cv_get: 59 | if cv_get.wait_for(lambda: session in done, timeout=30): 60 | if tasks[session]["code"] == 0: 61 | challenge = tasks[session]["challenge"] 62 | validate = tasks[session]["validate"] 63 | seccode = tasks[session]["seccode"] 64 | del tasks[session] 65 | ddd.remove(session) 66 | return jsonify( 67 | { 68 | "code": 0, 69 | "message": "success", 70 | "data": { 71 | "challenge": challenge, 72 | "validate": validate, 73 | "seccode": seccode, 74 | }, 75 | } 76 | ) 77 | else: 78 | del tasks[session] 79 | ddd.remove(session) 80 | return jsonify( 81 | { 82 | "code": -2, 83 | "message": "error", 84 | } 85 | ) 86 | else: 87 | if session in pending: 88 | pending.remove(session) 89 | if session in doing: 90 | doing.remove(session) 91 | done.append(session) 92 | del tasks[session] 93 | ddd.remove(session) 94 | return jsonify( 95 | { 96 | "code": -3, 97 | "message": "timeout", 98 | } 99 | ) 100 | else: 101 | return jsonify( 102 | { 103 | "code": -1, 104 | "message": "gt challenge and uid error", 105 | } 106 | ) 107 | 108 | 109 | @app.route("/favicon.ico") 110 | def favicon(): 111 | return send_from_directory( 112 | "static", "favicon.ico", mimetype="image/vnd.microsoft.icon" 113 | ) 114 | 115 | 116 | @app.route("/feedback", methods=["POST"]) 117 | def feedback(): 118 | if all([key in request.form for key in ["session", "code"]]): 119 | session = request.form.get("session") 120 | if session in doing: 121 | if request.form.get("code") == "0" and all( 122 | [key in request.form for key in ["challenge", "validate", "seccode"]] 123 | ): 124 | tasks[session]["code"] = 0 125 | tasks[session]["challenge"] = request.form.get("challenge") 126 | tasks[session]["validate"] = request.form.get("validate") 127 | tasks[session]["seccode"] = request.form.get("seccode") 128 | doing.remove(session) 129 | done.append(session) 130 | with cv_get: 131 | cv_get.notify_all() 132 | return jsonify( 133 | { 134 | "code": 0, 135 | "message": "success", 136 | } 137 | ) 138 | else: 139 | return jsonify( 140 | { 141 | "code": -2, 142 | "message": "invalid session", 143 | } 144 | ) 145 | else: 146 | return jsonify( 147 | { 148 | "code": -1, 149 | "message": "invalid parameter", 150 | } 151 | ) 152 | 153 | 154 | @app.route("/fetch") 155 | def fetch(): 156 | with cv_put: 157 | if cv_put.wait_for(lambda: pending, timeout=15): 158 | session = pending.pop(0) 159 | doing.append(session) 160 | uid = tasks[session] 161 | if uid["uid"] == request.args.get("uid"): 162 | return jsonify( 163 | { 164 | "session": session, 165 | "gt": uid["gt"], 166 | "challenge": uid["challenge"], 167 | "success": uid["success"], 168 | } 169 | ) 170 | else: 171 | abort(503) 172 | else: 173 | abort(503) 174 | 175 | 176 | @app.route("/validate") 177 | def task2(): 178 | if all([key in request.args for key in ["uid"]]): 179 | uid = request.args.get("uid") 180 | print(f"UID{uid}:访问网页") 181 | for uid2 in ddd: 182 | if uid2 in tasks: 183 | if ck := tasks[uid2]["uid"]: 184 | if uid in ck: 185 | print(uid2) 186 | return render_template("geetest.html", uid=uid) 187 | return f"Edelweiss的验证

UID{uid} 当前无剩余验证

" 188 | else: 189 | return f"Edelweiss的验证

你的访问UID呢

" 190 | 191 | 192 | if __name__ == "__main__": 193 | main() 194 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/api.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import json 3 | import time 4 | import random 5 | import string 6 | from typing import Union, Dict 7 | 8 | import httpx 9 | from LittlePaimon.utils.api import ( 10 | md5, 11 | get_cookie, 12 | random_hex, 13 | get_ds, 14 | ) 15 | from LittlePaimon.utils.requests import aiorequests 16 | from LittlePaimon.utils import logger 17 | 18 | from .config import config 19 | 20 | # 签到列表 21 | SIGN_LIST_URL = "https://api-takumi.mihoyo.com/event/bbs_sign_reward/home" 22 | # 签到信息 23 | SIGN_INFO_URL = "https://api-takumi.mihoyo.com/event/bbs_sign_reward/info" 24 | # 执行签到 25 | SIGN_URL = "https://api-takumi.mihoyo.com/event/bbs_sign_reward/sign" 26 | 27 | 28 | VERIFICATION_URL = "https://api-takumi-record.mihoyo.com/game_record/app/card/wapi/createVerification?is_high=false" 29 | VERIFY_URL = ( 30 | "https://api-takumi-record.mihoyo.com/game_record/app/card/wapi/verifyVerification" 31 | ) 32 | 33 | _HEADER = { 34 | "x-rpc-app_version": "2.11.1", 35 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", 36 | "x-rpc-client_type": "5", 37 | "Referer": "https://webstatic.mihoyo.com/", 38 | "Origin": "https://webstatic.mihoyo.com", 39 | } 40 | 41 | 42 | async def query_score(): 43 | url = f"http://api.rrocr.com/api/integral.html?appkey={config.appkey}" 44 | try: 45 | response = await aiorequests.get(url) 46 | except httpx.RequestError as exc: 47 | return False, f"访问失败,{exc}" 48 | data = response.json() 49 | if data["status"] == -1: 50 | logger.info("查询积分", "➤", "", "失败", False) 51 | return True, "查询积分失败" 52 | integral = data["integral"] 53 | if int(integral) < 10: 54 | logger.info("人人积分", "➤", "", "积分不足", False) 55 | return True, f"积分还剩{integral}" 56 | logger.info("人人积分", "➤", "", integral, True) 57 | return False, f"积分还剩{integral}" 58 | 59 | 60 | async def vaapigt(gt, challenge) -> str: 61 | """validate,challenge""" 62 | url = f"{config.vaapi}gt={gt}&challenge={challenge}" 63 | try: 64 | response = await aiorequests.get(url, timeout=60) 65 | except httpx.RequestError as exc: 66 | logger.info("第三方验证", "➤", "错误", exc, False) 67 | return None, None 68 | data = response.json() 69 | if "data" in data and "validate" in data["data"]: 70 | logger.info("第三方验证", "➤", "", "成功", True) 71 | validate = data["data"]["validate"] 72 | challenge = data["data"]["challenge"] 73 | return validate, challenge 74 | else: 75 | # 打码失败输出错误信息,返回None 76 | logger.info("第三方验证", "➤", "", "失败", False) 77 | validate = None 78 | challenge = None 79 | return validate, challenge # 失败返回None 成功返回validate 80 | 81 | 82 | async def rrocr(gt, challenge, referer) -> str: 83 | """validate,challenge""" 84 | jifen, _ = await query_score() 85 | if jifen: 86 | validate = None 87 | challenge = None 88 | return validate, challenge 89 | try: 90 | response = await aiorequests.post( 91 | "http://api.rrocr.com/api/recognize.html", 92 | params={ 93 | "appkey": config.appkey, 94 | "gt": gt, 95 | "challenge": challenge, 96 | "referer": referer, 97 | "sharecode": "a83baa99828342ccac180b19217e2a93", # ?不明 98 | }, 99 | timeout=60, 100 | ) 101 | except httpx.RequestError as exc: 102 | logger.info("人人验证", "➤", "错误", exc, False) 103 | return None, None 104 | data = response.json() 105 | if "data" in data and "validate" in data["data"]: 106 | logger.info("人人验证", "➤", "", data["msg"], True) 107 | validate = data["data"]["validate"] 108 | challenge = data["data"]["challenge"] 109 | return validate, challenge 110 | else: 111 | # 打码失败输出错误信息,返回None 112 | logger.info("人人验证", "➤", "", data["msg"], False) 113 | validate = None 114 | challenge = None 115 | return validate, challenge # 失败返回None 成功返回validate 116 | 117 | 118 | async def panel(gt, ch, uid) -> str: 119 | """过码(人工)""" 120 | url = f"http://127.0.0.1:5000/geetest?gt={gt}&challenge={ch}&uid={uid}" 121 | try: 122 | res = await aiorequests.get(url=url, timeout=31) 123 | res = res.json() 124 | except httpx.RequestError as exc: 125 | logger.info("人工验证", "➤", "", exc, False) 126 | res = {} 127 | if "data" in res and "validate" in res["data"]: 128 | ch = res["data"]["challenge"] 129 | va = res["data"]["validate"] 130 | logger.info("人工验证", "➤", "", "成功", True) 131 | return va, ch 132 | logger.info("人工验证", "➤", "", "失败", False) 133 | return None, None 134 | 135 | 136 | async def tilioc(header: Dict, uid: str, re: bool): 137 | header["DS"] = get_ds("is_high=false") 138 | raw_data = await aiorequests.get( 139 | url=VERIFICATION_URL, 140 | headers=header, 141 | ) 142 | raw_data = raw_data.json() 143 | if raw_data and "data" in raw_data and "gt" in raw_data["data"] and "challenge" in raw_data["data"]: 144 | gt = raw_data["data"]["gt"] 145 | ch = raw_data["data"]["challenge"] 146 | else: 147 | logger.info("验证码获取", "➤", "", raw_data, False) 148 | return False, None 149 | vl, ch = await get_validate(gt, ch, VERIFICATION_URL, uid, re) 150 | 151 | if vl: 152 | header["DS"] = get_ds( 153 | "", 154 | { 155 | "geetest_challenge": ch, 156 | "geetest_validate": vl, 157 | "geetest_seccode": f"{vl}|jordan", 158 | }, 159 | ) 160 | ch = await aiorequests.post( 161 | url=VERIFY_URL, 162 | headers=header, 163 | json={ 164 | "geetest_challenge": ch, 165 | "geetest_validate": vl, 166 | "geetest_seccode": f"{vl}|jordan", 167 | }, 168 | ) 169 | logger.info("统一验证", "➤", "", "成功", True) 170 | return True, ch 171 | else: 172 | logger.info("统一验证", "➤", "", "失败", False) 173 | return False, None 174 | 175 | 176 | async def get_validate(gt, challenge, referer, uid, re) -> str: 177 | """体力和签到验证函数""" 178 | mo = config.vaapikai2 if (re and config.vaapikai == 179 | "rg") else config.vaapikai 180 | if mo == "dsf": 181 | validate, challenge = await vaapigt(gt, challenge) 182 | elif mo == "rr": 183 | validate, challenge = await rrocr(gt, challenge, referer) 184 | elif mo == "rg": 185 | validate, challenge = await panel(gt, challenge, uid) 186 | else: 187 | validate = None 188 | challenge = None 189 | logger.info("验证", "➤", "", "错误的配置", False) 190 | return validate, challenge # 失败返回None 成功返回validate 191 | 192 | 193 | async def get_sign_info(uid, cookie) -> str: 194 | server_id = "cn_qd01" if uid[0] == "5" else "cn_gf01" 195 | HEADER = copy.deepcopy(_HEADER) 196 | HEADER["Cookie"] = cookie 197 | req = await aiorequests.get( 198 | url=SIGN_INFO_URL, 199 | headers=HEADER, 200 | params={"act_id": "e202009291139501", "region": server_id, "uid": uid}, 201 | ) 202 | return req.json() 203 | 204 | 205 | def old_version_get_ds_token(): 206 | n = "N50pqm7FSy2AkFz2B3TqtuZMJ5TOl3Ep" 207 | i = str(int(time.time())) 208 | r = "".join(random.sample(string.ascii_lowercase + string.digits, 6)) 209 | c = md5("salt=" + n + "&t=" + i + "&r=" + r) 210 | return i + "," + r + "," + c 211 | 212 | 213 | async def mihoyo_bbs_sign(user_id: str, uid: str, Header={}) -> Union[dict, str]: 214 | cookie_info = await get_cookie(user_id, uid, True, True) 215 | server_id = "cn_qd01" if uid[0] == "5" else "cn_gf01" 216 | HEADER = copy.deepcopy(_HEADER) 217 | HEADER["User_Agent"] = ( 218 | "Mozilla/5.0 (Linux; Android 10; MIX 2 Build/QKQ1.190825.002; wv) " 219 | "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 " 220 | "Chrome/83.0.4103.101 Mobile Safari/537.36 miHoYoBBS/2.35.2" 221 | ) 222 | HEADER["Cookie"] = cookie_info.cookie 223 | HEADER["x-rpc-device_id"] = random_hex(32) 224 | HEADER["x-rpc-app_version"] = "2.35.2" 225 | HEADER["x-rpc-client_type"] = "5" 226 | HEADER["X_Requested_With"] = "com.mihoyo.hyperion" 227 | HEADER["DS"] = old_version_get_ds_token() 228 | HEADER["Referer"] = ( 229 | "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html" 230 | "?bbs_auth_required=true&act_id=e202009291139501&utm_source=bbs" 231 | "&utm_medium=mys&utm_campaign=icon" 232 | ) 233 | HEADER.update(Header) 234 | req = await aiorequests.post( 235 | url=SIGN_URL, 236 | headers=HEADER, 237 | json={"act_id": "e202009291139501", "uid": uid, "region": server_id}, 238 | ) 239 | return req.json() 240 | 241 | 242 | async def get_sign_list() -> dict: 243 | req = await aiorequests.get( 244 | url=SIGN_LIST_URL, headers=_HEADER, params={ 245 | "act_id": "e202009291139501"} 246 | ) 247 | return req.json() 248 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/sign_handle.py: -------------------------------------------------------------------------------- 1 | import random 2 | import asyncio 3 | import time 4 | import datetime 5 | from collections import defaultdict 6 | from nonebot import get_bot 7 | 8 | from LittlePaimon.database import MihoyoBBSSub, LastQuery 9 | from LittlePaimon.utils import logger, scheduler 10 | from LittlePaimon.utils.api import get_cookie 11 | 12 | from .api import ( 13 | get_sign_info, 14 | mihoyo_bbs_sign, 15 | get_validate, 16 | get_sign_list, 17 | SIGN_URL, 18 | query_score, 19 | ) 20 | from .config import config 21 | 22 | already = 0 23 | 24 | # 签到函数 25 | 26 | 27 | async def sign_in(user_id, uid, re: bool) -> str: 28 | logger.info("米游社加强签到", "➤", {"用户": user_id, "UID": uid}, "开始执行签到!", True) 29 | cookie = await get_cookie(user_id, uid, True, True) 30 | if not cookie: 31 | return ( 32 | False, 33 | "未绑定私人cookies,绑定方法二选一:\n1.通过米游社扫码绑定:\n请发送指令[原神扫码绑定]\n2.获取cookies的教程:\ndocs.qq.com/doc/DQ3JLWk1vQVllZ2Z1\n获取后,使用[ysb cookie]指令绑定", 34 | ) 35 | # 获得签到信息 36 | sign_info = await get_sign_info(uid, cookie.cookie) 37 | await LastQuery.update_or_create( 38 | user_id=user_id, defaults={"uid": uid, "last_time": datetime.datetime.now()} 39 | ) 40 | # 获取签到列表 41 | sign_list = await get_sign_list() 42 | # 初步校验数据 43 | if sign_info and "data" in sign_info and sign_info["data"]: 44 | sign_info = sign_info["data"] 45 | else: 46 | logger.info( 47 | "米游社加强签到", "➤", {"用户": user_id, "UID": uid}, "出错, 请检查Cookies是否过期!", False 48 | ) 49 | return False, "签到失败...请检查Cookies是否过期!" 50 | # 检测是否已签到 51 | if sign_info["is_sign"]: 52 | logger.info("米游社加强签到", "➤", {"用户": user_id, "UID": uid}, "今天已经签过了", True) 53 | global already 54 | already += 1 55 | getitem = sign_list["data"]["awards"][int(sign_info["total_sign_day"]) - 1][ 56 | "name" 57 | ] 58 | getnum = sign_list["data"]["awards"][int(sign_info["total_sign_day"]) - 1][ 59 | "cnt" 60 | ] 61 | get_im = f"签到获得{getitem}x{getnum}" 62 | sign_missed = sign_info["sign_cnt_missed"] 63 | return True, f"今日已签到!\n{get_im}\n本月漏签次数:{sign_missed}" 64 | # 实际进行签到 65 | Header = {} 66 | for index in range(4): 67 | # 进行一次签到 68 | sign_data = await mihoyo_bbs_sign(user_id=user_id, uid=uid, Header=Header) 69 | # 检测数据 70 | if ( 71 | sign_data 72 | and "data" in sign_data 73 | and sign_data["data"] 74 | and "risk_code" in sign_data["data"] 75 | ): 76 | # 出现校验码 77 | if sign_data["data"]["risk_code"] == 375: 78 | logger.info( 79 | "米游社加强签到", 80 | "➤", 81 | {"用户": user_id, "UID": uid}, 82 | f"该用户出现校验码,开始尝试进行验证...,开始重试第 {index + 1} 次!", 83 | True, 84 | ) 85 | gt = sign_data["data"]["gt"] 86 | challenge = sign_data["data"]["challenge"] 87 | validate, challeng = await get_validate( 88 | gt, challenge, SIGN_URL, uid, re 89 | ) 90 | if (validate is not None) and (challeng is not None): 91 | delay = 10 + random.randint(1, 10) 92 | Header["x-rpc-challenge"] = challeng 93 | Header["x-rpc-validate"] = validate 94 | Header["x-rpc-seccode"] = f"{validate}|jordan" 95 | logger.info( 96 | "米游社加强签到", 97 | "➤", 98 | {"用户": user_id, "UID": uid}, 99 | f"已获取验证码, 等待时间{delay}秒", 100 | True, 101 | ) 102 | await asyncio.sleep(delay) 103 | else: 104 | delay = 600 + random.randint(1, 60) 105 | logger.info( 106 | "米游社加强签到", 107 | "➤", 108 | {"用户": user_id, "UID": uid}, 109 | f"未获取验证码,等待{delay}秒后重试...", 110 | False, 111 | ) 112 | await asyncio.sleep(delay) 113 | continue 114 | # 成功签到! 115 | else: 116 | if index == 0: 117 | logger.info( 118 | "米游社加强签到", "➤", {"用户": user_id, "UID": uid}, f"该用户无校验码!", True 119 | ) 120 | result = "[无验证]" 121 | else: 122 | logger.info( 123 | "米游社加强签到", 124 | "➤", 125 | {"用户": user_id, "UID": uid}, 126 | f"该用户重试 {index} 次验证成功!", 127 | True, 128 | ) 129 | result = "[有验证]" 130 | break 131 | # 重试超过阈值 132 | else: 133 | logger.info( 134 | "米游社加强签到", "➤", {"用户": user_id, "UID": uid}, f"超过请求阈值...", False 135 | ) 136 | return False, "签到失败...请求失败!\n请过段时间使用签到或手动至米游社进行签到" 137 | # 签到失败 138 | else: 139 | result = "签到失败!" 140 | logger.info( 141 | "米游社加强签到", "➤", {"用户": user_id, "UID": uid}, f"签到失败, 结果: {result}", False 142 | ) 143 | return False, result 144 | # 获取签到列表 145 | status = sign_data["message"] 146 | getitem = sign_list["data"]["awards"][int(sign_info["total_sign_day"])]["name"] 147 | getnum = sign_list["data"]["awards"][int(sign_info["total_sign_day"])]["cnt"] 148 | get_im = f"本次签到获得{getitem}x{getnum}" 149 | new_sign_info = await get_sign_info(uid, cookie.cookie) 150 | new_sign_info = new_sign_info["data"] 151 | if new_sign_info["is_sign"]: 152 | mes_im = "签到成功" 153 | else: 154 | result = f"签到失败, 状态为:{status}" 155 | return False, result 156 | sign_missed = sign_info["sign_cnt_missed"] 157 | result = f"{mes_im}{result}!\n{get_im}\n本月漏签次数:{sign_missed}" 158 | logger.info( 159 | "米游社加强签到", 160 | "➤", 161 | {"用户": user_id, "UID": uid}, 162 | f"签到完成, 结果: {mes_im}, 漏签次数: {sign_missed}", 163 | True, 164 | ) 165 | return True, result 166 | 167 | 168 | @scheduler.scheduled_job( 169 | "cron", hour=config.enable_hour, minute=config.enable_minute, misfire_grace_time=10 170 | ) 171 | async def _(): 172 | await bbs_auto_sign() 173 | 174 | 175 | async def bbs_auto_sign(): 176 | """ 177 | 指定时间,执行所有米游社原神签到任务 178 | """ 179 | if not config.enable: 180 | return 181 | t = time.time() # 计时用 182 | subs = await MihoyoBBSSub.filter(sub_event="米游社验证签到").all() 183 | if not subs: 184 | # 如果没有米游社原神签到订阅,则不执行签到任务 185 | return 186 | logger.info( 187 | "米游社加强签到", 188 | f"开始执行米游社加强签到,共{len(subs)}个任务,预计花费{round(100 * len(subs) / 60, 2)}分钟", 189 | ) 190 | coin_result_group = defaultdict(list) 191 | for sub in subs: 192 | im, result = await sign_in(str(sub.user_id), sub.uid, True) 193 | if (not im) and ("Cookie" in result): 194 | sub_data = { 195 | "user_id": str(sub.user_id), 196 | "uid": sub.uid, 197 | "sub_event": "米游社验证签到", 198 | } 199 | if sub := await MihoyoBBSSub.get_or_none(**sub_data): 200 | logger.info( 201 | "米游社加强签到", 202 | "➤", 203 | {"用户": str(sub.user_id), "UID": sub.uid}, 204 | "ck失效已经自动取消签到", 205 | False, 206 | ) 207 | await sub.delete() 208 | result = result if im else f"UID{sub.uid}{result}" 209 | if sub.user_id != sub.group_id: 210 | coin_result_group[sub.group_id].append( 211 | { 212 | "user_id": sub.user_id, 213 | "uid": sub.uid, 214 | "result": "失败" not in result and "Cookies" not in result, 215 | } 216 | ) 217 | await asyncio.sleep(random.randint(15, 25)) 218 | if config.vaapikai == "rr": 219 | _, jifen = await query_score() 220 | elif config.vaapikai == "dsf": 221 | jifen = "第三方验证" 222 | else: 223 | jifen = "错误的配置" 224 | logger.info("验证", "➤", "", "错误的配置", False) 225 | for group_id, result_list in coin_result_group.items(): 226 | result_num = len(result_list) 227 | if result_fail := len( 228 | [result for result in result_list if not result["result"]] 229 | ): 230 | fails = "\n".join( 231 | result["uid"] for result in result_list if not result["result"] 232 | ) 233 | msg = f"本群米游社签到共{result_num}个任务,其中成功{result_num - result_fail}个,失败{result_fail}个,失败的UID列表:\n{fails}\n方式:{jifen}" 234 | else: 235 | msg = f"本群米游社签到共{result_num}个任务,已全部完成\n方式:{jifen}" 236 | try: 237 | await get_bot().send_group_msg(group_id=int(group_id), message=msg) 238 | except Exception as e: 239 | logger.info("米游社加强签到", "➤➤", {"群": group_id}, f"发送结果失败: {e}", False) 240 | await asyncio.sleep(random.randint(3, 6)) 241 | 242 | logger.info("米游社加强签到", f"获取完成,共花费{round((time.time() - t) / 60, 2)}分钟") 243 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/geetest/static/gt.js: -------------------------------------------------------------------------------- 1 | "v0.4.6 Geetest Inc."; 2 | 3 | (function (window) { 4 | "use strict"; 5 | if (typeof window === 'undefined') { 6 | throw new Error('Geetest requires browser environment'); 7 | } 8 | 9 | var document = window.document; 10 | var Math = window.Math; 11 | var head = document.getElementsByTagName("head")[0]; 12 | 13 | function _Object(obj) { 14 | this._obj = obj; 15 | } 16 | 17 | _Object.prototype = { 18 | _each: function (process) { 19 | var _obj = this._obj; 20 | for (var k in _obj) { 21 | if (_obj.hasOwnProperty(k)) { 22 | process(k, _obj[k]); 23 | } 24 | } 25 | return this; 26 | } 27 | }; 28 | 29 | function Config(config) { 30 | var self = this; 31 | new _Object(config)._each(function (key, value) { 32 | self[key] = value; 33 | }); 34 | } 35 | 36 | Config.prototype = { 37 | api_server: 'api.geetest.com', 38 | protocol: 'http://', 39 | typePath: '/gettype.php', 40 | fallback_config: { 41 | slide: { 42 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 43 | type: 'slide', 44 | slide: '/static/js/geetest.0.0.0.js' 45 | }, 46 | fullpage: { 47 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 48 | type: 'fullpage', 49 | fullpage: '/static/js/fullpage.0.0.0.js' 50 | } 51 | }, 52 | _get_fallback_config: function () { 53 | var self = this; 54 | if (isString(self.type)) { 55 | return self.fallback_config[self.type]; 56 | } else if (self.new_captcha) { 57 | return self.fallback_config.fullpage; 58 | } else { 59 | return self.fallback_config.slide; 60 | } 61 | }, 62 | _extend: function (obj) { 63 | var self = this; 64 | new _Object(obj)._each(function (key, value) { 65 | self[key] = value; 66 | }) 67 | } 68 | }; 69 | var isNumber = function (value) { 70 | return (typeof value === 'number'); 71 | }; 72 | var isString = function (value) { 73 | return (typeof value === 'string'); 74 | }; 75 | var isBoolean = function (value) { 76 | return (typeof value === 'boolean'); 77 | }; 78 | var isObject = function (value) { 79 | return (typeof value === 'object' && value !== null); 80 | }; 81 | var isFunction = function (value) { 82 | return (typeof value === 'function'); 83 | }; 84 | 85 | var callbacks = {}; 86 | var status = {}; 87 | 88 | var random = function () { 89 | return parseInt(Math.random() * 10000) + (new Date()).valueOf(); 90 | }; 91 | 92 | var loadScript = function (url, cb) { 93 | var script = document.createElement("script"); 94 | script.charset = "UTF-8"; 95 | script.async = true; 96 | 97 | script.onerror = function () { 98 | cb(true); 99 | }; 100 | var loaded = false; 101 | script.onload = script.onreadystatechange = function () { 102 | if (!loaded && 103 | (!script.readyState || 104 | "loaded" === script.readyState || 105 | "complete" === script.readyState)) { 106 | 107 | loaded = true; 108 | setTimeout(function () { 109 | cb(false); 110 | }, 0); 111 | } 112 | }; 113 | script.src = url; 114 | head.appendChild(script); 115 | }; 116 | 117 | var normalizeDomain = function (domain) { 118 | return domain.replace(/^https?:\/\/|\/$/g, ''); 119 | }; 120 | var normalizePath = function (path) { 121 | path = path.replace(/\/+/g, '/'); 122 | if (path.indexOf('/') !== 0) { 123 | path = '/' + path; 124 | } 125 | return path; 126 | }; 127 | var normalizeQuery = function (query) { 128 | if (!query) { 129 | return ''; 130 | } 131 | var q = '?'; 132 | new _Object(query)._each(function (key, value) { 133 | if (isString(value) || isNumber(value) || isBoolean(value)) { 134 | q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; 135 | } 136 | }); 137 | if (q === '?') { 138 | q = ''; 139 | } 140 | return q.replace(/&$/, ''); 141 | }; 142 | var makeURL = function (protocol, domain, path, query) { 143 | domain = normalizeDomain(domain); 144 | 145 | var url = normalizePath(path) + normalizeQuery(query); 146 | if (domain) { 147 | url = protocol + domain + url; 148 | } 149 | 150 | return url; 151 | }; 152 | 153 | var load = function (protocol, domains, path, query, cb) { 154 | var tryRequest = function (at) { 155 | 156 | var url = makeURL(protocol, domains[at], path, query); 157 | loadScript(url, function (err) { 158 | if (err) { 159 | if (at >= domains.length - 1) { 160 | cb(true); 161 | } else { 162 | tryRequest(at + 1); 163 | } 164 | } else { 165 | cb(false); 166 | } 167 | }); 168 | }; 169 | tryRequest(0); 170 | }; 171 | 172 | 173 | var jsonp = function (domains, path, config, callback) { 174 | if (isObject(config.getLib)) { 175 | config._extend(config.getLib); 176 | callback(config); 177 | return; 178 | } 179 | if (config.offline) { 180 | callback(config._get_fallback_config()); 181 | return; 182 | } 183 | 184 | var cb = "geetest_" + random(); 185 | window[cb] = function (data) { 186 | if (data.status == 'success') { 187 | callback(data.data); 188 | } else if (!data.status) { 189 | callback(data); 190 | } else { 191 | callback(config._get_fallback_config()); 192 | } 193 | window[cb] = undefined; 194 | try { 195 | delete window[cb]; 196 | } catch (e) { 197 | } 198 | }; 199 | load(config.protocol, domains, path, { 200 | gt: config.gt, 201 | callback: cb 202 | }, function (err) { 203 | if (err) { 204 | callback(config._get_fallback_config()); 205 | } 206 | }); 207 | }; 208 | 209 | var throwError = function (errorType, config) { 210 | var errors = { 211 | networkError: '网络错误', 212 | gtTypeError: 'gt字段不是字符串类型' 213 | }; 214 | if (typeof config.onError === 'function') { 215 | config.onError(errors[errorType]); 216 | } else { 217 | throw new Error(errors[errorType]); 218 | } 219 | }; 220 | 221 | var detect = function () { 222 | return window.Geetest || document.getElementById("gt_lib"); 223 | }; 224 | 225 | if (detect()) { 226 | status.slide = "loaded"; 227 | } 228 | 229 | window.initGeetest = function (userConfig, callback) { 230 | 231 | var config = new Config(userConfig); 232 | 233 | if (userConfig.https) { 234 | config.protocol = 'https://'; 235 | } else if (!userConfig.protocol) { 236 | config.protocol = window.location.protocol + '//'; 237 | } 238 | 239 | if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || 240 | userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { 241 | config.apiserver = 'yumchina.geetest.com/'; 242 | config.api_server = 'yumchina.geetest.com'; 243 | } 244 | 245 | if (isObject(userConfig.getType)) { 246 | config._extend(userConfig.getType); 247 | } 248 | jsonp([config.api_server || config.apiserver], config.typePath, config, function (newConfig) { 249 | var type = newConfig.type; 250 | var init = function () { 251 | config._extend(newConfig); 252 | callback(new window.Geetest(config)); 253 | }; 254 | 255 | callbacks[type] = callbacks[type] || []; 256 | var s = status[type] || 'init'; 257 | if (s === 'init') { 258 | status[type] = 'loading'; 259 | 260 | callbacks[type].push(init); 261 | 262 | load(config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { 263 | if (err) { 264 | status[type] = 'fail'; 265 | throwError('networkError', config); 266 | } else { 267 | status[type] = 'loaded'; 268 | var cbs = callbacks[type]; 269 | for (var i = 0, len = cbs.length; i < len; i = i + 1) { 270 | var cb = cbs[i]; 271 | if (isFunction(cb)) { 272 | cb(); 273 | } 274 | } 275 | callbacks[type] = []; 276 | } 277 | }); 278 | } else if (s === "loaded") { 279 | init(); 280 | } else if (s === "fail") { 281 | throwError('networkError', config); 282 | } else if (s === "loading") { 283 | callbacks[type].push(init); 284 | } 285 | }); 286 | 287 | }; 288 | 289 | 290 | })(window); -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/sr_api.py: -------------------------------------------------------------------------------- 1 | from email import header 2 | import random 3 | import asyncio 4 | import time 5 | import copy 6 | from collections import defaultdict 7 | from nonebot import get_bot 8 | from typing import Union, Dict 9 | from LittlePaimon.database import MihoyoBBSSub 10 | from LittlePaimon.utils import logger, scheduler 11 | from LittlePaimon.utils.api import get_cookie 12 | from LittlePaimon.utils.requests import aiorequests 13 | from LittlePaimon.plugins.star_rail_info.data_handle import get_uid 14 | from .api import ( 15 | _HEADER, 16 | get_validate, 17 | query_score, 18 | old_version_get_ds_token, 19 | ) 20 | from .config import config 21 | 22 | # 列表 23 | OLD_URL = "https://api-takumi.mihoyo.com" 24 | STAR_RAIL_SIGN_LIST_URL = f"{OLD_URL}/event/luna/home" 25 | # 获得签到信息 26 | STAR_RAIL_SIGN_INFO_URL = f"{OLD_URL}/event/luna/info" 27 | # 签到 28 | STAR_RAIL_SIGN_URL = f"{OLD_URL}/event/luna/sign" 29 | 30 | 31 | async def sr_mihoyo_bbs_sign(uid: str, ck: str, Header={}) -> Union[dict, str]: 32 | HEADER = copy.deepcopy(_HEADER) 33 | HEADER["Cookie"] = ck 34 | HEADER["x-rpc-app_version"] = "2.44.1" 35 | HEADER["x-rpc-client_type"] = "5" 36 | HEADER["X_Requested_With"] = "com.mihoyo.hyperion" 37 | HEADER["DS"] = old_version_get_ds_token() 38 | HEADER["Referer"] = "https://webstatic.mihoyo.com" 39 | HEADER.update(Header) 40 | data = await aiorequests.post( 41 | url=STAR_RAIL_SIGN_URL, 42 | headers=HEADER, 43 | json={ 44 | "act_id": "e202304121516551", 45 | "region": "prod_gf_cn", 46 | "uid": uid, 47 | "lang": "zh-cn", 48 | }, 49 | ) 50 | return data.json() 51 | 52 | 53 | async def sr_get_sign_info(uid, ck): 54 | HEADER = copy.deepcopy(_HEADER) 55 | HEADER["Cookie"] = ck 56 | data = await aiorequests.get( 57 | url=STAR_RAIL_SIGN_INFO_URL, 58 | headers=HEADER, 59 | params={ 60 | "act_id": "e202304121516551", 61 | "lang": "zh-cn", 62 | "region": "prod_gf_cn", 63 | "uid": uid, 64 | }, 65 | ) 66 | return data.json() 67 | 68 | 69 | async def sr_get_sign_list() -> dict: 70 | data = await aiorequests.get( 71 | url=STAR_RAIL_SIGN_LIST_URL, 72 | headers=_HEADER, 73 | params={ 74 | "act_id": "e202304121516551", 75 | "lang": "zh-cn", 76 | }, 77 | ) 78 | return data.json() 79 | 80 | 81 | already = 0 82 | 83 | # 签到函数 84 | 85 | 86 | async def sr_sign_in(user_id, uid, uid2, re: bool) -> str: 87 | logger.info("星铁加强签到", "➤", {"用户": user_id, "UID": uid}, "开始执行签到!", True) 88 | cookie = await get_cookie(user_id, uid2, True, True) 89 | if not cookie: 90 | return ( 91 | False, 92 | "未绑定私人cookies,绑定方法二选一:\n1.通过原神扫码绑定:\n请发送指令[原神扫码绑定]\n2.获取cookies的教程:\ndocs.qq.com/doc/DQ3JLWk1vQVllZ2Z1\n获取后,使用[ysb cookie]指令绑定", 93 | ) 94 | # 获得签到信息 95 | sign_info = await sr_get_sign_info(uid, cookie.cookie) 96 | print(sign_info) 97 | # 获取签到列表 98 | sign_list = await sr_get_sign_list() 99 | print(sign_list) 100 | # 初步校验数据 101 | if sign_info and "data" in sign_info and sign_info["data"]: 102 | sign_info = sign_info["data"] 103 | else: 104 | logger.info( 105 | "星铁加强签到", "➤", {"用户": user_id, "UID": uid}, "出错, 请检查Cookies是否过期!", False 106 | ) 107 | return False, "签到失败...请检查Cookies是否过期!" 108 | # 检测是否已签到 109 | if sign_info["is_sign"]: 110 | logger.info("星铁加强签到", "➤", {"用户": user_id, "UID": uid}, "今天已经签过了", True) 111 | global already 112 | already += 1 113 | getitem = sign_list["data"]["awards"][int(sign_info["total_sign_day"]) - 1][ 114 | "name" 115 | ] 116 | getnum = sign_list["data"]["awards"][int(sign_info["total_sign_day"]) - 1][ 117 | "cnt" 118 | ] 119 | get_im = f"签到获得{getitem}x{getnum}" 120 | sign_missed = sign_info["sign_cnt_missed"] 121 | return True, f"今日已签到!\n{get_im}\n本月漏签次数:{sign_missed}" 122 | # 实际进行签到 123 | Header = {} 124 | for index in range(4): 125 | # 进行一次签到 126 | sign_data = await sr_mihoyo_bbs_sign( 127 | uid=uid, 128 | ck=cookie.cookie, 129 | Header=Header, 130 | ) 131 | # 检测数据 132 | if ( 133 | sign_data 134 | and "data" in sign_data 135 | and sign_data["data"] 136 | and "risk_code" in sign_data["data"] 137 | ): 138 | # 出现校验码 139 | if sign_data["data"]["risk_code"] == 5001: 140 | logger.info( 141 | "星铁加强签到", 142 | "➤", 143 | {"用户": user_id, "UID": uid}, 144 | f"该用户出现校验码,开始尝试进行验证...,开始重试第 {index + 1} 次!", 145 | True, 146 | ) 147 | gt = sign_data["data"]["gt"] 148 | challenge = sign_data["data"]["challenge"] 149 | validate, challeng = await get_validate( 150 | gt, challenge, STAR_RAIL_SIGN_URL, uid, re 151 | ) 152 | if (validate is not None) and (challeng is not None): 153 | delay = 10 + random.randint(1, 10) 154 | Header["x-rpc-challenge"] = challeng 155 | Header["x-rpc-validate"] = validate 156 | Header["x-rpc-seccode"] = f"{validate}|jordan" 157 | logger.info( 158 | "星铁加强签到", 159 | "➤", 160 | {"用户": user_id, "UID": uid}, 161 | f"已获取验证码, 等待时间{delay}秒", 162 | True, 163 | ) 164 | await asyncio.sleep(delay) 165 | else: 166 | delay = 60 + random.randint(1, 60) 167 | logger.info( 168 | "星铁加强签到", 169 | "➤", 170 | {"用户": user_id, "UID": uid}, 171 | f"未获取验证码,等待{delay}秒后重试...", 172 | False, 173 | ) 174 | await asyncio.sleep(delay) 175 | continue 176 | # 成功签到! 177 | else: 178 | if index == 0: 179 | logger.info( 180 | "星铁加强签到", "➤", {"用户": user_id, "UID": uid}, f"该用户无校验码!", True 181 | ) 182 | result = "[无验证]" 183 | else: 184 | logger.info( 185 | "星铁加强签到", 186 | "➤", 187 | {"用户": user_id, "UID": uid}, 188 | f"该用户重试 {index} 次验证成功!", 189 | True, 190 | ) 191 | result = "[有验证]" 192 | break 193 | # 重试超过阈值 194 | else: 195 | logger.info("星铁加强签到", "➤", {"用户": user_id, "UID": uid}, f"超过请求阈值...", False) 196 | return False, "签到失败...请求失败!\n请过段时间使用签到或手动进行签到" 197 | # 签到失败 198 | else: 199 | result = "签到失败!" 200 | logger.info( 201 | "星铁加强签到", "➤", {"用户": user_id, "UID": uid}, f"签到失败, 结果: {result}", False 202 | ) 203 | return False, result 204 | # 获取签到列表 205 | status = sign_data["message"] 206 | getitem = sign_list["data"]["awards"][int(sign_info["total_sign_day"])]["name"] 207 | getnum = sign_list["data"]["awards"][int(sign_info["total_sign_day"])]["cnt"] 208 | get_im = f"本次签到获得{getitem}x{getnum}" 209 | new_sign_info = await sr_get_sign_info(uid, cookie.cookie) 210 | new_sign_info = new_sign_info["data"] 211 | if new_sign_info["is_sign"]: 212 | mes_im = "签到成功" 213 | else: 214 | result = f"签到失败, 状态为:{status}" 215 | return False, result 216 | sign_missed = sign_info["sign_cnt_missed"] 217 | result = f"{mes_im}{result}!\n{get_im}\n本月漏签次数:{sign_missed}" 218 | logger.info( 219 | "星铁加强签到", 220 | "➤", 221 | {"用户": user_id, "UID": uid}, 222 | f"签到完成, 结果: {mes_im}, 漏签次数: {sign_missed}", 223 | True, 224 | ) 225 | return True, result 226 | 227 | 228 | @scheduler.scheduled_job( 229 | "cron", 230 | hour=config.sr_enable_hour, 231 | minute=config.sr_enable_minute, 232 | misfire_grace_time=10, 233 | ) 234 | async def _(): 235 | await sr_bbs_auto_sign() 236 | 237 | 238 | async def sr_bbs_auto_sign(): 239 | """ 240 | 指定时间,执行所有星铁签到任务 241 | """ 242 | if not config.sr_enable: 243 | return 244 | t = time.time() # 计时用 245 | subs = await MihoyoBBSSub.filter(sub_event="星铁签到").all() 246 | if not subs: 247 | # 如果没有星铁原神签到订阅,则不执行签到任务 248 | return 249 | logger.info( 250 | "星铁加强签到", 251 | f"开始执行星铁加强签到,共{len(subs)}个任务,预计花费{round(100 * len(subs) / 60, 2)}分钟", 252 | ) 253 | coin_result_group = defaultdict(list) 254 | for sub in subs: 255 | uid2 = get_uid(str(sub.user_id)) # 星铁uid 256 | im, result = await sr_sign_in(str(sub.user_id), uid2, sub.uid, True) 257 | if (not im) and ("Cookie" in result): 258 | sub_data = { 259 | "user_id": str(sub.user_id), 260 | "uid": sub.uid, 261 | "sub_event": "星铁签到", 262 | } 263 | if sub := await MihoyoBBSSub.get_or_none(**sub_data): 264 | logger.info( 265 | "星铁加强签到", 266 | "➤", 267 | {"用户": str(sub.user_id), "UID": uid2}, 268 | "ck失效已经自动取消签到", 269 | False, 270 | ) 271 | await sub.delete() 272 | result = result if im else f"UID{uid2}{result}" 273 | if sub.user_id != sub.group_id: 274 | coin_result_group[sub.group_id].append( 275 | { 276 | "user_id": sub.user_id, 277 | "uid": uid2, 278 | "result": "失败" not in result and "Cookies" not in result, 279 | } 280 | ) 281 | await asyncio.sleep(random.randint(15, 25)) 282 | if config.vaapikai == "rr": 283 | _, jifen = await query_score() 284 | elif config.vaapikai == "dsf": 285 | jifen = "第三方验证" 286 | else: 287 | jifen = "错误的配置" 288 | logger.info("验证", "➤", "", "错误的配置", False) 289 | for group_id, result_list in coin_result_group.items(): 290 | result_num = len(result_list) 291 | if result_fail := len( 292 | [result for result in result_list if not result["result"]] 293 | ): 294 | fails = "\n".join( 295 | result["uid"] for result in result_list if not result["result"] 296 | ) 297 | msg = f"本群星铁签到共{result_num}个任务,其中成功{result_num - result_fail}个,失败{result_fail}个,失败的UID列表:\n{fails}\n方式:{jifen}" 298 | else: 299 | msg = f"本群星铁签到共{result_num}个任务,已全部完成\n方式:{jifen}" 300 | try: 301 | await get_bot().send_group_msg(group_id=int(group_id), message=msg) 302 | except Exception as e: 303 | logger.info("星铁加强签到", "➤➤", {"群": group_id}, f"发送结果失败: {e}", False) 304 | await asyncio.sleep(random.randint(3, 6)) 305 | 306 | logger.info("星铁加强签到", f"获取完成,共花费{round((time.time() - t) / 60, 2)}分钟") 307 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/coin_handle.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import datetime 3 | import random 4 | import time 5 | from collections import defaultdict 6 | from typing import Tuple 7 | from nonebot import get_bot 8 | 9 | from LittlePaimon.database import MihoyoBBSSub, LastQuery 10 | from LittlePaimon.utils import logger, scheduler 11 | from LittlePaimon.utils.requests import aiorequests 12 | from LittlePaimon.utils.api import ( 13 | random_text, 14 | random_hex, 15 | get_old_version_ds, 16 | get_ds, 17 | get_cookie, 18 | ) 19 | 20 | from .api import query_score, tilioc 21 | from .config import config 22 | 23 | # 米游社的API列表 24 | bbs_Cookieurl = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}" 25 | bbs_Cookieurl2 = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}" 26 | bbs_Taskslist = "https://bbs-api.mihoyo.com/apihub/sapi/getUserMissionsState" 27 | bbs_Signurl = "https://bbs-api.mihoyo.com/apihub/app/api/signIn" 28 | bbs_Listurl = "https://bbs-api.mihoyo.com/post/api/getForumPostList?forum_id={}&is_good=false&is_hot=false&page_size=20&sort_type=1" 29 | bbs_Detailurl = "https://bbs-api.mihoyo.com/post/api/getPostFull?post_id={}" 30 | bbs_Shareurl = ( 31 | "https://bbs-api.mihoyo.com/apihub/api/getShareConf?entity_id={}&entity_type=1" 32 | ) 33 | bbs_Likeurl = "https://bbs-api.mihoyo.com/apihub/sapi/upvotePost" 34 | 35 | 36 | mihoyo_bbs_List = [ 37 | { 38 | "id": "1", 39 | "forumId": "1", 40 | "name": "崩坏3", 41 | "url": "https://bbs.mihoyo.com/bh3/", 42 | }, 43 | { 44 | "id": "2", 45 | "forumId": "26", 46 | "name": "原神", 47 | "url": "https://bbs.mihoyo.com/ys/", 48 | }, 49 | { 50 | "id": "3", 51 | "forumId": "30", 52 | "name": "崩坏2", 53 | "url": "https://bbs.mihoyo.com/bh2/", 54 | }, 55 | { 56 | "id": "4", 57 | "forumId": "37", 58 | "name": "未定事件簿", 59 | "url": "https://bbs.mihoyo.com/wd/", 60 | }, 61 | { 62 | "id": "5", 63 | "forumId": "34", 64 | "name": "大别野", 65 | "url": "https://bbs.mihoyo.com/dby/", 66 | }, 67 | { 68 | "id": "6", 69 | "forumId": "52", 70 | "name": "崩坏:星穹铁道", 71 | "url": "https://bbs.mihoyo.com/sr/", 72 | }, 73 | {"id": "8", "forumId": "57", "name": "绝区零", 74 | "url": "https://bbs.mihoyo.com/zzz/"}, 75 | ] 76 | 77 | 78 | class MihoyoBBSCoin: 79 | """ 80 | 米游币获取 81 | """ 82 | 83 | def __init__(self, cookies, uid, user_id, re): 84 | self.headers: dict = { 85 | "DS": get_old_version_ds(), 86 | "cookie": cookies, 87 | "x-rpc-client_type": "2", 88 | "x-rpc-app_version": "2.34.1", 89 | "x-rpc-sys_version": "6.0.1", 90 | "x-rpc-channel": "miyousheluodi", 91 | "x-rpc-device_id": random_hex(32), 92 | "x-rpc-device_name": random_text(random.randint(1, 10)), 93 | "x-rpc-device_model": "Mi 10", 94 | "Referer": "https://app.mihoyo.com", 95 | "Host": "bbs-api.mihoyo.com", 96 | "User-Agent": "okhttp/4.8.0", 97 | } 98 | self.header_2 = { 99 | 'Origin': 'https://webstatic.mihoyo.com', 100 | 'Cookie': cookies, 101 | 'x-rpc-app_version': "2.11.1", 102 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS ' 103 | 'X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1', 104 | 'x-rpc-client_type': '5', 105 | 'Referer': 'https://webstatic.mihoyo.com/', 106 | } 107 | self.postsList: list = [] 108 | self.Task_do: dict = { 109 | "bbs_Sign": False, 110 | "bbs_Read_posts": False, 111 | "bbs_Read_posts_num": 3, 112 | "bbs_Like_posts": False, 113 | "bbs_Like_posts_num": 5, 114 | "bbs_Share": False, 115 | } 116 | self.re: bool = re 117 | self.mihoyo_bbs_List: list = mihoyo_bbs_List 118 | self.available_coins: int = 0 119 | self.received_coins: int = 0 120 | self.total_coins: int = 0 121 | self.is_valid: bool = True 122 | self.state: str = "" 123 | self.uid = uid 124 | self.user_id = user_id 125 | 126 | async def run(self) -> Tuple[bool, str]: 127 | """ 128 | 执行米游币获取任务 129 | :return: 获取消息 130 | """ 131 | await self.get_tasks_list() 132 | if not self.is_valid: 133 | return False, self.state 134 | await self.get_list() 135 | w = 0 136 | msg1 = "讨论区签到:已经完成过了~" 137 | msg2 = "浏览帖子:已经完成过了~" 138 | msg3 = "点赞帖子:已经完成过了~" 139 | msg4 = "分享帖子:已经完成过了~" 140 | while self.available_coins != 0 and w < 3: 141 | logger.info("加强米游币自动获取", "➤", "", f"开始第{w+1}次获取信息", True) 142 | if w > 0: 143 | await self.get_tasks_list() 144 | msg1 = await self.signing() 145 | msg2 = await self.read_posts() 146 | msg3 = await self.like_posts() 147 | msg4 = await self.share_post() 148 | w += 1 149 | await asyncio.sleep(5) 150 | resul = "\n米游币获取结果:\n" 151 | if not self.is_valid: 152 | return False, self.state 153 | logger.info("加强米游币自动获取", "➤", "", "获取完毕", True) 154 | result = resul + msg1 + "\n" + msg2 + "\n" + msg3 + "\n" + msg4 155 | return True, result 156 | 157 | async def get_tasks_list(self): 158 | """ 159 | 获取任务列表,用来判断做了哪些任务 160 | """ 161 | data = await aiorequests.get(url=bbs_Taskslist, headers=self.headers) 162 | data = data.json() 163 | if data["retcode"] != 0: 164 | self.is_valid = False 165 | self.state = ( 166 | "Cookie已失效" 167 | if data["retcode"] in [-100, 10001] 168 | else f"出错了:{data['message']} {data['message']}" 169 | ) 170 | logger.info("加强米游币自动获取", f"➤➤{self.state}") 171 | return self.state 172 | self.available_coins = data["data"]["can_get_points"] 173 | self.received_coins = data["data"]["already_received_points"] 174 | self.total_coins = data["data"]["total_points"] 175 | # 如果当日可获取米游币数量为0直接判断全部任务都完成了 176 | if self.available_coins == 0: 177 | self.Task_do["bbs_Sign"] = True 178 | self.Task_do["bbs_Read_posts"] = True 179 | self.Task_do["bbs_Like_posts"] = True 180 | self.Task_do["bbs_Share"] = True 181 | else: 182 | # 如果第0个大于或等于62则直接判定任务没做 183 | if data["data"]["states"][0]["mission_id"] < 62: 184 | for i in data["data"]["states"]: 185 | # 58是讨论区签到 186 | if i["mission_id"] == 58: 187 | if i["is_get_award"]: 188 | self.Task_do["bbs_Sign"] = True 189 | # 59是看帖子 190 | elif i["mission_id"] == 59: 191 | if i["is_get_award"]: 192 | self.Task_do["bbs_Read_posts"] = True 193 | else: 194 | self.Task_do["bbs_Read_posts_num"] -= i["happened_times"] 195 | # 60是给帖子点赞 196 | elif i["mission_id"] == 60: 197 | if i["is_get_award"]: 198 | self.Task_do["bbs_Like_posts"] = True 199 | else: 200 | self.Task_do["bbs_Like_posts_num"] -= i["happened_times"] 201 | # 61是分享帖子 202 | elif i["mission_id"] == 61: 203 | if i["is_get_award"]: 204 | self.Task_do["bbs_Share"] = True 205 | # 分享帖子,是最后一个任务,到这里了下面都是一次性任务,直接跳出循环 206 | break 207 | logger.info( 208 | "加强米游币自动获取", f"➤➤该用户今天还可获取{self.available_coins}个米游币") 209 | 210 | async def get_list(self): 211 | """ 212 | 获取进行操作的帖子列表 213 | :return: 帖子id列表 214 | """ 215 | req = await aiorequests.get( 216 | url=bbs_Listurl.format( 217 | random.choice([bbs["forumId"] for bbs in self.mihoyo_bbs_List]) 218 | ), 219 | headers=self.headers, 220 | ) 221 | data = req.json() 222 | self.postsList = [ 223 | [d["post"]["post_id"], d["post"]["subject"]] 224 | for d in data["data"]["list"][:5] 225 | ] 226 | logger.info("加强米游币自动获取", "➤➤获取帖子列表成功") 227 | 228 | # 进行签到操作 229 | async def signing(self): 230 | """ 231 | 讨论区签到 232 | """ 233 | if self.Task_do["bbs_Sign"]: 234 | return "讨论区签到:完成!" 235 | header = {} 236 | header.update(self.headers) 237 | challenge = None 238 | for i in self.mihoyo_bbs_List: 239 | header["DS"] = get_ds("", {"gids": i["id"]}, True) 240 | req = await aiorequests.post( 241 | url=bbs_Signurl, json={"gids": i["id"]}, headers=header 242 | ) 243 | data = req.json() 244 | if data["retcode"] != 0: 245 | if data["retcode"] == 1034: 246 | logger.info("社区签到触发验证码") 247 | re, challenge = await tilioc(self.header_2, self.uid, self.re) 248 | if re: 249 | header["x-rpc-challenge"] = challenge 250 | if data["retcode"] != 1034: 251 | self.is_valid = False 252 | self.state = ( 253 | "Cookie已失效" 254 | if data["retcode"] in [-100, 10001] 255 | else f"出错了:{data['retcode']} {data['message']}" 256 | if data["retcode"] != 1034 257 | else "遇验证码阻拦" 258 | ) 259 | logger.info("米游币自动获取", f"➤➤{self.state}") 260 | return f"讨论区签到:{self.state}" 261 | if challenge is not None: 262 | challenge = None 263 | header.pop("x-rpc-challenge") 264 | await asyncio.sleep(random.randint(15, 30)) 265 | logger.info("加强米游币自动获取", "➤➤讨论区签到完成") 266 | return "讨论区签到:完成!" 267 | 268 | async def read_posts(self): 269 | """ 270 | 浏览帖子 271 | """ 272 | if self.Task_do["bbs_Read_posts"]: 273 | return "浏览帖子:完成3个!" 274 | num_ok = 0 275 | for i in range(self.Task_do["bbs_Read_posts_num"]): 276 | req = await aiorequests.get( 277 | url=bbs_Detailurl.format(self.postsList[i][0]), headers=self.headers 278 | ) 279 | data = req.json() 280 | if data["message"] == "OK": 281 | num_ok += 1 282 | await asyncio.sleep(random.randint(5, 10)) 283 | logger.info("加强米游币自动获取", "➤➤看帖任务完成") 284 | return f"浏览帖子:完成{str(num_ok)}个!" 285 | 286 | async def like_posts(self): 287 | """ 288 | 点赞帖子 289 | """ 290 | header = {} 291 | header.update(self.headers) 292 | challenge = None 293 | if self.Task_do["bbs_Like_posts"]: 294 | return "点赞帖子:完成5个!" 295 | num_ok = 0 296 | num_cancel = 0 297 | for i in range(self.Task_do["bbs_Like_posts_num"]): 298 | req = await aiorequests.post( 299 | url=bbs_Likeurl, 300 | headers=self.headers, 301 | json={ 302 | "post_id": self.postsList[i][0], 303 | "is_cancel": False, 304 | }, 305 | ) 306 | data = req.json() 307 | if data["message"] == "OK": 308 | num_ok += 1 309 | if challenge is not None: 310 | challenge = None 311 | header.pop("x-rpc-challenge") 312 | elif data["retcode"] == 1034: 313 | logger.info("点赞触发验证码") 314 | re, challenge = await tilioc(self.header_2, self.uid, self.re) 315 | if re: 316 | header["x-rpc-challenge"] = challenge 317 | # 取消点赞 318 | await asyncio.sleep(random.randint(3, 6)) 319 | req = await aiorequests.post( 320 | url=bbs_Likeurl, 321 | headers=self.headers, 322 | json={ 323 | "post_id": self.postsList[i][0], 324 | "is_cancel": True, 325 | }, 326 | ) 327 | data = req.json() 328 | if data["message"] == "OK": 329 | num_cancel += 1 330 | logger.info("米游币加强自动获取", "➤➤点赞任务完成") 331 | await asyncio.sleep(random.randint(5, 10)) 332 | return f'点赞帖子:完成{str(num_ok)}个{",遇验证码" if num_ok == 0 else ""}!' 333 | 334 | async def share_post(self): 335 | """ 336 | 分享帖子 337 | """ 338 | if self.Task_do["bbs_Share"]: 339 | return "分享帖子:完成!" 340 | for _ in range(3): 341 | req = await aiorequests.get( 342 | url=bbs_Shareurl.format(self.postsList[0][0]), headers=self.headers 343 | ) 344 | data = req.json() 345 | if data["message"] == "OK": 346 | return "分享帖子:完成!" 347 | else: 348 | await asyncio.sleep(random.randint(5, 10)) 349 | logger.info("加强米游币自动获取", "➤➤分享任务完成") 350 | await asyncio.sleep(random.randint(5, 10)) 351 | return "分享帖子:完成!" 352 | 353 | 354 | async def mhy_bbs_coin(user_id: str, uid: str, re: bool) -> str: 355 | """ 356 | 执行米游币获取任务 357 | :param user_id: 用户id 358 | :param uid: 原神uid 359 | :return: 结果 360 | """ 361 | # cookie = await PrivateCookie.get_or_none(user_id=user_id, uid=uid) 362 | cookie = await get_cookie(user_id, uid, True, True) 363 | if not cookie: 364 | return "你尚未绑定Cookie和Stoken,请先用ysb指令绑定!" 365 | elif cookie.stoken is None: 366 | return "你绑定Cookie中没有login_ticket,请重新用ysb指令绑定!" 367 | await LastQuery.update_or_create( 368 | user_id=user_id, defaults={"uid": uid, 369 | "last_time": datetime.datetime.now()} 370 | ) 371 | logger.info("加强米游币自动获取", "➤执行", { 372 | "用户": user_id, "UID": uid, "的米游币获取": "......"}) 373 | 374 | get_coin_task = MihoyoBBSCoin(cookie.stoken, uid, user_id, re) 375 | result, msg = await get_coin_task.run() 376 | return msg if result else f"UID{uid}{msg}" 377 | 378 | 379 | @scheduler.scheduled_job( 380 | "cron", hour=config.myb_hour, minute=config.myb_minute, misfire_grace_time=10 381 | ) 382 | async def _(): 383 | await bbs_auto_coin() 384 | 385 | 386 | async def bbs_auto_coin(): 387 | """ 388 | 指定时间,执行所有米游币获取订阅任务, 并将结果分群发送 389 | """ 390 | if not config.myb: 391 | return 392 | t = time.time() 393 | subs = await MihoyoBBSSub.filter(sub_event="米游币验证获取").all() 394 | if not subs: 395 | return 396 | logger.info( 397 | "米游币自动获取", 398 | f"开始执行加强米游币自动获取,共{len(subs)}个任务,预计花费{round(100 * len(subs) / 60, 2)}分钟", 399 | ) 400 | coin_result_group = defaultdict(list) 401 | for sub in subs: 402 | result = await mhy_bbs_coin(str(sub.user_id), sub.uid, True) 403 | if "Cookie" in result: 404 | sub_data = { 405 | "user_id": str(sub.user_id), 406 | "uid": sub.uid, 407 | "sub_event": "米游币验证获取", 408 | } 409 | if sub := await MihoyoBBSSub.get_or_none(**sub_data): 410 | logger.info( 411 | "加强米游币自动获取", 412 | "➤", 413 | {"用户": str(sub.user_id), "UID": sub.uid}, 414 | "ck失效已经自动取消获取", 415 | False, 416 | ) 417 | await sub.delete() 418 | if sub.user_id != sub.group_id: 419 | coin_result_group[sub.group_id].append( 420 | { 421 | "user_id": sub.user_id, 422 | "uid": sub.uid, 423 | "result": "出错" not in result and "Cookie" not in result, 424 | } 425 | ) 426 | if config.vaapikai == "rr": 427 | _, jifen = await query_score() 428 | elif config.vaapikai == "dsf": 429 | jifen = "第三方验证" 430 | else: 431 | jifen = "错误的配置" 432 | logger.info("验证", "➤", "", "错误的配置", False) 433 | for group_id, result_list in coin_result_group.items(): 434 | result_num = len(result_list) 435 | if result_fail := len( 436 | [result for result in result_list if not result["result"]] 437 | ): 438 | fails = "\n".join( 439 | result["uid"] for result in result_list if not result["result"] 440 | ) 441 | msg = f"本群加强米游币自动获取共{result_num}个任务,其中成功{result_num - result_fail}个,失败{result_fail}个,失败的UID列表:\n{fails}\n方式:{jifen}" 442 | else: 443 | msg = f"本群加强米游币自动获取共{result_num}个任务,已全部完成\n方式:{jifen}" 444 | try: 445 | await get_bot().send_group_msg(group_id=int(group_id), message=msg) 446 | except Exception as e: 447 | logger.info("米游币加强自动获取", "➤➤", { 448 | "群": group_id}, f"发送米游币自动结果失败: {e}", False) 449 | await asyncio.sleep(random.randint(3, 6)) 450 | 451 | logger.info( 452 | "米游币加强自动获取", f"获取完成,共花费{round((time.time() - t) / 60, 2)}分钟") 453 | -------------------------------------------------------------------------------- /LittlePaimon_plugin_Abyss/main.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from nonebot import on_command 4 | from nonebot.adapters.onebot.v11 import ( 5 | PrivateMessageEvent, 6 | GroupMessageEvent, 7 | Bot, 8 | Message, 9 | ) 10 | from nonebot.permission import SUPERUSER 11 | from nonebot.rule import to_me 12 | from nonebot.plugin import PluginMetadata 13 | 14 | from LittlePaimon.database import PrivateCookie, MihoyoBBSSub 15 | from LittlePaimon.utils import logger, NICKNAME 16 | from LittlePaimon.utils.message import CommandUID, CommandSwitch, CommandPlayer 17 | 18 | from .static import get_pass_challenge 19 | from .sr_api import sr_sign_in, sr_bbs_auto_sign 20 | from .ssbq import handle_ssbq, handle_ssbq2 21 | from .coin_handle import mhy_bbs_coin, bbs_auto_coin 22 | from .config import config 23 | from .sign_handle import sign_in, bbs_auto_sign 24 | from . import web_page, web_api 25 | 26 | 27 | __plugin_meta__ = PluginMetadata( 28 | name="加强签到", 29 | description="加强签到", 30 | usage="加强签到", 31 | extra={ 32 | "author": "Edelweiss", 33 | "version": "2.0", 34 | "priority": 7, 35 | }, 36 | ) 37 | 38 | 39 | sign = on_command( 40 | "验证签到", 41 | priority=7, 42 | block=True, 43 | state={ 44 | "pm_name": "验证签到", 45 | "pm_description": "*执行米游社签到操作,并对验证码进行验证", 46 | "pm_usage": "验证签到(uid)[on|off]", 47 | "pm_priority": 1, 48 | }, 49 | ) 50 | 51 | 52 | all_sign = on_command( 53 | "全部验证重签", 54 | priority=7, 55 | block=True, 56 | permission=SUPERUSER, 57 | rule=to_me(), 58 | state={ 59 | "pm_name": "米游社验证重签", 60 | "pm_description": "重签全部米游社签到任务,需超级用户权限", 61 | "pm_usage": "@Bot 全部验证重签", 62 | "pm_priority": 2, 63 | }, 64 | ) 65 | 66 | ti = on_command( 67 | "验证体力", 68 | priority=7, 69 | block=True, 70 | state={ 71 | "pm_name": "体力", 72 | "pm_description": "*验证体力(uid)", 73 | "pm_usage": "查看原神体力,并对验证码进行验证", 74 | "pm_priority": 3, 75 | }, 76 | ) 77 | 78 | get_coin = on_command( 79 | "验证米游币获取", 80 | priority=7, 81 | block=True, 82 | state={ 83 | "pm_name": "验证米游币获取", 84 | "pm_description": "*执行米游币任务操作,并对验证码进行验证", 85 | "pm_usage": "验证米游币获取(uid)[on|off]", 86 | "pm_priority": 4, 87 | }, 88 | ) 89 | 90 | all_coin = on_command( 91 | "全部验证重做", 92 | priority=7, 93 | block=True, 94 | permission=SUPERUSER, 95 | rule=to_me(), 96 | state={ 97 | "pm_name": "全部验证重做", 98 | "pm_description": "重做全部米游币获取任务,需超级用户权限", 99 | "pm_usage": "@Bot 全部验证重做", 100 | "pm_priority": 5, 101 | }, 102 | ) 103 | 104 | 105 | sr_sign = on_command( 106 | "sr验证签到", 107 | priority=7, 108 | block=True, 109 | state={ 110 | "pm_name": "sr验证签到", 111 | "pm_description": "*执行星铁签到操作,并对验证码进行验证", 112 | "pm_usage": "sr验证签到[on|off]", 113 | "pm_priority": 6, 114 | }, 115 | ) 116 | 117 | sr_all_sign = on_command( 118 | "sr全部验证重签", 119 | priority=7, 120 | block=True, 121 | permission=SUPERUSER, 122 | rule=to_me(), 123 | state={ 124 | "pm_name": "sr米游社验证重签", 125 | "pm_description": "重签全部星铁签到任务,需超级用户权限", 126 | "pm_usage": "@Bot sr全部验证重签", 127 | "pm_priority": 7, 128 | }, 129 | ) 130 | get_pass = on_command( 131 | "米游社过码", 132 | priority=8, 133 | rule=to_me(), 134 | state={ 135 | "pm_name": "米游社过码", 136 | "pm_description": "进行一次米游社社区验证码验证,可能解开一些过不去的地方", 137 | "pm_usage": "米游社过码", 138 | "pm_priority": 8, 139 | }, 140 | ) 141 | 142 | 143 | list = [] 144 | sr_list = [] 145 | 146 | 147 | @get_pass.handle() 148 | async def _( 149 | bot: Bot, 150 | event: Union[GroupMessageEvent, PrivateMessageEvent], 151 | uid=CommandUID(), 152 | ): 153 | if isinstance(event, GroupMessageEvent): 154 | groupid = event.group_id 155 | else: 156 | groupid = "" 157 | if ( 158 | (groupid in config.whitelist) 159 | or (event.user_id in config.whlist) 160 | or (str(event.user_id) in bot.config.superusers) 161 | or (config.vaapikai == "rg") 162 | ): 163 | if config.vaapikai == "rg": 164 | await get_pass.send( 165 | f"请前往{config.ip}/validate?uid={uid}进行手动验证,如果无法访问请刷新,直到出结果", 166 | at_sender=True, 167 | ) 168 | data = await get_pass_challenge(str(event.user_id), uid) 169 | await get_pass.finish(data, at_sender=True) 170 | 171 | 172 | @sr_sign.handle() 173 | async def _( 174 | bot: Bot, 175 | event: Union[GroupMessageEvent, PrivateMessageEvent], 176 | switch=CommandSwitch(), 177 | ): 178 | if isinstance(event, GroupMessageEvent): 179 | groupid = event.group_id 180 | else: 181 | groupid = "" 182 | if ( 183 | (groupid in config.whitelist) 184 | or (event.user_id in config.whlist) 185 | or (str(event.user_id) in bot.config.superusers) 186 | or (config.vaapikai == "rg") 187 | ): 188 | from LittlePaimon.plugins.star_rail_info.data_handle import get_uid 189 | 190 | # 一样的函数名真是太屮了 191 | uid2 = get_uid(str(event.user_id)) # 星铁uid 192 | from LittlePaimon.utils.message import get_uid # 原神uid 用于获取ck 193 | 194 | uid = await get_uid(event=event) 195 | if not uid2: 196 | await sr_sign.finish("请先使用命令[星铁绑定uid]来绑定星穹铁道UID") 197 | if not uid: 198 | await sr_sign.finish("请先使用命令[ysb uid]来绑定原神UID") 199 | if switch is None: 200 | if f"{event.user_id}-{uid2}" in sr_list: 201 | await sr_sign.finish(f"你已经有验证任务了,{NICKNAME}会忙不过来的", at_sender=True) 202 | else: 203 | GF = f"{NICKNAME}开始为UID{uid2}执行加强星铁签到" 204 | if config.vaapikai == "rg": 205 | GF += f",\n请前往{config.ip}/validate?uid={uid2}进行手动验证,如果无法访问请刷新,直到出结果" 206 | await sr_sign.send(GF, at_sender=True) 207 | logger.info( 208 | "加强星铁签到", "➤", { 209 | "用户": str(event.user_id), "uid": uid2}, "执行签到", True 210 | ) 211 | sr_list.append(f"{event.user_id}-{uid2}") 212 | _, result = await sr_sign_in(str(event.user_id), uid2, uid, False) 213 | sr_list.remove(f"{event.user_id}-{uid2}") 214 | await sr_sign.finish(result, at_sender=True) 215 | elif isinstance(event, GroupMessageEvent) and ( 216 | (groupid in config.whitelist) 217 | or (event.user_id in config.whlist) 218 | or (str(event.user_id) in bot.config.superusers) 219 | ): 220 | sub_data = { 221 | "user_id": event.user_id, 222 | "uid": uid, 223 | "sub_event": "星铁签到", 224 | } 225 | if switch: 226 | # switch为开启,则添加订阅 227 | if await PrivateCookie.get_or_none(user_id=str(event.user_id), uid=uid): 228 | await MihoyoBBSSub.update_or_create( 229 | **sub_data, 230 | defaults={ 231 | "group_id": event.group_id 232 | if isinstance(event, GroupMessageEvent) 233 | else event.user_id 234 | }, 235 | ) 236 | logger.info( 237 | "加强星铁自动签到", 238 | "➤", 239 | {"用户": str(event.user_id), "uid": uid2}, 240 | "开启成功", 241 | True, 242 | ) 243 | await sr_sign.finish(f"UID{uid2}开启加强星铁签到", at_sender=True) 244 | else: 245 | await sr_sign.finish( 246 | f"UID{uid2}尚未绑定Cookie!请先使用ysb指令绑定吧!", at_sender=True 247 | ) 248 | else: 249 | # switch为关闭,则取消订阅 250 | if sub := await MihoyoBBSSub.get_or_none(**sub_data): 251 | await sub.delete() 252 | logger.info( 253 | "加强星铁自动签到", 254 | "➤", 255 | {"用户": str(event.user_id), "uid": uid2}, 256 | "关闭成功", 257 | True, 258 | ) 259 | await sr_sign.finish(f"UID{uid2}关闭加强星铁自动签到成功", at_sender=True) 260 | else: 261 | await sr_sign.finish(f"UID{uid2}尚未开启加强星铁自动签到,无需关闭!", at_sender=True) 262 | await sr_sign.finish(config.hfu, at_sender=True) 263 | 264 | 265 | @sr_all_sign.handle() 266 | async def _(event: Union[GroupMessageEvent, PrivateMessageEvent]): 267 | await sr_all_sign.send(f"{NICKNAME}开始执行全部加强星铁重签,需要一定时间...") 268 | await sr_bbs_auto_sign() 269 | 270 | 271 | @sign.handle() 272 | async def _( 273 | bot: Bot, 274 | event: Union[GroupMessageEvent, PrivateMessageEvent], 275 | uid=CommandUID(), 276 | switch=CommandSwitch(), 277 | ): 278 | if isinstance(event, GroupMessageEvent): 279 | groupid = event.group_id 280 | else: 281 | groupid = "" 282 | if ( 283 | (groupid in config.whitelist) 284 | or (event.user_id in config.whlist) 285 | or (str(event.user_id) in bot.config.superusers) 286 | or (config.vaapikai == "rg") 287 | ): 288 | if switch is None: 289 | if f"{event.user_id}-{uid}" in list: 290 | await sign.finish(f"你已经有验证任务了,{NICKNAME}会忙不过来的", at_sender=True) 291 | else: 292 | GF = f"{NICKNAME}开始为UID{uid}执行加强米游社签到" 293 | if config.vaapikai == "rg": 294 | GF += f",\n请前往{config.ip}/validate?uid={uid}进行手动验证,如果无法访问请刷新,直到出结果" 295 | await sign.send(GF, at_sender=True) 296 | logger.info( 297 | "加强米游社签到", "➤", { 298 | "用户": str(event.user_id), "uid": uid}, "执行签到", True 299 | ) 300 | list.append(f"{event.user_id}-{uid}") 301 | _, result = await sign_in(str(event.user_id), uid, False) 302 | list.remove(f"{event.user_id}-{uid}") 303 | await sign.finish(result, at_sender=True) 304 | elif isinstance(event, GroupMessageEvent) and ( 305 | (groupid in config.whitelist) 306 | or (event.user_id in config.whlist) 307 | or (str(event.user_id) in bot.config.superusers) 308 | ): 309 | sub_data = {"user_id": event.user_id, 310 | "uid": uid, "sub_event": "米游社验证签到"} 311 | if switch: 312 | # switch为开启,则添加订阅 313 | if await PrivateCookie.get_or_none(user_id=str(event.user_id), uid=uid): 314 | await MihoyoBBSSub.update_or_create( 315 | **sub_data, 316 | defaults={ 317 | "group_id": event.group_id 318 | if isinstance(event, GroupMessageEvent) 319 | else event.user_id 320 | }, 321 | ) 322 | logger.info( 323 | "加强米游社自动签到", 324 | "➤", 325 | {"用户": str(event.user_id), "uid": uid}, 326 | "开启成功", 327 | True, 328 | ) 329 | await sign.finish(f"UID{uid}开启加强米游社签到", at_sender=True) 330 | else: 331 | await sign.finish( 332 | f"UID{uid}尚未绑定Cookie!请先使用ysb指令绑定吧!", at_sender=True 333 | ) 334 | else: 335 | # switch为关闭,则取消订阅 336 | if sub := await MihoyoBBSSub.get_or_none(**sub_data): 337 | await sub.delete() 338 | logger.info( 339 | "加强米游社自动签到", 340 | "➤", 341 | {"用户": str(event.user_id), "uid": uid}, 342 | "关闭成功", 343 | True, 344 | ) 345 | await sign.finish(f"UID{uid}关闭加强米游社自动签到成功", at_sender=True) 346 | else: 347 | await sign.finish(f"UID{uid}尚未开启加强米游社自动签到,无需关闭!", at_sender=True) 348 | await sign.finish(config.hfu, at_sender=True) 349 | 350 | 351 | @all_sign.handle() 352 | async def _(event: Union[GroupMessageEvent, PrivateMessageEvent]): 353 | await all_sign.send(f"{NICKNAME}开始执行全部加强重签,需要一定时间...") 354 | await bbs_auto_sign() 355 | 356 | 357 | @ti.handle() 358 | async def _( 359 | bot: Bot, 360 | event: Union[GroupMessageEvent, PrivateMessageEvent], 361 | players=CommandPlayer(), 362 | ): 363 | if isinstance(event, GroupMessageEvent): 364 | groupid = event.group_id 365 | else: 366 | groupid = "" 367 | if ( 368 | (groupid in config.whitelist) 369 | or (event.user_id in config.whlist) 370 | or (str(event.user_id) in bot.config.superusers) 371 | or (config.vaapikai == "rg") 372 | ): 373 | for player in players: 374 | if f"{event.user_id}-{player.uid}" in list: 375 | await ti.finish(f"你已经有验证任务了,{NICKNAME}会忙不过来的", at_sender=True) 376 | else: 377 | logger.info( 378 | "原神体力", 379 | "➤", 380 | {"用户": str(event.user_id), "uid": player.uid}, 381 | "开始执行查询", 382 | True, 383 | ) 384 | result = Message() 385 | list.append(f"{event.user_id}-{player.uid}") 386 | msg = await handle_ssbq(player) 387 | if msg == "疑似验证码": 388 | if config.vaapikai == "rg": 389 | await ti.send( 390 | f"请前往{config.ip}/validate?uid={player.uid}进行手动验证,如果无法访问请刷新,直到出结果", 391 | at_sender=True, 392 | ) 393 | else: 394 | await ti.send(f"UID{player.uid}遇验证码阻拦,开始尝试过码", at_sender=True) 395 | msg = await handle_ssbq2(player) 396 | result += msg 397 | list.remove(f"{event.user_id}-{player.uid}") 398 | await ti.finish(result, at_sender=True) 399 | else: 400 | await ti.finish(config.hfu, at_sender=True) 401 | 402 | 403 | @get_coin.handle() 404 | async def _( 405 | bot: Bot, 406 | event: Union[GroupMessageEvent, PrivateMessageEvent], 407 | uid=CommandUID(), 408 | switch=CommandSwitch(), 409 | ): 410 | if isinstance(event, GroupMessageEvent): 411 | groupid = event.group_id 412 | else: 413 | groupid = "" 414 | if ( 415 | (groupid in config.whitelist) 416 | or (event.user_id in config.whlist) 417 | or (str(event.user_id) in bot.config.superusers) 418 | or (config.vaapikai == "rg") 419 | ): 420 | if switch is None: 421 | # 没有开关参数,手动执行米游币获取 422 | if f"{event.user_id}-{uid}" in list: 423 | await get_coin.finish(f"你已经有验证任务了,{NICKNAME}会忙不过来的", at_sender=True) 424 | else: 425 | GF = f"{NICKNAME}开始为UID{uid}执行加强米游币获取" 426 | if config.vaapikai == "rg": 427 | GF += f",\n请前往{config.ip}/validate?uid={uid}进行手动验证,如果无法访问请等待,直到出结果" 428 | await get_coin.send(GF, at_sender=True) 429 | logger.info( 430 | "加强米游币获取", "➤", { 431 | "用户": str(event.user_id), "uid": uid}, "执行获取", True 432 | ) 433 | list.append(f"{event.user_id}-{uid}") 434 | result = await mhy_bbs_coin(str(event.user_id), uid, False) 435 | list.remove(f"{event.user_id}-{uid}") 436 | await get_coin.finish(result, at_sender=True) 437 | elif isinstance(event, GroupMessageEvent) and ( 438 | (groupid in config.whitelist) 439 | or (event.user_id in config.whlist) 440 | or (str(event.user_id) in bot.config.superusers) 441 | ): 442 | sub_data = {"user_id": event.user_id, 443 | "uid": uid, "sub_event": "米游币验证获取"} 444 | if switch: 445 | # switch为开启,则添加订阅 446 | if ( 447 | ck := await PrivateCookie.get_or_none( 448 | user_id=str(event.user_id), uid=uid 449 | ) 450 | ) and ck.stoken is not None: 451 | await MihoyoBBSSub.update_or_create( 452 | **sub_data, 453 | defaults={ 454 | "group_id": event.group_id 455 | if isinstance(event, GroupMessageEvent) 456 | else event.user_id 457 | }, 458 | ) 459 | logger.info( 460 | "加强米游币自动获取", 461 | "➤", 462 | {"用户": str(event.user_id), "uid": uid}, 463 | "开启成功", 464 | True, 465 | ) 466 | await get_coin.finish(f"UID{uid}开启加强米游币自动获取成功", at_sender=True) 467 | else: 468 | await get_coin.finish( 469 | f"UID{uid}尚未绑定Cookie或Cookie中没有login_ticket!请先使用ysb指令绑定吧!", 470 | at_sender=True, 471 | ) 472 | else: 473 | # switch为关闭,则取消订阅 474 | if sub := await MihoyoBBSSub.get_or_none(**sub_data): 475 | await sub.delete() 476 | logger.info( 477 | "加强米游币自动获取", 478 | "➤", 479 | {"用户": str(event.user_id), "uid": uid}, 480 | "关闭成功", 481 | True, 482 | ) 483 | await get_coin.finish(f"UID{uid}关闭加强米游币自动获取成功", at_sender=True) 484 | else: 485 | await get_coin.finish( 486 | f"UID{uid}尚未开启加强米游币自动获取,无需关闭!", at_sender=True 487 | ) 488 | await get_coin.finish(config.hfu, at_sender=True) 489 | 490 | 491 | @all_coin.handle() 492 | async def _(event: Union[GroupMessageEvent, PrivateMessageEvent]): 493 | await all_coin.send(f"{NICKNAME}开始执行加强myb全部重做,需要一定时间...") 494 | await bbs_auto_coin() 495 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------