├── 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 | [![1703144086065.webp](https://infrasimage-r2.cf.cdn.infras.host/2023/12/21/6583ea8deed28.webp)](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 | [![1703144461590.webp](https://infrasimage-r2.cf.cdn.infras.host/2023/12/21/6583ec04d0242.webp)](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 |
2 |

··~ 萨雷安の军火库 ~··

3 |
4 |
5 | ASK 6 | Wiki 7 | Language 8 | Python Version 9 | License 10 | Last Commit 11 |
12 | 13 | ### 重中之重 14 | 本项目 **遵循AGPL V3协议开源 请勿商用** 15 | 本项目仅在GitHub上开源发布,如您通过其他方式获取则有可能对您的账号造成盗号风险请自行鉴别。 16 | 本项目完全使用官方API进行操作,按理来说不存在任何封禁风险,除非盛趣闲的蛋疼。 17 | 在任何情况下都不要泄露自己的**UserSessionID**给其他人 18 | 19 | **🥳 2.1.4 版本之后 抓包已经无需依赖手机叨鱼APP 让我们欢呼 让我们庆祝 顺便感谢盛趣已经破碎的社区梦** 20 | **😋 2.1.5 版本之后 进入稳定期 如果没有需要修复的内容将大幅度降低更新频率 如果发现问题请前往issues反馈** 21 | 22 | ### 使用教程 23 | 1. 下载[Releases](https://github.com/FF14CN/Sarean-arsenal/releases/latest)中最新发布的源码 24 | 2. 进入项目根目录 **目前仅支持 Python 3.11 环境** 25 | 3. 安装依赖库 ```pip -r requirements.txt``` 26 | 4. 然后根据自己的需求修改``` config.ini ```,每个模块都有对应的教程,如果您对Config的内容有任何疑问请查阅```config.ini.example``` 27 | 5. 运行本项目 python .\run.py,如需使用青龙面板请使用 ```qinglong.py``` 28 | 29 | ### 盛趣积分商城签到模块 30 | 可以实现盛趣商城的多账户签到 31 | 32 | ### 石之家模块 33 | 即可实现石之家多账户自动签到、领取奖励、房屋过期预警的功能。 34 | 35 | 当你准备好了,请点击这里查看[详细教程](https://github.com/FF14CN/Sarean-arsenal/wiki/%5B%E6%9C%80%E6%96%B0%5D-%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B) 36 | 37 | ## Other 38 | 项目开发计划与进度请移步至 [Dev Status](https://github.com/orgs/FF14CN/projects/1) 39 | 详细视频教程请观看 [B站视频](https://www.bilibili.com/video/BV1Gg4y1k7dr) 40 | [已过期]IOS详细叨鱼抓包教程 [B站视频](https://www.bilibili.com/video/BV1Ka4y1z71c) 41 | ## License 42 | 43 | [With AGPL V3](https://github.com/FF14CN/FF14AutoSign/blob/main/LICENSE) 44 | -------------------------------------------------------------------------------- /Utility/risingStones/constant.py: -------------------------------------------------------------------------------- 1 | """ 2 | author: KuLiPa 3 | contact: me@pipirapira.com 4 | created: 2024-07-02 5 | file: constant.py 6 | version: 1.0.0 7 | description: All headers 8 | """ 9 | 10 | RS_MIN_HEADERS = { 11 | 'authority': 'daoyu.sdo.com', 12 | 'method': 'GET', 13 | 'scheme': 'https', 14 | 'accept-encoding': 'gzip', 15 | 'user-agent': 'okhttp/2.5.0' 16 | } 17 | 18 | RS_HEADERS_GET = { 19 | 'authority': 'apiff14risingstones.web.sdo.com', 20 | 'method': 'GET', 21 | 'scheme': 'https', 22 | 'pragma': 'no-cache', 23 | 'cache-control': 'no-cache', 24 | 'accept': 'application/json, text/plain, */*', 25 | 'user-agent': 'Mozilla/5.0 (Linux; Android 12; V2218A Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 DaoYu/9.4.14', 26 | 'origin': 'https://ff14risingstones.web.sdo.com', 27 | 'x-requested-with': 'com.sdo.sdaccountkey', 28 | 'sec-fetch-site': 'same-site', 29 | 'sec-fetch-mode': 'cors', 30 | 'sec-fetch-dest': 'empty', 31 | 'referer': 'https://ff14risingstones.web.sdo.com/', 32 | 'accept-encoding': 'gzip, deflate', 33 | 'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7' 34 | } 35 | 36 | RS_HEADERS_POST_TEST = { 37 | 'user-agent': 'Mozilla/5.0 (Linux; Android 12; V2218A Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 DaoYu/9.4.14', 38 | 'Host': 'apiff14risingstones.web.sdo.com' 39 | } 40 | 41 | RS_HEADERS_POST = { 42 | 'authority': 'apiff14risingstones.web.sdo.com', 43 | 'method': 'POST', 44 | 'scheme': 'https', 45 | 'content-length': '0', 46 | 'pragma': 'no-cache', 47 | 'cache-control': 'no-cache', 48 | 'accept': 'application/json, text/plain, */*', 49 | 'user-agent': 'Mozilla/5.0 (Linux; Android 12; UI12c Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36 DaoYu/9.4.14', 50 | 'origin': 'https://ff14risingstones.web.sdo.com', 51 | 'x-requested-with': 'com.sdo.sdaccountkey', 52 | 'sec-fetch-site': 'same-site', 53 | 'sec-fetch-mode': 'cors', 54 | 'sec-fetch-dest': 'empty', 55 | 'referer': 'https://ff14risingstones.web.sdo.com/', 56 | 'accept-encoding': 'gzip, deflate', 57 | 'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7' 58 | } 59 | 60 | RS_PARAMS = { 61 | 'src_code': '4', 62 | 'app_version': '9.4.14', 63 | 'app_version_code': '688', 64 | 'device_gid': '_08:ff:3d:32:60:00', 65 | 'device_os': '12', 66 | 'device_manufacturer': 'vivo', 67 | 'device_txzDeviceId': '', 68 | '_dispath': '0', 69 | 'clientId': 'ff14risingstones', 70 | 'appId': '6788', 71 | 'scope': 'get_account_profile', 72 | 'extend': '', 73 | 'scene': '' 74 | } 75 | -------------------------------------------------------------------------------- /Deprecated/QRCode.py: -------------------------------------------------------------------------------- 1 | import requests, time, qrcode, json, os, configparser 2 | from pyzbar.pyzbar import decode 3 | from PIL import Image 4 | from datetime import datetime 5 | 6 | header = { 7 | 'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' 8 | } 9 | 10 | timestamp = lambda: int(round(time.time() * 1000)) 11 | 12 | skey = None 13 | ticket_cookie = None 14 | ticket = None 15 | authCookie = None 16 | params_cookie = None 17 | cookie_combine = None 18 | 19 | deBug = 'False' 20 | initialization = 'False' 21 | cookieSaved = 'False' 22 | 23 | 24 | def json_handel(self): 25 | return self.replace('\\', '').replace('"{', "{").replace('}"', "}").replace('codeKeyLogin_JSONPMethod(', 26 | "").replace(')', '') 27 | 28 | 29 | def get_path(): 30 | return os.path.dirname(os.path.abspath(__file__)) 31 | 32 | 33 | def get_current_time(): 34 | return datetime.now().strftime("%H:%M:%S") 35 | 36 | 37 | # 处理QrCode 38 | def qrcode_get(appId,areaId): 39 | global skey, barcode_url 40 | # 获取二维码缓存到本地 41 | qrcode_url = "https://w.cas.sdo.com/authen/getcodekey.jsonp?&appId=" + str(appId) + "&areaId=" + str(areaId) + "/getcodekey.png" 42 | res = requests.get(qrcode_url, headers=header, verify=False) 43 | skey_cookie = res.cookies 44 | with open(f"{get_path()}/temp/qrcode.png", 'wb') as f: 45 | f.write(res.content) 46 | f.close() 47 | 48 | # 使用pyzbar解析缓存的二维码图片 49 | barcodes = decode(Image.open(f"{get_path()}/temp/qrcode.png")) 50 | for barcode in barcodes: 51 | barcode_url = barcode.data.decode("utf-8") 52 | 53 | # 使用qrcode生成二维码到终端 54 | qr = qrcode.QRCode() 55 | qr.add_data(barcode_url) 56 | qr.print_ascii(invert=True) 57 | 58 | # 将二维码携带的skey传递给登录阶段 59 | if barcode_url is not None: 60 | skey = skey_cookie 61 | print(f'[{get_current_time()}]获取登录二维码成功,请尽快使用叨鱼扫描') 62 | else: 63 | print(f'[{get_current_time()}]获取登录验证码失败,请反馈给开发者') 64 | 65 | 66 | # 进行登录 67 | def qrcode_check(appId, areaId, serviceUrl): 68 | show_qrcode = False 69 | global skey 70 | global ticket_cookie 71 | global ticket 72 | 73 | # 检查skey 74 | if skey is not None: 75 | 76 | url = ("https://w.cas.sdo.com/authen/codeKeyLogin.jsonp?callback=codeKeyLogin_JSONPMethod&appId=" 77 | + str(appId) 78 | + "&areaId=" 79 | + str(areaId) 80 | + "&code=300&serviceUrl=" 81 | + serviceUrl 82 | + f"&productId=2&productVersion=3.1.0&authenSource=2&_={timestamp}") 83 | 84 | # 进入二维码扫描判断 85 | while True: 86 | 87 | res = requests.get(url, headers=header, timeout=30, cookies=skey) 88 | s = requests.session() 89 | s.keep_alive = False 90 | 91 | # 替换Json中无效字符 92 | response = json_handel(res.text) 93 | obj = json.loads(response) 94 | return_code = obj["return_code"] 95 | 96 | # 是否已经展示二维码 97 | if not show_qrcode: 98 | show_qrcode = True 99 | print(f'[{get_current_time()}]' + '请速速打开叨鱼APP扫描二维码,不然我就哭给你看!') 100 | 101 | # 判断是否扫码 并将携带ticket的cookie传递 102 | if return_code != -10515805: 103 | ticket_cookie = res.cookies 104 | ticket = obj["data"]["ticket"] 105 | print( 106 | f'[{get_current_time()}]' + f'侦测到你拿起了手机,现在请放下手机并耐心等待.[你问我怎么知道你拿起了手机?]') 107 | return ticket 108 | else: 109 | print(f'[{get_current_time()}]' + '扫码失败,请检查网络连接或者尝试拍打一下电脑.') 110 | 111 | 112 | def login(appId, areaId, serviceUrl): 113 | """ 114 | :param appId: 应用ID 115 | :param areaId: 区域ID 116 | :param serviceUrl: 服务地址 117 | :return: 盛趣登录的Cookie,格式:json 118 | """ 119 | qrcode_get(appId, areaId) 120 | ticket_cookie = qrcode_check(appId, areaId, serviceUrl) 121 | return ticket_cookie 122 | -------------------------------------------------------------------------------- /Utility/risingStones/dailyTask.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: KuliPoi 3 | Contact: me@pipirapira.com 4 | Update: 2024-07-02 5 | File: DailyTask.py 6 | Version: 1.6.0 7 | Description: Start the game. 8 | """ 9 | from Utility.risingStones import signIn as rs_signin 10 | from Utility.risingStones import rs_login 11 | from Utility.risingStones import getSignReward 12 | from Utility.risingStones import getUserInfo 13 | from Utility.risingStones.rs_login import rs_get_flowid as get_flowid 14 | from Utility.risingStones.rs_login import rs_get_account_id_list as get_account_id_list 15 | from Utility.risingStones.rs_login import rs_make_confirm as make_confirm 16 | from Utility.risingStones.rs_login import rs_get_temp_cookies as get_temp_cookies 17 | from Utility.risingStones.rs_login import rs_get_sub_account_key as get_sub_account_key 18 | from Utility.risingStones.rs_login import rs_dao_login as dao_login 19 | from Utility.risingStones.rs_login import rs_do_login as do_login 20 | from Utility.risingStones.rs_login import rs_bind as rs_bind 21 | from Utility.risingStones.houseStatusChecker import house_status_checker as house_status_checker 22 | 23 | 24 | def daily_task(): 25 | final_msg = '' 26 | flowid = get_flowid() 27 | account_id_list = get_account_id_list(flowid) 28 | if account_id_list is not None: 29 | 30 | for index, account_id in enumerate(account_id_list): 31 | display_name = account_id["displayName"] 32 | rs_login.logger_stream.info(f'当前操作账户 [{index + 1}] [{display_name}]') 33 | if make_confirm(account_id["accountId"], flowid): 34 | cookies = get_temp_cookies() 35 | sub_account_key = get_sub_account_key(flowid) 36 | if sub_account_key is not None: 37 | daoyu_ticket = dao_login(sub_account_key, cookies) 38 | login_status, login_cookies = do_login(daoyu_ticket, cookies) 39 | if rs_login.debug: 40 | print(login_cookies) 41 | if login_status: 42 | # bind character 43 | bind_cookies = rs_bind(login_cookies, daoyu_ticket) 44 | # sign in 45 | bind_cookies = f'ff14risingstones={bind_cookies}' 46 | sign_in_msg = rs_signin.rs_signin(bind_cookies, daoyu_ticket) 47 | # Get Reward 48 | get_reward_msg = getSignReward.getReward(bind_cookies, daoyu_ticket) 49 | # Get Userinfo 50 | user_info = getUserInfo.get_rs_userinfo(bind_cookies, daoyu_ticket) 51 | # Get HouseInfo 52 | house_msg = house_status_checker(user_info) 53 | if rs_login.debug: 54 | print(f'sign-msg:{sign_in_msg}') 55 | print(f'get-reward-msg:{get_reward_msg}') 56 | print(f'house-info-msg:{house_msg}') 57 | msg = f'{display_name}石之家任务结果, {sign_in_msg}, {get_reward_msg}, {house_msg}' 58 | final_msg = msg + final_msg 59 | rs_login.logger_stream.info(final_msg) 60 | final_msg = msg + final_msg 61 | if rs_login.debug: 62 | print(final_msg) 63 | if index + 1 < len(account_id_list): 64 | flowid = get_flowid() 65 | else: 66 | if index + 1 < len(account_id_list): 67 | flowid = get_flowid() 68 | else: 69 | msg = f'{display_name}登录石之家失败,可能的原因是没有在手机端绑定游戏内角色。' 70 | final_msg = msg + final_msg 71 | if index + 1 < len(account_id_list): 72 | flowid = get_flowid() 73 | 74 | else: 75 | msg = f'{display_name}与服务器鉴权失败,请检查config.ini是否正确配置' 76 | final_msg = msg + final_msg 77 | if index + 1 < len(account_id_list): 78 | flowid = get_flowid() 79 | final_msg = msg + final_msg 80 | 81 | else: 82 | msg = '拉取账号列表失败,请检查config.ini中的参数是否正确设置' 83 | final_msg = msg + final_msg 84 | 85 | return final_msg 86 | -------------------------------------------------------------------------------- /Utility/sqMall/sqMallDoSign.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: KuliPoi 3 | Contact: me@pipirapira.com 4 | Created: 2023-12-21 5 | File: sqMailDoSign.py 6 | Version: 2.5.0 7 | Description: Do SQMALL AUTO SIGN, FUCK SQ BY THE WAY 8 | """ 9 | from Utility.sqMall import sm_login 10 | from Utility.sqMall.daoyuBuildinMallSign import daoyumall_sign 11 | from Utility.sqMall.daoyuBuildinMallBalance import daoyu_mall_balance 12 | import Utility.Notifications.push as pusher 13 | 14 | 15 | def main(): 16 | if sm_login.initialize(): 17 | device_id, manuid, main_key, show_username = sm_login.config_handler() 18 | sm_login.logger_logs.info(f'Get Config File Success,' 19 | f'show_username: {show_username}' 20 | f'daoyu_key: {sm_login.dykey_encrypt(main_key)}' 21 | f'device_id: {device_id}, ' 22 | f'manuid: {manuid}') 23 | if main_key != '' and show_username != '': 24 | sm_login.logger_stream.info('读取到了你手动设置的DaoyuKey和ShowUserName') 25 | elif main_key == '' or show_username == '': 26 | sm_login.logger_stream.info('DaoyuKey 或者 showUsername 为空 看Github上的教程 求求你辣') 27 | exit() 28 | else: 29 | sm_login.logger_stream.info('config.ini可能存在问题,发个issue看看,注意不要直接将你的Config文件直接发在issue里') 30 | exit() 31 | flowid = sm_login.get_flowid(manuid, device_id, main_key, show_username) 32 | account_id_list = sm_login.get_account_id_list(flowid, device_id, manuid, main_key, show_username) 33 | temp_account_sessionid = sm_login.get_temp_sessionid(main_key) 34 | if account_id_list is not None: 35 | results = [] 36 | for index, account_id in enumerate(account_id_list): 37 | if sm_login.make_confirm(account_id["accountId"], flowid, device_id, manuid, main_key, show_username): 38 | sub_account_key = sm_login.get_sub_account_key(flowid, manuid, device_id, main_key, show_username) 39 | sub_account_session = sm_login.get_sub_account_session(sub_account_key, temp_account_sessionid) 40 | sign_msg = daoyumall_sign(sub_account_session, account_id["accountId"]) 41 | if sign_msg == 0: 42 | sm_login.logger_stream.info( 43 | f'账号{account_id["displayName"]}签到成功,当前积分余额{daoyu_mall_balance(sub_account_session)}') 44 | sub_msg = f'账号{account_id["displayName"]}签到成功,当前积分余额{daoyu_mall_balance(sub_account_session)},' 45 | results.append(sub_msg) 46 | if index + 1 < len(account_id_list): 47 | flowid = sm_login.get_flowid(manuid, device_id, main_key, show_username) 48 | elif sign_msg == 1: 49 | sm_login.logger_stream.info( 50 | f'账号{account_id["displayName"]}已经签到过了,当前积分余额{daoyu_mall_balance(sub_account_session)}') 51 | sub_msg = f'账号{account_id["displayName"]}已经签到过了,当前积分余额{daoyu_mall_balance(sub_account_session)},' 52 | results.append(sub_msg) 53 | if index + 1 < len(account_id_list): 54 | flowid = sm_login.get_flowid(manuid, device_id, main_key, show_username) 55 | else: 56 | sm_login.logger_stream.info(f'账号{account_id["displayName"]}签到失败)') 57 | sub_msg = f'账号{account_id["displayName"]}签到失败),' 58 | results.append(sub_msg) 59 | if index + 1 < len(account_id_list): 60 | flowid = sm_login.get_flowid(manuid, device_id, main_key, show_username) 61 | else: 62 | sm_login.logger_stream.info(f'账号{account_id["displayName"]}与服务器握手失败') 63 | msg = pusher.push('盛趣商城自动签到助手', ''.join(results)) 64 | if msg['status'] == 'success': 65 | sm_login.logger_stream.info('推送消息成功') 66 | else: 67 | sm_login.logger_stream.info('推送消息失败,请检查消息推送服务是否配置成功') 68 | sm_login.logger_logs.error(msg) 69 | 70 | else: 71 | sm_login.logger_stream.info('没有发现你的账户,请检查logs文件') 72 | 73 | 74 | if __name__ == '__main__': 75 | main() 76 | -------------------------------------------------------------------------------- /Utility/risingStones/getSignReward.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import time 3 | from Utility.risingStones import rs_login 4 | from Utility.risingStones import constant 5 | import httpx 6 | import requests 7 | 8 | 9 | def getRewardStatus(cookies, daoyu_ticket): 10 | """ 11 | 获取签到奖励状态 12 | :param cookies: 石之家登录后的cookie 13 | """ 14 | currentTime = time.strftime("%Y-%m") 15 | RewardStatusurl = "https://apiff14risingstones.web.sdo.com/api/home/sign/signRewardList?" 16 | headers = { 17 | **constant.RS_HEADERS_GET, 18 | 'authorization': daoyu_ticket, 19 | } 20 | status_cookies = { 21 | 'ff14risingstones': cookies 22 | } 23 | with httpx.Client(http2=True) as client: 24 | rewardStatus = client.get(RewardStatusurl, headers=headers, cookies=status_cookies).json() 25 | signLogUrl = "https://apiff14risingstones.web.sdo.com/api/home/sign/mySignLog?month={}".format(currentTime) 26 | with httpx.Client(http2=True) as client: 27 | signLogCount = client.get(signLogUrl, headers=headers, cookies=status_cookies).json()["data"]["count"] 28 | if rewardStatus["code"] == 10000: 29 | return { 30 | "status": "success", 31 | "count": signLogCount, 32 | "data": rewardStatus["data"] 33 | } 34 | else: 35 | return { 36 | "status": "failed", 37 | "data": rewardStatus["msg"] 38 | } 39 | 40 | 41 | def getSignIDReward(cookies, rewardId, rewardMonth, daoyu_ticket): 42 | """ 43 | 获取签到奖励 44 | :param cookies: 石之家登录后的cookie 45 | :param rewardId: 签到奖励状态内的id 46 | :param rewardMonth: 签到奖励的月份 47 | """ 48 | getRewardUrl = "https://apiff14risingstones.web.sdo.com/api/home/sign/getSignReward?tempsuid=4128eb82-03e6-41d0-b6e9-1d87f5f08d6c" 49 | headers = { 50 | **constant.RS_HEADERS_GET, 51 | 'authorization': daoyu_ticket, 52 | } 53 | payload = { 54 | "id": rewardId, 55 | "month": rewardMonth, 56 | "tempsuid": "6870dd4d-ddfc-4be7-9628-c720e53c1994" 57 | } 58 | get_signid_reward_cookies = { 59 | 'ff14risingstones': cookies 60 | } 61 | with httpx.Client(http2=True) as client: 62 | getReward = client.post(getRewardUrl, headers=headers, data=payload, cookies=get_signid_reward_cookies).json() 63 | if getReward["code"] == 10000: 64 | return { 65 | "status": "success", 66 | "data": getReward["msg"] 67 | } 68 | else: 69 | return { 70 | "status": "failed", 71 | "data": getReward["msg"] 72 | } 73 | 74 | 75 | def getReward(cookies, daoyu_ticket): 76 | """ 77 | 获取签到奖励 78 | :param cookies: 石之家登录后的cookie 79 | :param daoyu_ticket: daoyu_ticket 80 | """ 81 | global status 82 | msg = '' 83 | rewardStatus = getRewardStatus(cookies, daoyu_ticket) 84 | if rewardStatus["status"] == "success": 85 | returnMsg = { 86 | "count": rewardStatus["count"], 87 | "data": [ 88 | { 89 | "description": "[10天]传送网使用券*30", 90 | "status": rewardStatus["data"][0]["is_get"], 91 | "claimStatus": "" 92 | }, 93 | { 94 | "description": "[20天]白银陆行鸟的羽毛X5", 95 | "status": rewardStatus["data"][1]["is_get"], 96 | "claimStatus": "" 97 | }, 98 | { 99 | "description": "[30天]1根黄金陆行鸟的羽毛", 100 | "status": rewardStatus["data"][2]["is_get"], 101 | "claimStatus": "" 102 | } 103 | ] 104 | } 105 | index = 1 106 | today = datetime.now() 107 | for status in rewardStatus["data"]: 108 | reward_name = returnMsg["data"][index - 1]["description"] 109 | if status["is_get"] == 0: 110 | time.sleep(50) 111 | getResult = getSignIDReward(cookies, index, today.strftime("%Y-%m"), daoyu_ticket) 112 | returnMsg["data"][index - 1]["claimStatus"] = str(getResult) 113 | if rs_login.debug: 114 | print(str(getResult)) 115 | claimStatus = f"{reward_name}已经成功领取。 " 116 | msg = msg + claimStatus 117 | else: 118 | claimStatus = f"{reward_name}不满足领取条件或已领取 " 119 | 120 | returnMsg["data"][index - 1]["claimStatus"] = claimStatus 121 | msg = msg + claimStatus 122 | index += 1 123 | else: 124 | if rs_login.debug: 125 | print(rewardStatus["data"]) 126 | if rs_login.debug: 127 | print(msg) 128 | return msg -------------------------------------------------------------------------------- /Deprecated/risingStonesLaunchActivity.py: -------------------------------------------------------------------------------- 1 | import random 2 | from time import sleep 3 | 4 | import requests 5 | 6 | 7 | def getUserTaskInfo(cookies): 8 | """ 9 | 获取石之家盖章任务状态 10 | :param cookies: 石之家cookies 11 | :return: 石之家盖章任务状态 12 | """ 13 | taskInfoUrl = "https://apiff14risingstones.web.sdo.com/api/home/active/online2312/myTaskInfo" 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 | userTaskInfo = requests.get(taskInfoUrl, headers=headers) 19 | seal_total = userTaskInfo.json()["data"]["onceTask"]["seal_total"] 20 | userTaskInfo = userTaskInfo.json()["data"]["dayTask"] 21 | 22 | return { 23 | "seal_total": seal_total, 24 | "sign_status": userTaskInfo["sign_status"], 25 | "sign_seal": userTaskInfo["sign_seal"], 26 | "like_num": userTaskInfo["like_num"], 27 | "like_seal": userTaskInfo["like_seal"], 28 | "comment_status": userTaskInfo["comment_status"], 29 | "comment_seal": userTaskInfo["comment_seal"], 30 | } 31 | 32 | 33 | def finishTask(cookies, taskType): 34 | """ 35 | 完成石之家盖章任务 36 | :param cookies: 石之家cookies 37 | :param taskType: 任务类型 38 | """ 39 | if taskType == "like": 40 | from Utility.risingStones.like import rs_like 41 | for count in range(10): 42 | randomNum = random.randint(1, 863318) 43 | status = rs_like(randomNum, 2, cookies) 44 | sleep(1) 45 | return status 46 | elif taskType == "comment": 47 | from Utility.risingStones.comment import rs_comment 48 | randomNum = random.randint(1, 29) 49 | commentraw = f'

[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. 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 | --------------------------------------------------------------------------------