├── requirements.txt ├── .gitignore ├── config.ini ├── run.py ├── Utility ├── Notifications │ ├── serverchan.py │ ├── telegram.py │ ├── pushdeer.py │ ├── push.py │ ├── bark.py │ ├── qmsg.py │ ├── readme.md │ └── smtp.py ├── Encryption │ └── OCR-ChaoJIYing.py ├── risingStones │ ├── getUUIDUserInfo.py │ ├── getUserInfo.py │ ├── houseStatusChecker.py │ ├── signIn.py │ ├── constant.py │ ├── dailyTask.py │ ├── getSignReward.py │ └── rs_login.py └── sqMall │ ├── daoyuBuildinMallBalance.py │ ├── daoyuBuildinMallSign.py │ ├── sqMallDoSign.py │ └── sm_login.py ├── config.ini.example ├── qinglong.py ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.yaml │ └── bug-report.yaml ├── Deprecated ├── like.py ├── comment.py ├── QRCode.py ├── risingStonesLaunchActivity.py └── Daoyu_SMSLogin.py ├── README.md └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | APScheduler==3.10.4 2 | kuai_log==0.8 3 | Requests==2.31.0 4 | urllib3==2.1.0 5 | httpx[http2] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /venv/ 2 | /.idea/ 3 | DO_NOT_PUSH/ 4 | logs/ 5 | Temp/ 6 | *.pyc 7 | *.zip 8 | *.log 9 | config.ini 10 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [Modules] 2 | risingStonesTask = True 3 | sqMallTask = True 4 | 5 | [Normal] 6 | daoyukey = 7 | showusername = 8 | 9 | [Notification] 10 | noc-enable = False 11 | push-method = bark 12 | push-key = xxxxx 13 | qmsg-target = normal 14 | qmsg-target-number = 15 | smtp-host = "" 16 | smtp-port = 465 17 | smtp-username = "" 18 | smtp-password = "" 19 | smtp-ssl = True 20 | 21 | [Debug] 22 | Debug = False 23 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | # 用于直接执行任务的脚本 2 | import configparser 3 | 4 | from Utility.sqMall import sqMallDoSign as sqMallSign 5 | from Utility.risingStones.dailyTask import daily_task as lets_go 6 | 7 | taskConfig = configparser.RawConfigParser() 8 | taskConfig.read('config.ini', encoding='utf-8') 9 | if taskConfig.get('Modules', 'sqMallTask') == 'True': 10 | sqMallSign.main() 11 | 12 | if taskConfig.get('Modules', 'risingStonesTask') == 'True': 13 | print(lets_go()) 14 | -------------------------------------------------------------------------------- /Utility/Notifications/serverchan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import configparser 3 | import requests 4 | 5 | 6 | def send(title, content): 7 | config = configparser.ConfigParser() 8 | config.read('config.ini', encoding='UTF-8') 9 | key = config.get('Notification', 'push-key') 10 | url = f'https://sctapi.ftqq.com/{key}.send' + '?title=' + title + '&desp=' + content 11 | response = requests.get(url) 12 | if response.status_code == 200: 13 | return {'status': 'success', 'response': response.text} 14 | else: 15 | return {'status': 'failed', 'error': response.text} -------------------------------------------------------------------------------- /Utility/Notifications/telegram.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import configparser 3 | import requests 4 | 5 | 6 | def send(title, content): 7 | config = configparser.ConfigParser() 8 | config.read('config.ini', encoding='UTF-8') 9 | token = config.get('Notification', 'tg-token') 10 | userid = config.get('Notification', 'tg-userid') 11 | url = f'https://api.telegram.org/{token}/sendMessage?chat_id={userid}' + '&text=' + title + '%0A' + content 12 | response = requests.get(url) 13 | if response.status_code == 200: 14 | return {'status': 'success', 'response': response.text} 15 | else: 16 | return {'status': 'failed', 'error': response.text} 17 | -------------------------------------------------------------------------------- /Utility/Notifications/pushdeer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import configparser 3 | import urllib.parse 4 | import requests 5 | 6 | 7 | def send(title, content): 8 | config = configparser.ConfigParser() 9 | config.read('config.ini', encoding='UTF-8') 10 | key = config.get('Notification', 'push-key') 11 | postdata = { 12 | "text": title, 13 | "desp": content, 14 | "type": "markdown" 15 | } 16 | url = f'https://api2.pushdeer.com/message/push?pushkey={key}' 17 | response = requests.post(url, data=postdata) 18 | if response.status_code == 200: 19 | return {'status': 'success'} 20 | else: 21 | return {'status': 'failed', 'error': response.text} 22 | -------------------------------------------------------------------------------- /Utility/Notifications/push.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import importlib 3 | 4 | ''' 5 | example: 6 | import Utility.Notifications.push as pusher 7 | print(pusher.push('test', 'test')) 8 | ''' 9 | 10 | def push(title, content): 11 | """ 12 | Push推送 13 | :param title: 通知标题 14 | :param content: 通知内容 15 | :return: 推送结果,如:{'status': 'success'} 或 {'status': 'failed', 'error': 'error message'} 16 | """ 17 | 18 | push_config = configparser.ConfigParser() 19 | push_config.read('config.ini', encoding='UTF-8') 20 | # 根据配置导入对应的包 21 | push_module = importlib.import_module("Utility.Notifications." + push_config.get('Notification', 'push-method')) 22 | result = push_module.send(title, content) 23 | return result 24 | -------------------------------------------------------------------------------- /config.ini.example: -------------------------------------------------------------------------------- 1 | [Modules] 2 | ;需要启用的模组任务,请修改True/False(请注意大小写) 3 | risingStonesTask = True 4 | sqMallTask = True 5 | 6 | [Normal] 7 | ;叨鱼Key,关键参数 8 | daoyukey = 9 | ;show_Username 关键参数 10 | showusername = 11 | 12 | [Notification] 13 | ;若要启用消息通知,请将noc-enable设置为True,并根据说明正确配置push-key 14 | noc-enable = False 15 | ;通知服务商可选:bark pushdeer serverchan smtp qmsg telegram 16 | push-method = bark 17 | ;推送密钥或目标地址 18 | push-key = xxxxx 19 | 20 | ;qmsg酱推送设置 21 | ;推送目标 normal=私聊 group=群聊 22 | qmsg-target = normal 23 | ;推送目标QQ号/群号,留空默认全发 24 | qmsg-target-number = 25 | 26 | 27 | ;SMTP 发件服务器配置 28 | ;目前仅支持SSL 465端口发送 29 | ; TODO: 支持非SSL 30 | smtp-host = "" 31 | smtp-port = 465 32 | smtp-username = "" 33 | smtp-password = "" 34 | smtp-ssl = True 35 | 36 | ;Telegram推送设置 37 | ;Telegram机器人的token,例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw 38 | tg-token = botxxxxx 39 | ;Telegram用户的id,例如:129xxx206 40 | tg-user = xxxxx 41 | -------------------------------------------------------------------------------- /qinglong.py: -------------------------------------------------------------------------------- 1 | # 用于青龙面板的任务 2 | # 青龙面板需要满足以下条件: 3 | # docker镜像必须使用【whyour/qinglong:debian】,否则依赖无法安装。 4 | # 请安装【requirement.txt】内的Python依赖 5 | # 请安装【zbar-tools】linux依赖 6 | # 请确保已经填写好【config.ini】配置文件,此任务将不会引导您填写配置文件。 7 | import configparser 8 | 9 | from Utility.sqMall import sqMallDoSign as sqMallSign 10 | from Utility.risingStones.dailyTask import daily_task as lets_go 11 | 12 | taskConfig = configparser.RawConfigParser() 13 | taskConfig.read('config.ini', encoding='utf-8') 14 | if taskConfig.get('Modules', 'sqMallTask') == 'True': 15 | sqMallSign.main() 16 | 17 | if taskConfig.get('Modules', 'risingStonesTask') == 'True': 18 | noc_config = configparser.RawConfigParser() 19 | noc_config.read('config.ini', encoding='utf-8') 20 | if noc_config.get('Notification', 'noc-enable') == 'True': 21 | import Utility.Notifications.push as pusher 22 | msg = lets_go() 23 | pusher.push('石之家任务结果', msg) 24 | else: 25 | import Utility.Notifications.push as pusher 26 | msg = lets_go() 27 | pusher.push('石之家任务结果', msg) -------------------------------------------------------------------------------- /Utility/Encryption/OCR-ChaoJIYing.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def ocr_cjy(host, username, password, soft_id, codetype,img): 5 | """ 6 | :param host: 超级鹰的解析APi 7 | :param username: 用户名 8 | :param password: 密码 9 | :param soft_id: 软件ID 10 | :param codetype: 验证码类型 11 | :return: 解析成功会返回验证码,失败会返回服务器的错误信息 12 | """ 13 | ocr_params = { 14 | 'user': username, 15 | 'pass': password, 16 | 'softid': soft_id, 17 | 'codetype': codetype, 18 | 'len_min': 6 19 | } 20 | ocr_header = { 21 | 'Connection': 'Keep-Alive', 22 | 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', 23 | } 24 | files = {'userfile': ('Captcha.jpeg', img)} 25 | ocr_resp = requests.post(host, data=ocr_params, headers=ocr_header, files=files, verify=False) 26 | code_json = ocr_resp.json() 27 | code = code_json['pic_str'] 28 | errmsg = code_json['err_str'] 29 | if errmsg != 'OK': 30 | return ocr_resp.text 31 | else: 32 | return code 33 | -------------------------------------------------------------------------------- /Utility/risingStones/getUUIDUserInfo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Des: 石之家用户信息组件,获取特定uuid的用户信息 3 | Note: 此文件待更新,用到的时候再说。 4 | """ 5 | import requests 6 | def get_rs_userinfo(cookies,uuid): 7 | """ 8 | 石之家用户信息组件,获取特定uuid的用户信息 9 | :param cookies: 石之家cookie 10 | :param uuid: 石之家用户uuid 11 | :return: status: success/fail, message: msg 12 | """ 13 | userInfoApi = "https://apiff14risingstones.web.sdo.com/api/home/userInfo/getUserInfo?uuid={}".format(uuid) 14 | headers = { 15 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36", 16 | "Cookie": cookies 17 | } 18 | userInfoResult = requests.get(url=userInfoApi, headers=headers) 19 | userInfoResult = userInfoResult.json() 20 | if userInfoResult["code"] == 10000: 21 | return { 22 | "status": "success", 23 | "message": userInfoResult 24 | } 25 | else: 26 | return { 27 | "status": "fail", 28 | "message": userInfoResult 29 | } 30 | -------------------------------------------------------------------------------- /Utility/risingStones/getUserInfo.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import httpx 3 | from Utility.risingStones import constant 4 | 5 | 6 | def get_rs_userinfo(cookies, daoyu_ticket): 7 | """ 8 | 石之家用户信息组件,获取登录用户的信息 9 | :param cookies: 石之家cookie 10 | :return: status: success/fail, message: msg 11 | """ 12 | userInfoApi = "https://apiff14risingstones.web.sdo.com/api/home/userInfo/getUserInfo" 13 | headers = { 14 | **constant.RS_HEADERS_GET, 15 | 'authorization': daoyu_ticket, 16 | } 17 | get_userinfo_cookies = { 18 | 'ff14risingstones': cookies 19 | } 20 | with httpx.Client(http2=True) as client: 21 | userInfoResult = client.get(userInfoApi, headers=headers, cookies=get_userinfo_cookies) 22 | userInfoResult = userInfoResult.json() 23 | if userInfoResult["code"] == 10000: 24 | return { 25 | "status": "success", 26 | "message": userInfoResult 27 | } 28 | else: 29 | return { 30 | "status": "fail", 31 | "message": userInfoResult 32 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: 提出一个新功能 3 | labels: enhancement 4 | body: 5 | - type: checkboxes 6 | id: checks 7 | attributes: 8 | label: 在提问之前... 9 | options: 10 | - label: 我已确认当前请求的功能不在预订计划内,请查阅:https://github.com/orgs/FF14CN/projects/1 11 | required: true 12 | - type: textarea 13 | id: describe 14 | attributes: 15 | label: 您的功能请求是否与当前存在问题有关? 16 | description: '对问题所在的清晰简洁的描述。例如,我在[??]的情况下无法解决,需要[??]功能。若不是,请填写【无关】' 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: first 21 | attributes: 22 | label: 描述您想要的解决方案 23 | description: 对你想要发生的事情进行清晰简洁的描述。 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: second 28 | attributes: 29 | label: 描述您考虑过的备选方案 30 | description: 对您考虑过的任何替代解决方案或功能的清晰简洁的描述。 31 | validations: 32 | required: false 33 | - type: textarea 34 | id: context 35 | attributes: 36 | label: 附加上下文 37 | description: 在此处添加有关功能请求的任何其他上下文或屏幕截图。 38 | validations: 39 | required: false 40 | -------------------------------------------------------------------------------- /Utility/Notifications/bark.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | import requests 4 | 5 | 6 | def send(title: str, content: str): 7 | """ 8 | 发送 bark 通知 9 | :param title: 通知标题 10 | :param content: 通知内容 11 | :return: {'status': 'success'} 或 {'status': 'failed'} 12 | """ 13 | config = configparser.ConfigParser() 14 | config.read('config.ini', encoding='UTF-8') 15 | pushkey = config.get('Notification', 'push-key') 16 | url = 'https://api.day.app/' + pushkey 17 | headers = { 18 | "Content-Type": "application/json", 19 | "charset": "UTF-8" 20 | } 21 | # bark通知将被分组到“FF14”,并且会自动保存 22 | load = { 23 | "title": title, 24 | "body": content, 25 | "badge": 1, 26 | "sound": "minuet.caf", 27 | "icon": "https://huiji-thumb.huijistatic.com/ff14/uploads/thumb/3/33/000030.png/30px-000030.png", 28 | "group": "FF14", 29 | "isArchive": 1, 30 | } 31 | response = requests.post(url, headers=headers, json=load) 32 | if response.status_code == 200: 33 | return {'status': 'success'} 34 | else: 35 | return {'status': 'failed', 'error': response.text} 36 | -------------------------------------------------------------------------------- /Deprecated/like.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | """ 4 | 点个赞示范: 5 | from Utility.risingStones import rs_login 6 | from Utility.risingStones import like as rs_like 7 | print(rs_like.rs_like(9365, 1, rs_login.login())) 8 | #给官方水楼点赞 9 | """ 10 | 11 | 12 | def rs_like(post_id, type, cookies): 13 | """ 14 | 石之家评论组件 15 | :param post_id: 帖子id 16 | :param type: 点赞类型,1=对贴文点赞,2=对评论点赞 17 | :param cookies: 石之家cookies 18 | :return: status: success/fail 19 | """ 20 | likeApi = "https://apiff14risingstones.web.sdo.com/api/home/posts/like" 21 | headers = { 22 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36", 23 | "Cookie": cookies 24 | } 25 | payload = { 26 | "id": post_id, 27 | "type": type, 28 | } 29 | likeResult = requests.post(url=likeApi, headers=headers, data=payload) 30 | likeResult = likeResult.json() 31 | if likeResult["code"] == 10000: 32 | return { 33 | "status": "success", 34 | "message": likeResult["msg"], 35 | } 36 | else: 37 | return { 38 | "status": "fail", 39 | "message": likeResult["msg"], 40 | } 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: 提交一个bug帮助我们改善代码 3 | labels: ['bug'] 4 | body: 5 | - type: textarea 6 | id: describe 7 | attributes: 8 | label: 问题描述 9 | description: | 10 | 简短描述该Bug或报错 11 | A clear and concise description of what the bug is. 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: process 16 | attributes: 17 | label: 重现步骤 18 | description: | 19 | 你是如何触发该错误的? 20 | 1. 打开 '...' 21 | 2. 输入 '....' 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: screenshots 26 | attributes: 27 | label: 截图 28 | description: 如果方便,请提交您的截图 29 | validations: 30 | required: false 31 | - type: textarea 32 | id: environment 33 | attributes: 34 | label: 运行环境 35 | description: | 36 | 系统: [e.g. iOS] 37 | Python版本 [e.g. 3.7] 38 | 本软件版本 [e.g. v2.4.1-alpha] 39 | validations: 40 | required: false 41 | - type: textarea 42 | id: others 43 | attributes: 44 | label: 附加消息 45 | description: 任何能让我们对你所遇到的问题有更多了解的东西 46 | validations: 47 | required: false 48 | - type: markdown 49 | id: notice 50 | attributes: 51 | value: "**我已确认没有重复的问题**" 52 | 53 | -------------------------------------------------------------------------------- /Utility/Notifications/qmsg.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | import requests 4 | # https://qmsg.zendee.cn/docs/api/#%E6%8E%A8%E9%80%81%E6%8E%A5%E5%8F%A3%E5%8F%82%E6%95%B0 5 | 6 | def send(title: str, content: str): 7 | """ 8 | 发送 qmsg 通知 9 | :param title: 通知标题 10 | :param content: 通知内容 11 | :return: {'status': 'success'} 或 {'status': 'failed'} 12 | """ 13 | config = configparser.ConfigParser() 14 | config.read('config.ini', encoding='UTF-8') 15 | pushkey = config.get('Notification', 'push-key') 16 | qmsg_target = config.get('Notification', 'qmsg-target') 17 | qmsg_target_number = config.get('Notification', 'qmsg-target-number') 18 | 19 | if qmsg_target == 'normal': 20 | url = 'https://qmsg.zendee.cn/jsend/' + pushkey 21 | elif qmsg_target == 'group': 22 | url = 'https://qmsg.zendee.cn/jgroup/' + pushkey 23 | else: 24 | return {'status': 'failed', 'error': 'qmsg-target配置错误'} 25 | 26 | 27 | headers = { 28 | "Content-Type": "application/json", 29 | "charset": "UTF-8" 30 | } 31 | load = { 32 | "msg": "[" + title + "]\n" + content, 33 | } 34 | if qmsg_target_number != '': 35 | load['qq'] = qmsg_target_number 36 | response = requests.post(url, headers=headers, json=load) 37 | if response.status_code == 200: 38 | return {'status': 'success'} 39 | else: 40 | return {'status': 'failed', 'error': response.text} 41 | 42 | -------------------------------------------------------------------------------- /Utility/risingStones/houseStatusChecker.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | 4 | 5 | def house_status_checker(user_info): 6 | """ 7 | 石之家房屋状态组件 8 | :return: {"house_status": "Unknown"/"Normal"/"Warning", "remain_day": remain_day/"error"} 9 | """ 10 | if user_info["status"] == "success": # 判断是否成功获取用户信息 11 | user_info = user_info["message"]["data"]["characterDetail"] 12 | if "house_remain_day" in user_info: # 判断是否存在房屋过提醒字段。如果用户主动隐藏房屋信息,该字段必定出现且内容为“******” 13 | if "*" in user_info["message"]["data"]["characterDetail"]["house_remain_day"]: # 判断用户是否隐藏。 14 | houseStatus = {"house_status": "Unknown", "error": "用户隐藏了房屋状态"} 15 | else: 16 | remain_day = re.findall(r'\d+', user_info["house_remain_day"]) 17 | houseStatus = {"house_status": "Warning", "remain_day": remain_day} 18 | else: 19 | houseStatus = {"house_status": "Normal"} 20 | 21 | else: 22 | houseStatus = {"house_status": "Unknown", "error": str(user_info)} 23 | 24 | if houseStatus["house_status"] == "Warning": 25 | house_status_msg = "房屋剩余{}天".format(houseStatus["remain_day"]) 26 | return house_status_msg 27 | if houseStatus["house_status"] == "Unknown": 28 | house_status_msg = "房屋状态未知,具体错误信息:{}".format(houseStatus["error"]) 29 | return house_status_msg 30 | if houseStatus["house_status"] == "Normal": 31 | house_status_msg = "房屋状态正常" 32 | return house_status_msg -------------------------------------------------------------------------------- /Utility/risingStones/signIn.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Cettiidae 3 | Version: 1.2.0 4 | Update Date: 2024-06-27 5 | """ 6 | import requests 7 | from Utility.risingStones import rs_login 8 | from Utility.risingStones import constant 9 | import httpx 10 | 11 | def rs_signin(cookies,daoyu_ticket): 12 | """ 13 | 石之家签到组件 14 | :param daoyu_ticket: 15 | :param cookies: 石之家cookies 16 | :return: status: success/fail 17 | """ 18 | signinApi = "https://apiff14risingstones.web.sdo.com/api/home/sign/signIn" 19 | headers = { 20 | **constant.RS_HEADERS_POST_TEST, 21 | 'authorization': daoyu_ticket, 22 | # "Cookie": cookies 23 | } 24 | with httpx.Client(http2=True) as client: 25 | signinResult = client.post(signinApi, headers=headers) 26 | 27 | signinResult = signinResult.json() 28 | if signinResult["code"] == 10000: 29 | if rs_login.debug: 30 | print(f'SignIn OK‘ {signinResult["msg"]}') 31 | msg = '签到成功 ~' 32 | rs_login.logger_stream.info('签到成功了 ~') 33 | elif signinResult["code"] == 10001: 34 | if rs_login.debug: 35 | print(f'Already signed in‘ {signinResult["msg"]}') 36 | msg = '已经签过到了,请不要重复签到 ~' 37 | else: 38 | if rs_login.debug: 39 | print(f'SignIn Fail‘ {signinResult["msg"]}') 40 | msg = '签到失败 ~ 原因可能是盛趣的签到参数变动,请反馈给开发者。' 41 | rs_login.logger_stream.info('签到失败了 ~') 42 | rs_login.logger_logs.error(signinResult["msg"]) 43 | return msg -------------------------------------------------------------------------------- /Utility/Notifications/readme.md: -------------------------------------------------------------------------------- 1 | # 推送组件 2 | 3 | **本组件仅为以下服务商提供调用方法,不存在任何利益关系。** 4 | **若与服务商产生经济纠纷请与服务商协商解决。** 5 | 6 | 目前已支持: 7 | - [X] [Bark (仅供iOS)](https://bark.day.app/#/) 8 | - [X] [PushDeer (多平台)](https://www.pushdeer.com/) 9 | - [X] [ServerChan (多平台)](https://sct.ftqq.com/) 10 | - [X] SMTP邮件 (多平台) 11 | 12 | 13 | # 使用方法 14 | 15 | ## 用户教程 16 | 请在项目 `config.ini` 文件内填写好以下内容: 17 | ```ini 18 | [Notification] 19 | ;若要启用消息通知,请将noc-enable设置为True,并根据说明正确配置push-key 20 | noc-enable = False 21 | ;通知服务商可选:bark、pushdeer serverchan smtp 22 | push-method = bark 23 | ;推送密钥或目标地址 24 | push-key = xxxxx 25 | 26 | ;SMTP 发件服务器配置 27 | ;目前仅支持SSL 465端口发送 28 | ; TODO: 支持非SSL 29 | smtp-host = "" 30 | smtp-port = 465 31 | smtp-username = "" 32 | smtp-password = "" 33 | smtp-ssl = True 34 | ``` 35 | 36 | ### 获取push-key 37 | #### Bark 38 | [](https://infrasimage-r2.cf.cdn.infras.host/2023/12/21/6583ea8deed28.webp) 39 | 40 | #### PushDeer 41 | 1. 通过apple账号(或微信账号·仅Android版支持)登录 42 | 2. 切换到「设备」标签页,点击右上角的加号,注册当前设备 43 | 3. 切换到「Key」标签页,点击右上角的加号,创建一个Key 44 | 4. 将Key填入配置文件 45 | 46 | #### ServerChan 47 | [](https://infrasimage-r2.cf.cdn.infras.host/2023/12/21/6583ec04d0242.webp) 48 | 49 | #### SMTP邮件 50 | 请自行Google/百度:“QQ邮箱SMTP发件教程” “XX邮箱SMTP教程”。 51 | **请注意,SMTP的`Push-key`为邮件收件人,例如 `1000@qq.com`** 52 | 53 | ## 调用教程 54 | 此章节内容仅供开发者使用,普通用户无需关注此部分内容。 55 | 56 | 导入`push.sh`: 57 | ```python 58 | import Utility.Notifications.push as pusher 59 | pusher.push('title标题', 'content内容') 60 | ``` 61 | -------------------------------------------------------------------------------- /Deprecated/comment.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | """ 4 | 水一贴示范: 5 | from Utility.risingStones import rs_login 6 | from Utility.risingStones import comment as rs_comment 7 | commentraw = '
[emo29]
' 8 | print(rs_comment.rs_comment("9365", commentraw, "0", "0", "", rs_login.login())) 9 | #官方水楼水一贴 10 | """ 11 | 12 | 13 | def rs_comment(post_id, comment, parent_id, root_parent, comment_pic, cookies): 14 | """ 15 | 石之家评论组件 16 | :param post_id: 帖子id 17 | :param comment: 评论内容 请使用p标签包裹 18 | :param parent_id: 父评论id 19 | :param root_parent: 根评论id 目前观察到与parent_id相同 20 | :param comment_pic: 评论图片 21 | :param cookies: 石之家cookie 22 | :return: status: success/fail 23 | """ 24 | commentApi = "https://apiff14risingstones.web.sdo.com/api/home/posts/comment" 25 | headers = { 26 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36", 27 | "Cookie": cookies 28 | } 29 | payload = { 30 | "content": str(comment), 31 | "posts_id": str(post_id), 32 | "parent_id": str(parent_id), 33 | "root_parent": str(root_parent), 34 | "comment_pic": str(comment_pic) 35 | } 36 | commentResult = requests.post(url=commentApi, headers=headers, data=payload) 37 | commentResult = commentResult.json() 38 | if commentResult["code"] == 10000: 39 | return { 40 | "status": "success", 41 | "message": commentResult["msg"], 42 | } 43 | else: 44 | return { 45 | "status": "fail", 46 | "message": commentResult["msg"], 47 | } 48 | -------------------------------------------------------------------------------- /Utility/sqMall/daoyuBuildinMallBalance.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def daoyu_mall_balance(session_id): 5 | """ 6 | 仅适用于叨鱼内部商城的查询签到积分 PC端不适用 7 | :param session_id: 子账号的Daoyukey值 8 | :return: 返回签到积分余额 9 | """ 10 | get_balance_url = 'https://sqmallservice.u.sdo.com/api/rs/member/integral/balance?merchantId=1' 11 | get_balance_header = { 12 | 'authority': 'sqmallservice.u.sdo.com', 13 | 'method': 'GET', 14 | 'scheme': 'https', 15 | 'pragma': 'no-cache', 16 | 'cache-control': 'no-cache', 17 | 'qu-deploy-platform': '4', 18 | 'accept': 'application/json, text/javascript, */*; q=0.01', 19 | 'qu-merchant-id': '1', 20 | 'qu-hardware-platform': '1', 21 | 'qu-software-platform': '2', 22 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 DaoYu/9.3.3', 23 | 'qu-web-host': 'https://m.qu.sdo.com', 24 | 'origin': 'https://m.qu.sdo.com', 25 | 'x-requested-with': 'com.sdo.sdaccountkey', 26 | 'sec-fetch-site': 'same-site', 27 | 'sec-fetch-mode': 'cors', 28 | 'sec-fetch-dest': 'empty', 29 | 'referer': 'https://m.qu.sdo.com/', 30 | 'accept-encoding': 'gzip, deflate', 31 | 'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', 32 | } 33 | get_balance_cookies = { 34 | 'sessionId': session_id 35 | } 36 | get_balance_response = requests.get(get_balance_url, headers=get_balance_header, cookies=get_balance_cookies, 37 | verify=False) 38 | get_balance_json = get_balance_response.json() 39 | balance = get_balance_json['data']['balance'] 40 | return balance 41 | -------------------------------------------------------------------------------- /Utility/Notifications/smtp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import smtplib 5 | import configparser 6 | from email.mime.text import MIMEText 7 | from email.utils import formataddr 8 | 9 | 10 | def send(title, content): 11 | """ 12 | SMTP发送邮件 13 | :param title: 邮件标题 14 | :param content: 邮件内容 15 | :return: 16 | """ 17 | smtp_config = configparser.ConfigParser() 18 | smtp_config.read('config.ini') 19 | pushkey = smtp_config.get('Notification', 'pushkey') 20 | sender = smtp_config.get('Notification', 'smtp-username') 21 | sender_pass = smtp_config.get('Notification', 'smtp-password') 22 | smtp_host = smtp_config.get('Notification', 'smtp-host') 23 | smtp_port = int(smtp_config.get('Notification', 'smtp-port')) 24 | 25 | reciver = pushkey 26 | sender = sender 27 | sender_pass = sender_pass 28 | 29 | msg = MIMEText(content, 'plain', 'utf-8') 30 | msg['From'] = formataddr(["FF14 Utility Server", sender]) 31 | msg['To'] = formataddr(["User", reciver]) 32 | msg['Subject'] = title # 邮件标题 33 | # +++++++++++++++++++++++++++++++++++ 34 | import ssl 35 | ctx = ssl.create_default_context() 36 | ctx.set_ciphers('DEFAULT') 37 | # +++++++++++++++++++++++++++++++++++ 38 | server = smtplib.SMTP_SSL(smtp_host, smtp_port, context=ctx) 39 | server.login(sender, sender_pass) 40 | try: 41 | server.sendmail(sender, [reciver, ], msg.as_string()) 42 | except Exception as e: 43 | print(e) 44 | server.quit() 45 | return { 46 | 'status': 'failed', 47 | 'error': e 48 | } 49 | server.sendmail(sender, [reciver, ], msg.as_string()) 50 | server.quit() 51 | return { 52 | 'status': 'success' 53 | } 54 | -------------------------------------------------------------------------------- /Utility/sqMall/daoyuBuildinMallSign.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def daoyumall_sign(sub_session_id, account_id): 5 | """ 6 | 仅适用于叨鱼内的盛趣商城签到操作 PC端不适用 7 | :param sub_session_id: 子账号的Daoyukey值 8 | :param account_id: 子账号的AccountID 9 | :return: 0: 签到成功 1: 重复签到 2: 签到失败 10 | """ 11 | sign_url = 'https://sqmallservice.u.sdo.com/api/us/integration/checkIn' 12 | sign_data = {'merchantId': 1} 13 | sign_header = { 14 | 'authority': 'sqmallservice.u.sdo.com', 15 | 'method': 'PUT', 16 | 'scheme': 'https', 17 | 'qu-web-host': 'https://m.qu.sdo.com', 18 | 'qu-hardware-platform': '1', 19 | 'qu-software-platform': '2', 20 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 DaoYu/9.3.3', 21 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 22 | 'accept': 'application/json, text/javascript, */*; q=0.01', 23 | 'qu-deploy-platform': '4', 24 | 'qu-merchant-id': '1', 25 | 'origin': 'https://m.qu.sdo.com', 26 | 'x-requested-with': 'com.sdo.sdaccountkey', 27 | 'sec-fetch-site': 'same-site', 28 | 'sec-fetch-mode': 'cors', 29 | 'sec-fetch-dest': 'empty', 30 | 'referer': 'https://m.qu.sdo.com/', 31 | } 32 | sign_cookies = { 33 | 'sessionId': sub_session_id, 34 | 'direbmemllam': account_id, 35 | } 36 | sign_response = requests.put(sign_url, headers=sign_header, cookies=sign_cookies, data=sign_data, verify=False) 37 | sign_json = sign_response.json() 38 | if sign_json['resultMsg'] == 'SUCCESS': 39 | return 0 40 | elif sign_json['resultMsg'] == '今日已签到,请勿重复签到': 41 | return 1 42 | else: 43 | return 2 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
6 | [emo{randomNum}]
' 50 | status = rs_comment("9365", commentraw, "0", "0", "", cookies) 51 | return status 52 | elif taskType == "signIn": 53 | from Utility.risingStones.signIn import rs_signin 54 | status = rs_signin(cookies) 55 | return status 56 | else: 57 | print("任务类型错误") 58 | 59 | 60 | def doSeal(cookies, sealType): 61 | """ 62 | 执行盖章任务 63 | :param cookies: 石之家cookies 64 | :param sealType: 盖章类型 65 | :return: 盖章状态 66 | """ 67 | sealUrl = "https://apiff14risingstones.web.sdo.com/api/home/active/online2312/doSeal" 68 | headers = { 69 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36", 70 | "Cookie": cookies 71 | } 72 | payload = { 73 | "type": sealType 74 | } 75 | 76 | doSeal = requests.post(sealUrl, headers=headers, data=payload) 77 | if doSeal.json()["code"] == 10000: 78 | return { 79 | "status": doSeal.json()["msg"], 80 | } 81 | else: 82 | return { 83 | "status": doSeal.json()["msg"], 84 | } 85 | 86 | 87 | def getSealReward(cookies, seal_num): 88 | """ 89 | 获取盖章奖励 90 | :param cookies: 石之家cookies 91 | :param seal_num: 盖章数量 92 | """ 93 | # 有奖数量3、5、9、15、19、24、27、30 94 | sealRewardUrl = f"https://apiff14risingstones.web.sdo.com/api/home/active/online2312/getSealReward?seal_num={seal_num}" 95 | headers = { 96 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36", 97 | "Cookie": cookies 98 | } 99 | getSealReward = requests.get(sealRewardUrl, headers=headers) 100 | if getSealReward.json()["code"] == 10000: 101 | return { 102 | "status": getSealReward.json()["msg"], 103 | } 104 | else: 105 | return { 106 | "status": getSealReward.json()["msg"], 107 | } 108 | 109 | 110 | def run(): 111 | from Utility.risingStones.rs_login import login as rs_login 112 | from Utility.risingStones.rs_login import is_rs_login as rs_userInfo 113 | cookies = rs_login() 114 | userTaskInfo = getUserTaskInfo(cookies) 115 | userInfo = rs_userInfo(cookies)["msg"] 116 | print("当前登录账户:" + userInfo["character_name"] + "@" + userInfo["group_name"]) 117 | print("当前签到状态:" + str(userTaskInfo)) 118 | # if userTaskInfo["sign_status"] == 1: 119 | # 不知道为什么,没签到也是1 120 | finishTask(cookies, "signIn") 121 | 122 | if userTaskInfo["like_num"] < 5: 123 | # 点赞不够 124 | print("点赞不够,正在随机点赞") 125 | print(finishTask(cookies, "like")) 126 | 127 | if userTaskInfo["comment_status"] < 1: 128 | # 评论不够 129 | print("评论不够,正在随机评论") 130 | print(finishTask(cookies, "comment")) 131 | 132 | # 盖章 133 | for sealIndex in range(1, 4): 134 | sleep(1) 135 | print(doSeal(cookies, sealIndex)) 136 | 137 | # 领奖 138 | for rewardNum in [3, 5, 9, 15, 19, 24, 27, 30]: 139 | sleep(10) 140 | print("Day=" + str(rewardNum) + str(getSealReward(cookies, rewardNum))) 141 | 142 | userTaskInfo = getUserTaskInfo(cookies) 143 | print("当前签到状态:" + str(userTaskInfo)) 144 | -------------------------------------------------------------------------------- /Deprecated/Daoyu_SMSLogin.py: -------------------------------------------------------------------------------- 1 | # 此登陆方式不再维护,仅供科研使用 2 | import requests 3 | import json 4 | 5 | # 进行短信验证码登录,并获取sessid 6 | def get_main_key(manu_id, device_id, guid, phone_number, scene): 7 | # 获得验证码 Session 8 | send_sms_captcha_url = 'https://daoyu.sdo.com/api/userCommon/sendSmsCode' 9 | send_sms_captcha_params = { 10 | 'device_os': 'iOS17.0', 11 | 'device_manuid': manu_id, 12 | 'device_id': device_id, 13 | 'idfa': '00000000-0000-0000-0000-000000000000', 14 | 'image_type': '2', 15 | 'sms_type': '2', 16 | 'sms_channel': '1', 17 | 'guid': guid, 18 | 'app_version': 'i.9.3.3', 19 | 'media_channel': 'AppStore', 20 | 'phone': phone_number, 21 | 'scene': scene, 22 | 'src_code': '8', 23 | } 24 | headers = { 25 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 26 | 'content-type': 'application/json', 27 | } 28 | send_sms_captcha_response = requests.get(send_sms_captcha_url, params=send_sms_captcha_params, headers=headers, 29 | verify=False) 30 | send_sms_captcha_json = send_sms_captcha_response.json() 31 | captcha_session = send_sms_captcha_json['data']['captcha_session'] 32 | captcha_url = send_sms_captcha_json['data']['captcha_url'] 33 | 34 | if captcha_session and captcha_url != '': 35 | logger_logs.debug(f'Get Captcha Successful. captcha_session: {captcha_session} captcha_url: + {captcha_url}') 36 | return captcha_session 37 | else: 38 | logger_logs.error(f'Get Captcha Error : {send_sms_captcha_json}') 39 | logger_stream.error('致命错误,获取Captcha失败了,请将Logs反馈给开发者,程序即将停止。') 40 | exit() 41 | # 进入验证码循环校验流程 42 | while True: 43 | # 将获取的验证码进行OCR 44 | captcha_img_url = 'http://captcha.sdo.com/fcgi-bin/show_img.fcgi' 45 | captcha_img_params = { 46 | 'appid': '151', 47 | 'session_key': captcha_session, 48 | 'gameid': '991002627', 49 | 'areaid': '1', 50 | } 51 | captcha_img_headers = { 52 | 'User-Agent': '%E5%8F%A8%E9%B1%BC/1 CFNetwork/1474 Darwin/23.0.0', 53 | 'Host': 'captcha.sdo.com', 54 | 'Accept-Encoding': 'gzip, deflate' 55 | } 56 | 57 | captcha_response = requests.get(captcha_img_url, params=captcha_img_params, headers=captcha_img_headers, 58 | verify=False) 59 | with open('Temp/Captcha.jpeg', 'wb') as f: 60 | f.write(captcha_response.content) 61 | captcha_img = open('Temp/Captcha.jpeg', 'rb').read() 62 | captcha_code = ocr_handler(captcha_img) 63 | 64 | # 校验OCR的验证码 65 | check_captcha_url = 'https://daoyu.sdo.com/api/userCommon/checkCaptcha?' 66 | check_captcha_params = { 67 | 'idfa': '00000000-0000-0000-0000-000000000000', 68 | 'app_version': 'i.9.3.3', 69 | 'device_id': '7D30233F-F928-430E-BF0C-14A21260527A', 70 | 'phone': phone_number, 71 | 'scene': scene, 72 | 'device_os': 'iOS17.0', 73 | 'src_code': '8', 74 | 'captcha_session': captcha_session, 75 | 'sms_type': '2', 76 | 'image_type': '1', 77 | 'out_info': '', 78 | 'device_manuid': manu_id, 79 | 'media_channel': 'AppStore', 80 | 'captcha_code': captcha_code, 81 | } 82 | check_captcha_header = { 83 | 'authority': 'daoyu.sdo.com', 84 | 'scheme': 'https', 85 | 'content-type': 'application/json', 86 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 87 | } 88 | check_captcha_response = requests.get(check_captcha_url, params=check_captcha_params, 89 | headers=check_captcha_header, verify=False) 90 | check_captcha_json = check_captcha_response.json() 91 | if 'is_captcha' in check_captcha_json['data'] and check_captcha_json['data']['is_captcha'] == '0': 92 | logger_stream.info('校验码和服务器核对成功,短信验证码已经下发,请在1分钟内填入') 93 | break 94 | elif 'captcha_session' in check_captcha_json['data']: 95 | logger_stream.info('校验码和服务器核对失败,即将重试..') 96 | captcha_session = check_captcha_json['data']['captcha_session'] 97 | elif 'return_message' in check_captcha_json and check_captcha_json[ 98 | 'return_message'] == '短信获取频繁,已被受限,请稍等再试': 99 | logger_stream.info('触发服务器风控,将在24小时后重试') 100 | time.sleep(86400) 101 | else: 102 | logger_stream.info('未知错误,程序将退出.') 103 | exit() 104 | 105 | # 输入短信验证码并校验 106 | sms_code_input = input("请输入短信验证码:") 107 | sms_login_url = 'https://daoyu.sdo.com/api/userCommon/validateSmsCodeLogin' 108 | sms_login_params = { 109 | 'sms_code': sms_code_input, 110 | 'phone': phone_number, 111 | 'device_manuid': manu_id, 112 | 'device_os': 'iOS17.0', 113 | 'idfa': '00000000-0000-0000-0000-000000000000', 114 | 'media_channel': 'AppStore', 115 | 'device_id': device_id, 116 | 'app_version': 'i.9.3.3', 117 | 'src_code': '8', 118 | } 119 | sms_login_header = { 120 | 'authority': 'daoyu.sdo.com', 121 | 'method': 'GET', 122 | 'scheme': 'https', 123 | 'content-type': 'application/json', 124 | 'accept-encoding': 'gzip, deflate, br', 125 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 126 | 127 | } 128 | sms_login_response = requests.get(sms_login_url, params=sms_login_params, headers=sms_login_header, verify=False) 129 | sms_login_json = sms_login_response.json() 130 | if 'USERSESSID' in sms_login_json['data'] and sms_login_json['data']['is_login'] == '1': 131 | # DY_XXXX 132 | user_sessid = sms_login_json['data']['USERSESSID'] 133 | nick_name = sms_login_json['data']['nickname'] 134 | show_username = sms_login_json['data']['show_username'] 135 | logger_stream.info(f'你好,{nick_name},目前一切正常...程序将继续执行剩余任务...') 136 | config = configparser.ConfigParser() 137 | config.read('config.ini', encoding='utf-8') 138 | config.set('Normal', 'DaoyuKeyInit', '0') 139 | config.set('Normal', 'DaoyuKey', user_sessid) 140 | config.set('Normal', 'ShowUsername', show_username) 141 | try: 142 | with open('config.ini', 'w', encoding='utf-8') as configfile: 143 | config.write(configfile) 144 | except Exception as e: 145 | logger_stream.info('写配置项失败,请手动修改config.ini ' + 'DaoyuKey: ' + user_sessid, 146 | 'ShowUsername: ' + show_username) 147 | logger_logs.error('write config.ini error: ' + str(e)) 148 | return user_sessid, show_username 149 | else: 150 | logger_stream.info('获取DaoyuKey失败,请将Logs反馈给开发者') 151 | logger_logs.error(f'sms_login_json: {sms_login_json}') 152 | return None 153 | -------------------------------------------------------------------------------- /Utility/risingStones/rs_login.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: KuliPoi 3 | Contact: me@pipirapira.com 4 | Created: 2024-06-27 5 | File: rs_login.py 6 | Version: 1.5.0 7 | Description: daoyu login into rising stones (Fuck SQ by the way) 8 | """ 9 | 10 | import configparser 11 | import logging 12 | import sys 13 | import string 14 | import random 15 | import uuid 16 | import requests 17 | import httpx 18 | import urllib3 19 | from kuai_log import get_logger 20 | from Utility.risingStones import constant 21 | 22 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 23 | 24 | dev = False 25 | rs_config = configparser.RawConfigParser() 26 | 27 | if dev: 28 | print('Dev environment detected') 29 | rs_config.read('E:\ProJect\Python\Sarean-arsenal\config.ini', encoding='UTF-8') 30 | else: 31 | rs_config.read('config.ini') 32 | 33 | if rs_config.get('Debug', 'Debug') == 'True': 34 | print("Debug mode on. Everything is cumming to ya.") 35 | debug = True 36 | else: 37 | debug = False 38 | 39 | logger_stream = get_logger('INFO', level=logging.INFO, is_add_stream_handler=True) 40 | logger_logs = get_logger('DEBUG', level=logging.DEBUG, is_add_file_handler=True, is_add_stream_handler=False, 41 | log_path='Logs/', log_filename='latest.log') 42 | 43 | 44 | def rs_tencent_check(response): 45 | """ 46 | check if banned by tencent-fire-wall [您的请求已被该站点的安全策略拦截] 47 | :param response: 48 | :return: bools True or False 49 | """ 50 | baned_text = '您的请求已被该站点的安全策略拦截' 51 | if baned_text in response: 52 | logger_stream.error('请求已经被沟槽的腾讯防火墙拦截,暂时没有解决办法,建议一个小时左右后重试') 53 | if debug: 54 | print('Baned by Tencent-Fire-Wall.') 55 | return True 56 | else: 57 | if debug: 58 | print('Tencent-Fire-Wall passed.') 59 | return False 60 | 61 | 62 | def rs_config_check(result): 63 | """ 64 | check config.ini 65 | :return: True or False 66 | """ 67 | if ((rs_config.get('Normal', 'daoyukey', ) != '' 68 | or rs_config.get('Normal', 'manuid', ) != '' 69 | or rs_config.get('Normal', 'deviceid', ) != '') 70 | or rs_config.get('Normal', 'showusername', ) != ''): 71 | if debug: 72 | print('Everything which i need is alright.') 73 | return True 74 | else: 75 | logger_stream.error('重要参数缺失,请检查Config.ini是否正确配置,程序即将结束。') 76 | sys.exit() 77 | 78 | 79 | def rs_get_temp_cookies(): 80 | """ 81 | get a temp cookies for rising stones login 82 | :return: temp_cookies: temp cookies for rising stones login 83 | """ 84 | 85 | tp_cookies_url = 'https://apiff14risingstones.web.sdo.com/api/home/GHome/isLogin' 86 | headers = { 87 | **constant.RS_HEADERS_GET 88 | } 89 | with httpx.Client(http2=True) as client: 90 | get_tp_cookies_response = client.get(tp_cookies_url, headers=headers) 91 | tp_cookies = get_tp_cookies_response.cookies.get('ff14risingstones') 92 | if tp_cookies != '': 93 | if debug: 94 | print('Temp-Cookies: ' + tp_cookies) 95 | return tp_cookies 96 | else: 97 | logger_logs.error(f'Temp-Cookies is none {get_tp_cookies_response.text}') 98 | if debug: 99 | print('Temp-Cookies is fucked') 100 | return None 101 | 102 | 103 | def rs_get_flowid(): 104 | """ 105 | get flowid for rising stones login 106 | :return: flowid 107 | """ 108 | user_sessid = rs_config.get('Normal', 'daoyukey') 109 | device_id = '-'.join(str(uuid.uuid4()).upper() for _ in range(5)) 110 | manuid = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(6)) 111 | get_flowid_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/initialize' 112 | get_flowid_headers = { 113 | **constant.RS_MIN_HEADERS 114 | } 115 | get_flowid_params = { 116 | **constant.RS_PARAMS, 117 | 'device_id': device_id, 118 | 'device_manuid': manuid, 119 | 'USERSESSID': user_sessid, 120 | } 121 | get_flowid_cookies = { 122 | 'USERSESSID': user_sessid 123 | } 124 | with httpx.Client(http2=True) as client: 125 | get_flowid_response = client.get(get_flowid_url, params=get_flowid_params, cookies=get_flowid_cookies, 126 | headers=get_flowid_headers) 127 | try: 128 | flowid = get_flowid_response.json()['data']['flowId'] 129 | if flowid != '': 130 | if debug: 131 | print('Flowid: ' + str(flowid)) 132 | return flowid 133 | except KeyError as e: 134 | logger_logs.error(f'KeyError: {e}') 135 | if debug: 136 | print('KeyError: ' + str(e)) 137 | return None 138 | 139 | 140 | def rs_get_account_id_list(flowid): 141 | """ 142 | query account id list 143 | :return: return a account id list like {"accountId": "123456789","displayName": "Username"} 144 | """ 145 | user_sessid = rs_config.get('Normal', 'daoyukey') 146 | device_id = '-'.join(str(uuid.uuid4()).upper() for _ in range(5)) 147 | manuid = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(6)) 148 | 149 | get_account_id_list_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/queryAccountList' 150 | get_account_id_list_params = { 151 | **constant.RS_PARAMS, 152 | 'device_id': device_id, 153 | 'device_manuid': manuid, 154 | 'USERSESSID': user_sessid, 155 | 'flowId': flowid 156 | } 157 | get_account_id_header = { 158 | **constant.RS_MIN_HEADERS 159 | } 160 | get_account_id_list_cookies = { 161 | 'USERSESSID': user_sessid, 162 | } 163 | with httpx.Client(http2=True) as client: 164 | get_account_id_list_response = client.get(get_account_id_list_url, params=get_account_id_list_params, 165 | headers=get_account_id_header, 166 | cookies=get_account_id_list_cookies) 167 | get_account_id_list_json = get_account_id_list_response.json() 168 | if get_account_id_list_json['return_message'] == 'success': 169 | account_id_list = get_account_id_list_json['data']['accountList'] 170 | if debug: 171 | print(account_id_list) 172 | logger_stream.info(f'拉取账号列表成功,当前主账户下发现{len(account_id_list)}个子账户.') 173 | return account_id_list 174 | else: 175 | logger_stream.info(f'从服务器拉取子账号列表失败,请检查config.ini中的参数是否填写准确。') 176 | if debug: 177 | print(get_account_id_list_json) 178 | logger_logs.error(f'Get accountList error,{get_account_id_list_json}。') 179 | return None 180 | 181 | 182 | def rs_make_confirm(account_id, flowid): 183 | """ 184 | 服务器鉴权 185 | :param account_id: 从get_account_id_list中获取的真实用户ID 186 | :param flowid: 浮动值 187 | :return: 成功返回True 失败返回False 188 | """ 189 | user_sessid = rs_config.get('Normal', 'daoyukey') 190 | device_id = '-'.join(str(uuid.uuid4()).upper() for _ in range(5)) 191 | manuid = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(6)) 192 | 193 | make_confirm_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/chooseAccount?' 194 | make_confirm_header = { 195 | **constant.RS_MIN_HEADERS 196 | } 197 | make_confirm_params = { 198 | **constant.RS_PARAMS, 199 | 'device_id': device_id, 200 | 'device_manuid': manuid, 201 | 'USERSESSID': user_sessid, 202 | 'flowId': flowid, 203 | 'accountId': account_id 204 | } 205 | make_confirm_cookies = { 206 | 'USERSESSID': user_sessid, 207 | } 208 | with httpx.Client(http2=True) as client: 209 | make_confirm_response = client.get(make_confirm_url, params=make_confirm_params, 210 | headers=make_confirm_header, cookies=make_confirm_cookies) 211 | make_confirm_json = make_confirm_response.json() 212 | confirm_message = make_confirm_json['return_message'] 213 | if confirm_message == 'success': 214 | if debug: 215 | print(f'Confirm with server Successful, AccountID:{account_id}') 216 | return True 217 | else: 218 | if debug: 219 | print(make_confirm_json) 220 | logger_stream.info(f'与服务器鉴权中发生错误,请将Logs反馈给开发者') 221 | logger_logs.error(f'Confirm with server Failed :{confirm_message}') 222 | return False 223 | 224 | 225 | def rs_get_sub_account_key(flowid): 226 | """ 227 | 获取子账号的DaoyuKey 228 | :return: 成功返回子账号的DaoyuKey 例如 DYA-xxxxxxxxx 失败返回None 229 | """ 230 | user_sessid = rs_config.get('Normal', 'daoyukey') 231 | device_id = '-'.join(str(uuid.uuid4()).upper() for _ in range(5)) 232 | manuid = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(6)) 233 | 234 | get_daoyu_ticket_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/confirm?' 235 | get_daoyu_ticket_header = { 236 | **constant.RS_MIN_HEADERS 237 | } 238 | get_daoyu_ticket_params = { 239 | **constant.RS_PARAMS, 240 | 'device_id': device_id, 241 | 'device_manuid': manuid, 242 | 'USERSESSID': user_sessid, 243 | 'flowId': flowid 244 | } 245 | get_daoyu_ticket_cookies = { 246 | 'USERSESSID': user_sessid, 247 | } 248 | with httpx.Client(http2=True) as client: 249 | get_daoyu_ticket_response = client.get(get_daoyu_ticket_url, params=get_daoyu_ticket_params, 250 | headers=get_daoyu_ticket_header, cookies=get_daoyu_ticket_cookies) 251 | get_sub_account_key_json = get_daoyu_ticket_response.json() 252 | if get_sub_account_key_json['return_code'] == 0: 253 | try: 254 | sub_account_key = get_sub_account_key_json['data']['authorization'] 255 | if debug: 256 | print(f'Get Sub_Account_Key Successful, User_sessionID:{sub_account_key}') 257 | return sub_account_key 258 | except KeyError: 259 | logger_stream.info(f'该账号游戏内没有创建角色,跳过操作。') 260 | return None 261 | else: 262 | logger_logs.error(f'Get Sub_Account_Key error,{get_sub_account_key_json}') 263 | logger_stream.info(f'获取叨鱼子账号票据失败,请将Logs反馈给开发者') 264 | return None 265 | 266 | 267 | def rs_dao_login(sub_account_key, temp_cookies): 268 | """ 269 | # step in risingStones loginnnnnn. fucking new way to get DaoyuToken 270 | :return: DaoyuToken 271 | """ 272 | 273 | dao_login_url = 'https://apiff14risingstones.web.sdo.com/api/home/GHome/daoLogin' 274 | dao_login_params = { 275 | 'authorization': sub_account_key 276 | } 277 | dao_login_headers = { 278 | **constant.RS_HEADERS_GET 279 | } 280 | dao_login_cookies = { 281 | 'ff14risingstones': temp_cookies 282 | } 283 | with httpx.Client(http2=True) as client: 284 | dao_login_response = client.get(dao_login_url, headers=dao_login_headers, params=dao_login_params, 285 | cookies=dao_login_cookies) 286 | dao_login_json = dao_login_response.json() 287 | if dao_login_json['code'] == 10000: 288 | daoyu_token = dao_login_json['data']['DaoyuToken'] 289 | if debug: 290 | print(f'Get DaoyuToken Successful, DaoyuToken:{daoyu_token}') 291 | return daoyu_token 292 | else: 293 | logger_logs.error(f'Get DaoyuToken error,{dao_login_response}') 294 | logger_stream.info(f'获取DaoyuToken失败,请将Logs反馈给开发者') 295 | return None 296 | 297 | 298 | def rs_do_login(daoyu_ticket, temp_cookies): 299 | rs_login_url = 'https://apiff14risingstones.web.sdo.com/api/home/GHome/isLogin' 300 | rs_login_headers = { 301 | **constant.RS_HEADERS_GET, 302 | 'authorization': daoyu_ticket, 303 | } 304 | rs_login_cookies = { 305 | 'ff14risingstones': temp_cookies 306 | } 307 | with httpx.Client(http2=True) as client: 308 | rs_login_response = client.get(rs_login_url, headers=rs_login_headers, cookies=rs_login_cookies) 309 | rs_login_json = rs_login_response.json() 310 | if rs_login_json['code'] == 10000: 311 | if debug: 312 | print(f'当前账户:[{rs_login_json["data"]["area_name"]}]' 313 | f'[{rs_login_json["data"]["group_name"]}]' 314 | f'[{rs_login_json["data"]["character_name"]}]') 315 | rs_cookies = rs_login_response.cookies.get('ff14risingstones') 316 | rs_login_status = True 317 | return rs_login_status, rs_cookies 318 | elif rs_login_json['code'] == 10103: 319 | rs_cookies = '' 320 | rs_login_status = False 321 | logger_stream.info(f'清先用手机石之家绑定游戏内角色后重试,详情请查看Github上相关说明') 322 | return rs_login_status, rs_cookies 323 | else: 324 | rs_cookies = '' 325 | rs_login_status = False 326 | if debug: 327 | print(f'rs login failed' + rs_login_json) 328 | logger_logs.error(f'Login error,{rs_login_json}') 329 | logger_stream.info(f'其他错误导致登录失败,请将Logs反馈给开发者') 330 | 331 | return rs_login_status, rs_cookies 332 | 333 | 334 | def rs_bind(cookies, daoyu_ticket): 335 | bind_url = 'https://apiff14risingstones.web.sdo.com/api/home/groupAndRole/getCharacterBindInfo?platform=1' 336 | bind_headers = { 337 | **constant.RS_HEADERS_GET, 338 | 'authorization': daoyu_ticket, 339 | } 340 | bind_cookies = { 341 | 'ff14risingstones': cookies 342 | } 343 | with httpx.Client(http2=True) as client: 344 | bind_response = client.get(bind_url, headers=bind_headers, cookies=bind_cookies) 345 | bind_cookies = bind_response.cookies.get('ff14risingstones') 346 | if debug: 347 | print(f'Bind user account Success. {bind_cookies}') 348 | return bind_cookies 349 | -------------------------------------------------------------------------------- /Utility/sqMall/sm_login.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: KuliPoi 3 | Contact: me@pipirapira.com 4 | Created: 2023-12-20 5 | File: sm_login.py 6 | Version: 2.5.0 7 | Description: DAOYU LOGIN, FUCK SQ BY WAY 8 | """ 9 | import logging 10 | import random 11 | import urllib3 12 | import requests 13 | import configparser 14 | import os 15 | import uuid 16 | import string 17 | from kuai_log import get_logger 18 | import re 19 | 20 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 21 | logger_stream = get_logger('INFO', level=logging.INFO, is_add_stream_handler=True) 22 | logger_logs = get_logger('DEBUG', level=logging.DEBUG, is_add_file_handler=True, is_add_stream_handler=False, 23 | log_path='Logs/',log_filename='latest.log') 24 | 25 | 26 | # DYKey打码 27 | def dykey_encrypt(self): 28 | return re.sub(r"(?<=DY_)(.*)(?=..)", lambda match: '*' * len(match.group(1)), self) 29 | 30 | 31 | # 配置文件读取 32 | def config_handler(): 33 | """ 34 | 读取配置文件 config.ini 35 | :return: 返回 手机号/配置服务器/设备ID/设备型号/DaoyuKey/ShowUsername 36 | """ 37 | config = configparser.ConfigParser() 38 | config.read('config.ini', encoding='utf-8') 39 | device_id = '-'.join(str(uuid.uuid4()).upper() for _ in range(5)) 40 | manuid = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(6)) 41 | daoyu_key = config.get('Normal', 'DaoyuKey') 42 | show_username = config.get('Normal', 'ShowUsername') 43 | return device_id, manuid, daoyu_key, show_username 44 | 45 | 46 | # 初始化检测 47 | def initialize(): 48 | """ 49 | 初始化检测 50 | :return: 本地已有配置文件,从云端同步成功返回True / 从云端同步失败返回None 51 | """ 52 | if os.path.exists('config.ini'): # 检测配置文件是否存在 53 | logger_stream.info('检测到存在配置文件,无需进行初始化...') 54 | return True 55 | else: 56 | url = 'https://pipirapira.com/config/config.ini' 57 | local_filename = 'config.ini' 58 | try: 59 | logger_stream.info('检测到第一次运行,正在从云端同步配置文件..') 60 | requests.get(url, verify=False).raise_for_status() 61 | with open(local_filename, 'wb') as file: 62 | file.write(requests.get(url).content) 63 | logger_stream.info('从云端同步配置文件成功') 64 | return True 65 | except requests.exceptions.RequestException as e: 66 | logger_stream.error(f'同步配置失败,请检查网络连接或反馈给开发者, {e}') 67 | return None 68 | 69 | 70 | # 获取guid 71 | def get_guid(device_id, manuid): 72 | """ 73 | Guid 是叨鱼App 结合设备ID和设备型号产生的一个参数会在登录前提供生成 Scene 莫名其妙的参数 大概是叨鱼的app的信息 74 | :param device_id: 设备ID 75 | :param manuid: 设备型号 76 | :return: 成功会返回 guid 和 scene 失败则返回None 因为Scene很莫名其妙所以不敢写死 动态获取吧 77 | """ 78 | guid_url = 'https://daoyu.sdo.com/api/userCommon/getGuid' 79 | guid_params = { 80 | 'phone': 'phone', 81 | 'media_channel': 'AppStore', 82 | 'device_id': {device_id}, 83 | 'app_version': 'i.9.3.3', 84 | 'key': 'key', 85 | 'device_os': 'iOS17.0', 86 | 'idfa': '00000000-0000-0000-0000-000000000000', 87 | 'src_code': '8', 88 | 'device_manuid': {manuid}, 89 | } 90 | headers = { 91 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 92 | 'content-type': 'application/json', 93 | } 94 | 95 | guid_response = requests.get(guid_url, params=guid_params, headers=headers, verify=False) 96 | guid_json = guid_response.json() 97 | guid = guid_json['data']['guid'] 98 | scene = guid_json['data']['scene'] 99 | if guid != '' and scene != '': 100 | logger_logs.debug('Guid and scene get Successful. Guid:' + guid + ' scene:' + scene) 101 | return guid, scene 102 | else: 103 | logger_logs.error('Guid or Scene Error Guid:' + guid + 'scene:' + scene) 104 | logger_stream.info('致命错误,获取Guid失败了,请将Logs反馈给开发者,程序即将停止。') 105 | return None 106 | 107 | 108 | # DaoyuTicket 109 | # step 1/4 Get flowid 110 | def get_flowid(manuid, deviceid, sessionid, show_username): 111 | """ 112 | 获取浮动值 用来校验 校验四联的第一步 113 | :param manuid: 设备型号 114 | :param deviceid: 设备ID 115 | :param sessionid: Daoyu_Key 主账号的关键参数 类似 DY_xxxxx 手机号登录之后生成,不重新登陆一般不会改变 116 | :param show_username: 一般是叨鱼登录手机号的中间四位打码值 如 138****1234 117 | :return: 成功则返回浮动值 失败返回None 118 | """ 119 | get_flowid_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/initialize' 120 | get_flowid_params = { 121 | 'media_channel': 'AppStore', 122 | 'device_os': 'iOS17.0', 123 | 'idfa': '00000000-0000-0000-0000-000000000000', 124 | 'circle_id': '854742', 125 | 'app_version': 'i.9.3.3', 126 | 'device_manuid': manuid, 127 | 'src_code': '8', 128 | 'clientId': 'qu_shop', 129 | 'appId': '6666', 130 | 'scope': 'get_account_profile', 131 | 'extend': '', 132 | 'device_id': deviceid 133 | } 134 | get_flowid_header = { 135 | 'authority': 'daoyu.sdo.com', 136 | 'method': 'GET', 137 | 'scheme': 'https', 138 | 'content-type': 'application/json', 139 | 'accept': '*/*', 140 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 141 | 'accept-language': 'zh-Hans-CN;q=1, zh-Hant-CN;q=0.9', 142 | 'accept-encoding': 'gzip, deflate, br', 143 | } 144 | get_flowid_cookies = { 145 | 'USERSESSID': sessionid, 146 | 'is_login': '1', 147 | 'show_username': show_username, 148 | } 149 | get_flowid_response = requests.get(get_flowid_url, params=get_flowid_params, headers=get_flowid_header, 150 | cookies=get_flowid_cookies, verify=False) 151 | get_flowid_json = get_flowid_response.json() 152 | if get_flowid_json['return_code'] == 0: 153 | flowid = get_flowid_json['data']['flowId'] 154 | logger_logs.info(f'Get flowID Successful : {flowid}') 155 | return flowid 156 | else: 157 | logger_stream.info('无法获取叨鱼浮动值,请将日志文件发送给开发者') 158 | logger_logs.error(f'Get flowID Error : {get_flowid_json}') 159 | return None 160 | 161 | 162 | # step 2/4 Get accountID list 163 | def get_account_id_list(flowid, deviceid, manuid, sessionid, show_username): 164 | """ 165 | 拉取子账号列表,每个叨鱼我们称之为一个主账号和多个子账号 校验四联的第二步 166 | :param flowid: 浮动值 167 | :param deviceid: 设备ID 168 | :param manuid: 设备型号 169 | :param sessionid: Daoyu_Key 主账号的关键参数 类似 DY_xxxxx 手机号登录之后生成,不重新登陆一般不会改变 170 | :param show_username: 一般是叨鱼登录手机号的中间四位打码值 如 138****1234 171 | :return: 成功则返回一个列表 包含账号的真实ID和登录名 如 {"accountId": "123456789","displayName": "Username"} 172 | """ 173 | get_account_id_list_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/queryAccountList' 174 | get_account_id_list_params = { 175 | 'flowId': flowid, 176 | 'idfa': '00000000-0000-0000-0000-000000000000', 177 | 'device_manuid': manuid, 178 | 'src_code': '8', 179 | 'circle_id': '854742', 180 | 'device_os': 'iOS17.0', 181 | 'media_channel': 'AppStore', 182 | 'app_version': 'i.9.3.3', 183 | 'device_id': deviceid 184 | } 185 | get_account_id_header = { 186 | 'authority': 'daoyu.sdo.com', 187 | 'method': 'GET', 188 | 'scheme': 'https', 189 | 'content-type': 'application/json', 190 | 'accept': '*/*', 191 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 192 | 'accept-language': 'zh-Hans-CN;q=1, zh-Hant-CN;q=0.9', 193 | 'accept-encoding': 'gzip, deflate, br', 194 | } 195 | get_account_id_list_cookies = { 196 | 'USERSESSID': sessionid, 197 | 'is_login': '1', 198 | 'show_username': show_username, 199 | } 200 | get_account_id_list_response = requests.get(get_account_id_list_url, params=get_account_id_list_params, 201 | headers=get_account_id_header, 202 | cookies=get_account_id_list_cookies, verify=False) 203 | get_account_id_list_json = get_account_id_list_response.json() 204 | if get_account_id_list_json['return_message'] == 'success': 205 | account_id_list = get_account_id_list_json['data']['accountList'] 206 | logger_stream.info(f'当前主账户下存在{len(account_id_list)}个账户.') 207 | return account_id_list 208 | else: 209 | logger_stream.info(f'拉取子账号列表失败,请将Logs反馈给开发者') 210 | logger_logs.error(f'Get accountList error,{get_account_id_list_json}。') 211 | return None 212 | 213 | 214 | # step 3/4 Confirm with server 215 | def make_confirm(account_id, flowid, deviceid, manuid, sessionid, show_username): 216 | """ 217 | 和服务器握手确认登录 218 | :param account_id: 从get_account_id_list中获取的真实用户ID 219 | :param flowid: 浮动值 220 | :param deviceid: 设备ID 221 | :param manuid: 设备型号 222 | :param sessionid: Daoyu_Key 主账号的关键参数 类似 DY_xxxxx 手机号登录之后生成,不重新登陆一般不会改变 223 | :param show_username: 一般是叨鱼登录手机号的中间四位打码值 如 138****1234 224 | :return: 成功返回True 失败返回False 225 | """ 226 | make_confirm_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/chooseAccount?' 227 | make_confirm_header = { 228 | 'authority': 'daoyu.sdo.com', 229 | 'method': 'GET', 230 | 'scheme': 'https', 231 | 'content-type': 'application/json', 232 | 'accept': '*/*', 233 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 234 | 'accept-language': 'zh-Hans-CN;q=1, zh-Hant-CN;q=0.9', 235 | 'accept-encoding': 'gzip, deflate, br', 236 | } 237 | make_confirm_params = { 238 | 'idfa': '00000000-0000-0000-0000-000000000000', 239 | 'flowId': flowid, 240 | 'device_os': 'iOS17.0', 241 | 'media_channel': 'AppStore', 242 | 'accountId': account_id, 243 | 'device_manuid': manuid, 244 | 'app_version': 'i.9.3.3', 245 | 'src_code': '8', 246 | 'device_id': deviceid, 247 | 'circle_id': '854742' 248 | } 249 | make_confirm_cookies = { 250 | 'USERSESSID': sessionid, 251 | 'is_login': '1', 252 | 'show_username': show_username, 253 | } 254 | make_confirm_response = requests.get(make_confirm_url, params=make_confirm_params, 255 | headers=make_confirm_header, cookies=make_confirm_cookies, verify=False) 256 | make_confirm_json = make_confirm_response.json() 257 | confirm_message = make_confirm_json['return_message'] 258 | if confirm_message == 'success': 259 | logger_logs.debug(f'Confirm with server Successful, AccountID:{account_id}') 260 | return True 261 | else: 262 | logger_stream.info(f'和服务器握手途中发生错误,请将Logs反馈给开发者') 263 | logger_logs.error(f'Confirm with server Failed :{confirm_message}') 264 | return False 265 | 266 | 267 | # step 4/4 Get DaoyuTicket 268 | def get_sub_account_key(flowid, manuid, deviceid, sessionid, show_username): 269 | """ 270 | 获取子账号的DaoyuKey 271 | :param flowid: 浮动值 272 | :param manuid: 设备型号 273 | :param deviceid: 设备ID 274 | :param sessionid: Daoyu_Key 主账号的关键参数 类似 DY_xxxxx 手机号登录之后生成,不重新登陆一般不会改变 275 | :param show_username: 一般是叨鱼登录手机号的中间四位打码值 如 138****1234 276 | :return: 成功返回子账号的DaoyuKey 例如 DYA-xxxxxxxxx 失败返回None 277 | """ 278 | get_daoyu_ticket_url = 'https://daoyu.sdo.com/api/thirdPartyAuth/confirm?' 279 | get_daoyu_ticket_header = { 280 | 'authority': 'daoyu.sdo.com', 281 | 'method': 'GET', 282 | 'scheme': 'https', 283 | 'content-type': 'application/json', 284 | 'accept': '*/*', 285 | 'user-agent': 'SdAccountKeyM/9.3.3 (iPhone; iOS 17.0; Scale/3.00)', 286 | 'accept-language': 'zh-Hans-CN;q=1, zh-Hant-CN;q=0.9', 287 | 'accept-encoding': 'gzip, deflate, br', 288 | } 289 | get_daoyu_ticket_params = { 290 | 'app_version': 'i.9.3.3', 291 | 'flowId': flowid, 292 | 'idfa': '00000000-0000-0000-0000-000000000000', 293 | 'circle_id': '854742', 294 | 'device_manuid': manuid, 295 | 'media_channel': 'AppStore', 296 | 'device_os': 'iOS17.0', 297 | 'device_id': deviceid, 298 | 'src_code': '8' 299 | } 300 | get_daoyu_ticket_cookies = { 301 | 'USERSESSID': sessionid, 302 | 'is_login': '1', 303 | 'show_username': show_username, 304 | } 305 | get_daoyu_ticket_response = requests.get(get_daoyu_ticket_url, params=get_daoyu_ticket_params, 306 | headers=get_daoyu_ticket_header, cookies=get_daoyu_ticket_cookies, 307 | verify=False) 308 | get_daoyu_ticket_json = get_daoyu_ticket_response.json() 309 | if get_daoyu_ticket_json['return_code'] == 0: 310 | daoyu_ticket = get_daoyu_ticket_json['data']['authorization'] 311 | logger_logs.debug(f'Get_daoyuSubKey Successful') 312 | return daoyu_ticket 313 | else: 314 | logger_logs.error(f'Get_daoyuTicket error,{get_daoyu_ticket_json}') 315 | logger_stream.info(f'获取叨鱼子账号票据失败,请将Logs反馈给开发者') 316 | return None 317 | 318 | 319 | # get_temp_session ID 320 | def get_temp_sessionid(main_key): 321 | get_temp_sessionid_url = 'https://sqmallservice.u.sdo.com/api/us/daoyu/account/getMallLoginStatus' 322 | get_temp_sessionid_header = { 323 | 'authority': 'sqmallservice.u.sdo.com', 324 | 'method': 'GET', 325 | 'scheme': 'https', 326 | 'pragma': 'no-cache', 327 | 'cache-control': 'no-cache', 328 | 'qu-deploy-platform': '4', 329 | 'accept': 'application/json, text/javascript, */*; q=0.01', 330 | 'qu-merchant-id': '', 331 | 'qu-hardware-platform': '1', 332 | 'qu-software-platform': '2', 333 | 'user-agent': 'Mozilla/5.0 (Linux; Android 12; 2206122SC Build/V417IR; wv) AppleWebKit/537.36 (KHTML, ' 334 | 'like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 DaoYu/9.4.8', 335 | 'qu-web-host': 'https://m.qu.sdo.com', 336 | 'origin': 'https://m.qu.sdo.com', 337 | 'x-requested-with': 'com.sdo.sdaccountkey', 338 | 'sec-fetch-site': 'same-site', 339 | 'sec-fetch-mode': 'cors', 340 | 'sec-fetch-dest': 'empty', 341 | 'referer': 'https://m.qu.sdo.com/', 342 | 'accept-encoding': 'gzip, deflate', 343 | 'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', 344 | } 345 | get_temp_sessionid_params = { 346 | 'USERSESSID': main_key 347 | } 348 | get_temp_sessionid_response = requests.get(get_temp_sessionid_url, params=get_temp_sessionid_params, 349 | headers=get_temp_sessionid_header, verify=False) 350 | session_id = get_temp_sessionid_response.cookies.get('sessionId') 351 | return session_id 352 | 353 | 354 | # Get sub_account_session 355 | def get_sub_account_session(sub_account_key, temp_account_sessionid): 356 | get_sub_account_session_url = 'https://sqmallservice.u.sdo.com/api/us/daoyu/account/switch' 357 | get_sub_account_session_header = { 358 | 'authority': 'sqmallservice.u.sdo.com', 359 | 'method': 'GET', 360 | 'scheme': 'https', 361 | 'qu-merchant-id': '1', 362 | 'accept': 'application/json, text/javascript, */*; q=0.01', 363 | 'qu-deploy-platform': '8', 364 | 'sec-fetch-site': 'same-site', 365 | 'qu-web-host': 'https://m.qu.sdo.com', 366 | 'accept-language': 'zh-CN,zh-Hans;q=0.9', 367 | 'sec-fetch-mode': 'cors', 368 | 'accept-encoding': 'gzip, deflate, br', 369 | 'origin': 'https://m.qu.sdo.com', 370 | 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 DaoYu/9.3.3', 371 | 'referer': 'https://m.qu.sdo.com/', 372 | 'qu-hardware-platform': '2', 373 | 'qu-software-platform': '2', 374 | 'sec-fetch-dest': 'empty' 375 | } 376 | get_sub_account_session_params = { 377 | 'daoyuTicket': sub_account_key 378 | } 379 | get_sub_account_session_cookies = { 380 | 'sessionId': temp_account_sessionid 381 | } 382 | get_temp_sessionid_response = requests.get(get_sub_account_session_url, params=get_sub_account_session_params, 383 | headers=get_sub_account_session_header, 384 | cookies=get_sub_account_session_cookies, 385 | verify=False) 386 | session_id = get_temp_sessionid_response.cookies.get('sessionId') 387 | return session_id 388 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc.