backup: save local changes on 2025-09-26

This commit is contained in:
2025-09-26 15:30:05 +08:00
parent a5eca7b3f1
commit 68ebf173e3
7874 changed files with 2590582 additions and 9 deletions

1975
dist/main/_internal/Utils/Dy/DyUtils.py vendored Normal file

File diff suppressed because it is too large Load Diff

View File

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,375 @@
# -*- coding: utf-8 -*-
# python let's go
# 编辑人:kris思成
# coding=utf-8
import time
import uuid
import json
# 发送消息
def send_message(pigeon_sign: str, token: str, receiver_id: str, shop_id: str, talk_id: int, session_did: str, p_id: int, user_code: str, text: str):
"""
构造发送消息消息体
:param pigeon_sign: 接口返回
:param token: 接口返回
:param receiver_id: wss消息返回 对方用户id
:param shop_id: cookie自带
:param talk_id: wss消息返回 激活窗口id
:param session_did: cookie自带
:param p_id: wss消息返回
:param user_code: 用户token
:param text: 文本内容
:return:
"""
value = {
'1': 11778,
'2': int(time.time() * 1000),
'3': 10001,
'4': 1,
'5': [
{'1': b'pigeon_source', '2': b'web'},
{'1': b'PIGEON_BIZ_TYPE', '2': b'2'},
{'1': b'pigeon_sign', '2': pigeon_sign.encode()},
],
'7': {'14': 98},
'8': {
'1': 100,
'2': 11778,
'3': b'1.0.4-beta.2',
'4': token.encode(),
'5': 3,
'6': 3,
'7': b'2d97ea6:feat/add_init_callback',
'8': {
'100': {
'1': f"{receiver_id}:{shop_id}::2:1:pigeon".encode(),
'2': 11,
'3': p_id,
'4': text.encode(),
'5': [
{'1': b'type', '2': b'text'},
{'1': b'shop_id', '2': shop_id.encode()},
{'1': b'sender_role', '2': b'2'},
{'1': b'PIGEON_BIZ_TYPE', '2': b'2'},
{'1': b'src', '2': b'pc'},
{'1': b'srcType', '2': b'1'},
{'1': b'source', '2': b'pc-web'},
{'1': b'receiver_id', '2': str(receiver_id).encode()},
{'1': b'hierarchical_dimension', '2': b'{"dynamic_dimension":"4541_1131_9042_6599_9420_6832_4050_3823_3994_8564_1528_0388_8667_2179_7948_1870_1949_0989_8012_6240_7898_7548_8852_6245_9393_3650_8570_4026_4034_4057_6537_8632_2068_8958_0363_2387_9033_3425_2238_0982_1935_8188_3817_8557_7931_3278_4065_1893_6049_6961_3814_4883_4401_6637_7282_3652_9354_0437_4769_4815_9572_7230_5054_3951_4852_2188_3505_6813_2570_5394_0729","goofy_id":"1.0.1.1508","desk_version":"0.0.0","open_stores":"0","memL":"","cpuL":"","session_throughput":0,"message_throughput_send":0,"message_throughput_revice":0}'},
{'1': b'p:check_Send', '2': str(uuid.uuid4()).encode()},
{'1': b'track_info','2': json.dumps({"send_time": int(time.time() * 1000), "_send_delta": "77","_send_delta_2": "216"}).encode()},
{'1': b'user_agent', '2': b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'},
{'1': b'sender_id', '2': b''},
{'1': b'biz_ext', '2': b'{}'},
{'1': b'p:from_source', '2': b'web'},
{'1': b's:mentioned_users', '2': b''},
{'1': b's:client_message_id', '2': str(uuid.uuid4()).encode()}
],
'6': 1000,
'7': user_code.encode(),
'8': str(uuid.uuid4()).encode(),
'14': talk_id # 激活聊天窗口id
}
},
'9': session_did.encode(),
'11': b'web',
'15': [
{'1': b'pigeon_source', '2': b'web'},
{'1': b'PIGEON_BIZ_TYPE', '2': b'2'},
{'1': b'pigeon_sign', '2': b'MIG6BAz2BNUON43WdlOBuGYEgZcsIho9ZjVP4yyExLShzXgAZtsvUMj2e3jZWeMZv+6+TNVQQMq3xSLrqiwcs2cCaOVBDuS6zGsWm5gBlGtlvOOLM5td2/9OS8P37t1sdkjN4BSH2mB7FlGItioZIsTh1sodn6pYCGj+45mtId3Itenufgai3Mnkpt573uoWJmagF8J3jVPHMFtdwd25Qf5vsWC2kB30glpQBBCbk2VO2ubMqctqQSzhI6uD'},
{'1': b'session_aid', '2': b'1383'},
{'1': b'session_did', '2': session_did.encode()},
{'1': b'app_name', '2': b'im'},
{'1': b'priority_region', '2': b'cn'},
{'1': b'user_agent','2': b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'},
{'1': b'cookie_enabled', '2': b'true'},
{'1': b'browser_language', '2': b'zh-CN'},
{'1': b'browser_platform', '2': b'Win32'},
{'1': b'browser_name', '2': b'Mozilla'},
{'1': b'browser_version', '2': b'5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'},
{'1': b'browser_online', '2': b'true'},
{'1': b'screen_width', '2': b'1707'},
{'1': b'screen_height', '2': b'1067'},
{'1': b'referer', '2': b''},
{'1': b'timezone_name', '2': b'Asia/Shanghai'}
],
'18': 2}
}
message_type = {
"1": {
"type": "int",
"name": ""
},
"2": {
"type": "int",
"name": ""
},
"3": {
"type": "int",
"name": ""
},
"4": {
"type": "int",
"name": ""
},
"5": {
"type": "message",
"message_typedef": {
"1": {
"type": "bytes",
"name": ""
},
"2": {
"type": "bytes",
"name": ""
}
},
"name": ""
},
"7": {
"type": "message",
"message_typedef": {
"14": {
"type": "int",
"name": ""
}
},
"name": ""
},
"8": {
"type": "message",
"message_typedef": {
"1": {
"type": "int",
"name": ""
},
"2": {
"type": "int",
"name": ""
},
"3": {
"type": "bytes",
"name": ""
},
"4": {
"type": "bytes",
"name": ""
},
"5": {
"type": "int",
"name": ""
},
"6": {
"type": "int",
"name": ""
},
"7": {
"type": "bytes",
"name": ""
},
"8": {
"type": "message",
"message_typedef": {
"100": {
"type": "message",
"message_typedef": {
"1": {
"type": "bytes",
"name": ""
},
"2": {
"type": "int",
"name": ""
},
"3": {
"type": "int",
"name": ""
},
"4": {
"type": "bytes",
"name": ""
},
"5": {
"type": "message",
"message_typedef": {
"1": {
"type": "bytes",
"name": ""
},
"2": {
"type": "bytes",
"name": ""
}
},
"name": ""
},
"6": {
"type": "int",
"name": ""
},
"7": {
"type": "bytes",
"name": ""
},
"8": {
"type": "bytes",
"name": ""
},
"14": {
"type": "int",
"name": ""
}
},
"name": ""
}
},
"name": ""
},
"9": {
"type": "bytes",
"name": ""
},
"11": {
"type": "bytes",
"name": ""
},
"15": {
"type": "message",
"message_typedef": {
"1": {
"type": "bytes",
"name": ""
},
"2": {
"type": "bytes",
"name": ""
}
},
"name": ""
},
"18": {
"type": "int",
"name": ""
}
},
"name": ""
}
}
return value, message_type
# 获取token
def get_user_code(pigeon_sign: str, token: str, receiver_id: str, shop_id: str, session_did: str, p_id: int):
value = {'1': 10109, '2': int(time.time() * 1000), '3': 10001, '4': 1,
'5': [{'1': b'pigeon_source', '2': b'web'}, {'1': b'PIGEON_BIZ_TYPE', '2': b'2'}, {'1': b'pigeon_sign',
'2': pigeon_sign.encode()}],
'7': {'14': 98}, '8': {'1': 610, '2': 10109, '3': b'1.0.4-beta.2',
'4': token.encode(), '5': 3, '6': 3,
'7': b'2d97ea6:feat/add_init_callback', '8': {
'610': {'1': {'1': f"{receiver_id}:{shop_id}::2:1:pigeon".encode(), '2': p_id, '3': 10}}},
'9': session_did.encode(), '11': b'web',
'15': [{'1': b'pigeon_source', '2': b'web'}, {'1': b'PIGEON_BIZ_TYPE', '2': b'2'},
{'1': b'pigeon_sign',
'2': pigeon_sign.encode()},
{'1': b'session_aid', '2': b'1383'},
{'1': b'session_did', '2': session_did.encode()},
{'1': b'app_name', '2': b'im'}, {'1': b'priority_region', '2': b'cn'},
{'1': b'user_agent',
'2': b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'},
{'1': b'cookie_enabled', '2': b'true'},
{'1': b'browser_language', '2': b'zh-CN'},
{'1': b'browser_platform', '2': b'Win32'},
{'1': b'browser_name', '2': b'Mozilla'}, {'1': b'browser_version',
'2': b'5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'},
{'1': b'browser_online', '2': b'true'}, {'1': b'screen_width', '2': b'1707'},
{'1': b'screen_height', '2': b'1067'}, {'1': b'referer', '2': b''},
{'1': b'timezone_name', '2': b'Asia/Shanghai'}], '18': 2}}
message_type = {'1': {'type': 'int', 'name': ''}, '2': {'type': 'int', 'name': ''},
'3': {'type': 'int', 'name': ''}, '4': {'type': 'int', 'name': ''}, '5': {'type': 'message',
'message_typedef': {
'1': {'type': 'bytes',
'name': ''},
'2': {'type': 'bytes',
'name': ''}},
'name': ''},
'7': {'type': 'message', 'message_typedef': {'14': {'type': 'int', 'name': ''}}, 'name': ''},
'8': {'type': 'message',
'message_typedef': {'1': {'type': 'int', 'name': ''}, '2': {'type': 'int', 'name': ''},
'3': {'type': 'bytes', 'name': ''}, '4': {'type': 'bytes', 'name': ''},
'5': {'type': 'int', 'name': ''}, '6': {'type': 'int', 'name': ''},
'7': {'type': 'bytes', 'name': ''}, '8': {'type': 'message',
'message_typedef': {
'610': {'type': 'message',
'message_typedef': {
'1': {
'type': 'message',
'message_typedef': {
'1': {
'type': 'bytes',
'name': ''},
'2': {
'type': 'int',
'name': ''},
'3': {
'type': 'int',
'name': ''}},
'name': ''}},
'name': ''}},
'name': ''},
'9': {'type': 'bytes', 'name': ''}, '11': {'type': 'bytes', 'name': ''},
'15': {'type': 'message',
'message_typedef': {'1': {'type': 'bytes', 'name': ''},
'2': {'type': 'bytes', 'name': ''}},
'name': ''}, '18': {'type': 'int', 'name': ''}}, 'name': ''}}
return value, message_type
# 心跳包
def heartbeat_message(pigeon_sign: str, token: str, session_did: str):
value = {
'1': 11028,
'2': int(time.time() * 1000),
'3': 10001,
'4': 1,
'5': [
{'1': b'pigeon_source', '2': b'web'},
{'1': b'PIGEON_BIZ_TYPE', '2': b'2'},
{'1': b'pigeon_sign','2': pigeon_sign.encode()}
],
'7': {'14': 98},
'8': {
'1': 200,
'2': 11028,
'3': b'1.0.4-beta.2',
'4': token.encode(),
'5': 3,
'6': 3,
'7': b'2d97ea6:feat/add_init_callback',
'8': {'200': {'1': int(time.time() * 1000), '2': 50}},
'9': session_did.encode(),
'11': b'web',
'15': [
{'1': b'pigeon_source', '2': b'web'},
{'1': b'PIGEON_BIZ_TYPE', '2': b'2'},
{'1': b'pigeon_sign', '2': pigeon_sign.encode()},
{'1': b'session_aid', '2': b'1383'},
{'1': b'session_did', '2': session_did.encode()},
{'1': b'app_name', '2': b'im'},
{'1': b'priority_region', '2': b'cn'},
{'1': b'user_agent', '2': b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'},
{'1': b'cookie_enabled', '2': b'true'},
{'1': b'browser_language', '2': b'zh-CN'},
{'1': b'browser_platform', '2': b'Win32'}, {'1': b'browser_name', '2': b'Mozilla'},
{'1': b'browser_version', '2': b'5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'},
{'1': b'browser_online', '2': b'true'}, {'1': b'screen_width', '2': b'1707'},
{'1': b'screen_height', '2': b'1067'}, {'1': b'referer', '2': b''},
{'1': b'timezone_name', '2': b'Asia/Shanghai'}
],
'18': 2
}
}
message_type = {'1': {'type': 'int', 'name': ''}, '2': {'type': 'int', 'name': ''}, '3': {'type': 'int', 'name': ''}, '4': {'type': 'int', 'name': ''}, '5': {'type': 'message', 'message_typedef': {'1': {'type': 'bytes', 'name': ''}, '2': {'type': 'bytes', 'name': ''}}, 'name': ''}, '7': {'type': 'message', 'message_typedef': {'14': {'type': 'int', 'name': ''}}, 'name': ''}, '8': {'type': 'message', 'message_typedef': {'1': {'type': 'int', 'name': ''}, '2': {'type': 'int', 'name': ''}, '3': {'type': 'bytes', 'name': ''}, '4': {'type': 'bytes', 'name': ''}, '5': {'type': 'int', 'name': ''}, '6': {'type': 'int', 'name': ''}, '7': {'type': 'bytes', 'name': ''}, '8': {'type': 'message', 'message_typedef': {'200': {'type': 'message', 'message_typedef': {'1': {'type': 'int', 'name': ''}, '2': {'type': 'int', 'name': ''}}, 'name': ''}}, 'name': ''}, '9': {'type': 'bytes', 'name': ''}, '11': {'type': 'bytes', 'name': ''}, '15': {'type': 'message', 'message_typedef': {'1': {'type': 'bytes', 'name': ''}, '2': {'type': 'bytes', 'name': ''}}, 'name': ''}, '18': {'type': 'int', 'name': ''}}, 'name': ''}}
return value, message_type

981
dist/main/_internal/Utils/JD/JdUtils.py vendored Normal file
View File

@@ -0,0 +1,981 @@
# -*- coding: utf-8 -*-
# python let's go
# 编辑人:kris思成
import asyncio
import hashlib
import traceback
from datetime import datetime
import aiohttp
import jsonpath
import websockets
from loguru import logger
import requests
import json
import time
import threading
# 定义持久化数据类
class WebsocketManager:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._store = {}
cls._instance._lock = threading.RLock()
return cls._instance
def on_connect(self, shop_key, ws, **kwargs):
"""完全保持原有数据结构"""
with self._lock:
entry = self._store.setdefault(shop_key, {
'platform': None,
'customers': [],
'user_assignments': {}
})
entry['platform'] = {
'ws': ws, # 注意:这里存储的是强引用
'last_heartbeat': datetime.now(),
**kwargs
}
return entry
def get_connection(self, shop_key):
with self._lock:
return self._store.get(shop_key)
def remove_connection(self, shop_key):
with self._lock:
if shop_key in self._store:
del self._store[shop_key]
class JDBackendService:
"""京东后端服务调用类(已废弃:使用单后端连接 BackendClient 代替)"""
def __init__(self, *args, **kwargs):
self.current_store_id = ""
async def connect(self, store_id, *args, **kwargs):
try:
self.current_store_id = str(store_id or "")
except Exception:
self.current_store_id = ""
return True
async def send_message_to_backend(self, platform_message):
"""改为通过单后端连接发送需携带store_id"""
try:
from WebSocket.backend_singleton import get_backend_client
backend = get_backend_client()
if not backend:
return None
# 从platform_message中构造统一上行结构并附加store_id
body = platform_message.get('body', {}) if isinstance(platform_message, dict) else {}
sender_pin = platform_message.get('from', {}).get('pin', '') if isinstance(platform_message, dict) else ''
# 优先取消息内的store_id其次取body内再次退回当前会话store_id
store_id = (platform_message.get('store_id')
or body.get('store_id')
or self.current_store_id
or '')
body_type = body.get('type', 'text')
content_for_backend = body.get('content', '')
if body_type in ('image', 'video'):
# 统一从常见字段取URL
content_for_backend = (
body.get('url')
or body.get('imageUrl')
or body.get('videoUrl')
or body.get('picUrl')
or content_for_backend
)
# 检测文本中的商品卡片/订单卡片
if body_type == 'text' and isinstance(content_for_backend, str):
try:
import re as _re
text = content_for_backend.strip()
# 商品卡片JD商品URL
if _re.search(r"https?://item(\.m)?\.jd\.com/\d+\.html(\?.*)?", text):
body_type = 'product_card'
else:
# 订单卡片多样式匹配
# 1) 订单在前:咨询/查询/订单号:<order>[,]? 商品(ID|id|号|编号)<product>
m1 = _re.search(
r"(?:咨询订单号|查询订单号|订单号)\s*[:]\s*(\d+)[,]?\s*商品(?:ID|id|号|编号)\s*[:]\s*(\d+)",
text)
# 2) 商品在前:商品(ID|id|号|编号)<product>[,]? (咨询/查询)?订单号:<order>
m2 = _re.search(
r"商品(?:ID|id|号|编号)\s*[:]\s*(\d+)[,]?\s*(?:咨询订单号|查询订单号|订单号)\s*[:]\s*(\d+)",
text)
if m1 or m2:
body_type = 'order_card'
if m1:
order_number, product_id = m1.group(1), m1.group(2)
else:
product_id, order_number = m2.group(1), m2.group(2)
# 归一化 content
content_for_backend = f"商品id{product_id} 订单号:{order_number}"
except Exception:
pass
msg = {
'type': 'message',
'content': content_for_backend,
'msg_type': body_type,
'sender': {'id': sender_pin},
'store_id': store_id
}
backend.send_message(msg)
return None
except Exception:
return None
class FixJdCookie:
def __init__(self, log_callback=None):
# 定义一些常用参数
super().__init__() # 继承一些父类的初始化参数
self.ws_manager = WebsocketManager()
self.log_callback = log_callback # 存储日志回调
# 新增后端服务实例
self.backend_service = JDBackendService()
self.backend_connected = False
# 新增重连参数
self.reconnect_attempts = 0
self.max_reconnect_attempts = 10 # 最大重连次数
self.base_reconnect_delay = 1.0 # 基础重连延迟
self.max_reconnect_delay = 60.0 # 最大重连延迟
self.reconnect_backoff = 1.5 # 退避系数
def _log(self, message, log_type="INFO"):
"""内部日志方法"""
if self.log_callback:
self.log_callback(message, log_type)
else:
print(f"[{log_type}] {message}")
def get_config(self, cookies_str):
"""获取配置"""
headers = {
"cookie": cookies_str,
"user-agent": "Mozilla/5.0",
"referer": "https://dongdong.jd.com/",
}
response = requests.get("https://dongdong.jd.com/workbench/checkin.json", headers=headers,
params={"version": "2.6.3", "client": "openweb"})
return response.json()
async def init_wss(self, ws, aid, pin_zj):
"""初始化 socket"""
await self.send_heartbeat(ws, aid, pin_zj)
print("开始监听初始化")
auth = {
"id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(),
"aid": aid,
"lang": "zh_CN",
"timestamp": int(time.time() * 1000),
"type": "auth",
"body": {"presence": 1, "clientVersion": "2.6.3"},
"to": {"app": "im.waiter"},
"from": {"app": "im.waiter", "pin": pin_zj, "clientType": "comet", "dvc": "device1234"}
}
await ws.send(json.dumps(auth))
async def waiter_status_switch(self, ws, aid, pin_zj):
"""设置接待状态"""
message = {
"id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(),
"aid": aid,
"lang": "zh_CN",
"timestamp": int(time.time() * 1000),
"from": {
"app": "im.waiter",
"pin": pin_zj,
"art": "customerGroupMsg",
"clientType": "comet"
},
"type": "waiter_status_switch",
"body": {
"s": 1
}
}
await ws.send(json.dumps(message))
async def transfer_customer(self, ws, aid, pin, pin_zj, chat_name):
"""异步客服转接 在发送的消息为客服转接的关键词的时候"""
message = {
"id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(),
"aid": aid,
"lang": "zh_CN",
"timestamp": int(time.time() * 1000),
"from": {
"app": "im.waiter", "pin": pin_zj, "art": "customerGroupMsg", "clientType": "comet"
},
"type": "chat_transfer_partern",
"body": {
"customer": pin, "cappId": "im.customer", "waiter": chat_name, "reason": "",
"ext": {"pid": ""}, "pid": ""
}
}
try:
await ws.send(json.dumps(message))
return True
except Exception:
traceback.print_exc()
return False
async def send_message(self, ws, pin, aid, pin_zj, vender_id, content):
"""异步发送单条消息"""
try:
print('本地发送消息')
message = {
"ver": "4.3",
"type": "chat_message",
"from": {"pin": pin_zj, "app": "im.waiter", "clientType": "comet"},
"to": {"app": "im.customer", "pin": pin},
"id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(),
"lang": "zh_CN",
"aid": aid,
"timestamp": int(time.time() * 1000),
"readFlag": 0,
"body": {
"content": content,
"translated": False,
"param": {"cusVenderId": vender_id},
"type": "text"
}
}
await ws.send(json.dumps(message))
logger.info(f"消息已经发送到客户端[info] {pin}: {content[:20]} ...")
except websockets.ConnectionClosed:
logger.error('本地发送消息失败 连接关闭')
raise
except Exception as e:
# 同时这里也要及时进行raise抛出 这样比较好让系统可以看出 异常了可以抛出信息不至于后续被认为
logger.error(f"消息发送过程中出现特殊异常异常信息为: {e}")
raise
def get_userinfo(self, response_text):
"""获取用户 pin 并存入 pins 列表"""
pin = jsonpath.jsonpath(json.loads(response_text), "$..from.pin")
return pin[0] if pin else None
def is_merchants(self, store: object, response):
"""判断消息是否来自顾客"""
send = response['from']['pin']
# 补充: 方法lower()是转化为小写 upper()是转化为大写 title()是每个单词首字母大写其余小写
if send.lower() == "KLD测试".lower():
return False
return True
async def send_heartbeat(self, ws, aid, pin_zj):
"""发送心跳包"""
msg = {
"id": hashlib.md5(str(int(time.time() * 1000)).encode()).hexdigest(),
"type": "client_heartbeat",
"aid": aid,
"ver": "4.1",
"lang": "zh_CN",
"from": {"pin": pin_zj, "app": "im.waiter", "art": "customerGroupMsg", "clientType": "comet"}
}
await ws.send(json.dumps(msg, separators=(',', ':'))) # 使用最简心跳包模式 来节约部分性能 减少传输压力
async def heartbeat_loop(self, websocket, aid, pin_zj):
"""独立的心跳循环"""
"""
优化心跳 循环 新增改进
1. 心跳间隔动态调整
2. 异常重试机制
3. 心跳超时控制
4. 状态监控
"""
retry_count = 0
max_retries = 3
base_interval = 3.0 # 基础间隔1s
backoff_factor = 1.5 # 退避系数
timeout = 10.0
while True:
try:
start_time = time.monotonic()
# 使用websocket原生的ping/pong机制
pong_waiter = await websocket.ping()
await asyncio.wait_for(pong_waiter, timeout=timeout)
# 如果需要发送自定义心跳包
await self.send_heartbeat(websocket, aid, pin_zj)
# 计算剩余等待时间
elapsed = time.monotonic() - start_time
sleep_time = max(0.0, base_interval - elapsed)
if sleep_time > 0:
await asyncio.sleep(sleep_time)
retry_count = 0 # 重置重试计数
except asyncio.TimeoutError:
logger.warning(f'心跳超时,已用时: {time.monotonic() - start_time:.2f}')
retry_count += 1
except websockets.ConnectionClosed:
logger.error("连接关闭,心跳已停止")
break
except Exception as e:
logger.error(f"心跳异常: {e}", exc_info=True)
retry_count += 1
if retry_count >= max_retries:
logger.error(f"心跳连续失败{retry_count}次,终止循环")
break
# 退避策略
if retry_count > 0:
backoff_time = min(
base_interval * (backoff_factor ** (retry_count - 1)),
60.0 # 最大等待60秒
)
logger.debug(f"心跳失败,等待{backoff_time:.2f}秒后重试")
await asyncio.sleep(backoff_time)
''' 整理重连方法 '''
async def calculate_reconnect_delay(self):
"""计算指数退避的重连延迟时间"""
delay = self.base_reconnect_delay * (self.reconnect_backoff ** self.reconnect_attempts)
return min(delay, self.max_reconnect_delay)
async def should_reconnect(self):
"""判断是否应该继续重连"""
if self.reconnect_attempts >= self.max_reconnect_attempts:
self._log(f"已达到最大重连次数({self.max_reconnect_attempts}),停止重连", "ERROR")
return False
return True
async def handle_reconnect(self, exception=None):
"""处理重连逻辑"""
if exception:
error_type = type(exception).__name__
error_msg = str(exception)
self._log(f"连接异常[{error_type}]: {error_msg}", "WARNING")
if not await self.should_reconnect():
return False
delay = await self.calculate_reconnect_delay()
self._log(f"{self.reconnect_attempts + 1}次重连尝试,等待{delay:.1f}秒...", "WARNING")
await asyncio.sleep(delay)
self.reconnect_attempts += 1
return True
''' 后端服务调用方法 '''
async def connect_backend_service(self, store_id):
"""连接后端AI服务"""
try:
success = await self.backend_service.connect(store_id)
if success:
self.backend_connected = True
self._log("✅ 后端AI服务连接成功", "SUCCESS")
return success
except Exception as e:
self._log(f"❌ 连接后端AI服务失败: {e}", "ERROR")
return False
async def get_ai_reply_from_backend(self, platform_message):
"""从后端服务获取AI回复"""
# 首先检查后端服务连接状态
if not self.backend_connected:
# 尝试重新连接
store_id = str(platform_message.get('body', {}).get('chatinfo', {}).get('venderId', ''))
if store_id:
self.backend_connected = await self.connect_backend_service(store_id)
if not self.backend_connected:
self._log("❌ 后端服务未连接尝试重新连接失败使用本地AI回复", "WARNING")
# 降级到本地AI回复
customer_sid = platform_message.get('body', {}).get('chatinfo', {}).get('sid', '')
customer_message = platform_message.get('body', {}).get('content', '')
return
try:
# 推送给后端由后端异步回传AI消息到GUI此处不进行本地立即回复
await self.backend_service.send_message_to_backend(platform_message)
return None
except Exception as e:
self._log(f"❌ 获取AI回复失败: {e}使用本地AI", "ERROR")
customer_sid = platform_message.get('body', {}).get('chatinfo', {}).get('sid', '')
customer_message = platform_message.get('body', {}).get('content', '')
return
async def process_incoming_message(self, response, ws, aid, pin_zj, vender_id, store):
"""处理接收到的消息"""
try:
# 解析消息
json_resp = json.loads(response) if isinstance(response, (str, bytes)) else response
# 验证消息格式
if not all([
json_resp,
json_resp.get('type') == "chat_message",
json_resp.get('ver') == "4.2",
isinstance(json_resp.get("body"), dict)
]):
return
# 过滤非客户消息
pin = self.get_userinfo(response)
if not self.is_merchants(store, json_resp):
return
# 提取消息内容
message_type = json_resp['body']['type']
if message_type == 'text':
customer_message = json_resp['body']['content']
elif message_type == 'image':
customer_message = f"[图片] {json_resp['body'].get('url') or json_resp['body'].get('imageUrl') or json_resp['body'].get('picUrl') or ''}"
elif message_type == 'video':
customer_message = f"[视频] {json_resp['body'].get('url') or json_resp['body'].get('videoUrl') or ''}"
else:
return
self._log(f"📩 收到客户消息: {pin}: {customer_message[:100]}...", "INFO")
# 从后端服务获取AI回复单连接模式仅转交不本地立即回复
ai_result = await self.get_ai_reply_from_backend(json_resp)
if isinstance(ai_result, str) and ai_result.strip():
# 发送回复消息(仅当本地生成/降级时)
await self.send_message(
ws=ws, pin=pin, aid=aid,
pin_zj=pin_zj, vender_id=vender_id,
content=ai_result
)
self._log(f"📤 已发送回复: {ai_result[:100]}...", "INFO")
else:
# 正常链路已转交后端AI等待后端异步回传并由 GUI 转发到平台
self._log("🔄 已转交后端AI处理等待平台回复下发", "INFO")
except json.JSONDecodeError:
self._log("❌ 消息JSON解析失败", "ERROR")
except Exception as e:
self._log(f"❌ 消息处理失败: {e}", "ERROR")
async def message_monitoring(self, cookies_str, aid, pin_zj, vender_id, store, sleep_time=0.5, stop_event=None):
print("✅ DEBUG 进入message_monitoring方法")
print(f"参数验证: cookies={bool(cookies_str)}, aid={aid}, pin_zj={pin_zj}")
# 连接后端AI服务 - 使用店铺ID或venderId
store_id = str(store.get('id', '')) or str(vender_id)
self._log(f"🔗 尝试连接后端服务店铺ID: {store_id}", "DEBUG")
backend_connected = await self.connect_backend_service(store_id)
if not backend_connected:
self._log("⚠️ 后端服务连接失败将使用本地AI回复", "WARNING")
else:
self._log("✅ 后端服务连接成功", "SUCCESS")
stop_event = stop_event or asyncio.Event() # 如果外部没传,自己创建
uri = "wss://dongdong.jd.com/workbench/websocket"
headers = {"cookie": cookies_str}
self._log(f"🔵 开始连接WebSocket: {uri}", "INFO")
# 充值重连计数器
self.reconnect_attempts = 0
while not stop_event.is_set():
try:
self._log(f"🔄 尝试连接WebSocket", "INFO")
async with websockets.connect(uri, additional_headers=headers, ping_interval=6, ping_timeout=10, close_timeout=1, max_queue=1024) as ws:
# 连接成功,重置重连计数器
self.reconnect_attempts = 0
self._log("✅ WebSocket-JD连接成功", "SUCCESS")
await self.init_wss(ws, aid, pin_zj)
# === 验证连接成功的核心指标 ===
# print(f"✅ 连接状态: open={ws.open}, closed={ws.closed}")
print(f"🖥️ 服务端地址: {ws.remote_address}")
# --- 注册连接信息到全局管理
shop_key = f"京东:{store['id']}"
loop = asyncio.get_running_loop()
entry = self.ws_manager.on_connect(
shop_key=shop_key,
ws=ws,
vender_id=vender_id,
aid=aid,
pin_zj=pin_zj,
platform="京东",
loop=loop
)
await self.waiter_status_switch(ws=ws, aid=aid, pin_zj=pin_zj)
heartbeat_task = asyncio.create_task(self.heartbeat_loop(ws, aid, pin_zj))
message_tasks = set()
try:
while not stop_event.is_set(): # 检查是否收到中止信号
try:
print(f"等待监听消息-{datetime.now()}")
response = await asyncio.wait_for(ws.recv(), timeout=1)
print(f"原始消息类型:{type(response)}, 消息体为: {response}")
await self.process_incoming_message(response, ws, aid, pin_zj, vender_id, store)
# 安全解析消息
json_resp = json.loads(response) if isinstance(response, (str, bytes)) else response
print(json_resp)
ver = json_resp.get("ver")
print(f"版本{ver}")
except asyncio.TimeoutError:
continue
except websockets.ConnectionClosed:
# 连接关闭 跳出内层循环进行重连
break
except Exception as e:
self._log(f"消息处理异常: {e}", "ERROR")
continue
await asyncio.sleep(sleep_time)
###
##{'ver': '4.2', 'mid': 366566937, 'body': {'chatinfo': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10143502227300', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'label': 1, 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': 'ce7f35b51a9c00ac898b0fe08608674a', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd01C5XRGQHGNJBSFYWWA7RF54QXYHF2N34TM32NGLZV6YAAPPNKWLAIQ2MZV25T4QZ2TJE4HRF6UZ2L3THX7I2BLLB37YVAWKR2BYGRRPA01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'param': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10143502227300', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'label': 1, 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': 'ce7f35b51a9c00ac898b0fe08608674a', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd01C5XRGQHGNJBSFYWWA7RF54QXYHF2N34TM32NGLZV6YAAPPNKWLAIQ2MZV25T4QZ2TJE4HRF6UZ2L3THX7I2BLLB37YVAWKR2BYGRRPA01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'type': 'text', 'requestData': {'entry': 'sdk_item', 'venderId': '11961298'}, 'content': '你好', 'sid': 'ce7f35b51a9c00ac898b0fe08608674a'}, 'type': 'chat_message', 'clientTime': 1755076542320, 'datetime': '2025-08-13 17:15:42', 'len': 0, 'from': {'app': 'im.customer', 'art': '', 'clientType': 'android', 'pin': 'jd_thpotntctwys'}, 'subType': 'text', 'id': 'bc3c42a706fa4fa482ff565304c7dfbb', 'to': {'app': 'im.waiter', 'clientType': 'comet', 'pin': 'KLD测试'}, 'lang': 'zh_CN', 'timestamp': 1755076542671}
# {'ver': '4.2', 'mid': 366566966, 'body': {'chatinfo': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10052172306055', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': '538fc3761474ea693812ceed4b39639c', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd014TJZZMUXFLXYGJJJ4M3HY3PTU4PBA22SPDIXDWOGZ7XTUTOWTY4LU3VWN6OKDOJPJOVDINMCJXCSPSG5X2K6KWHNQJQOROB57LDG5EY01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'param': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10052172306055', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': '538fc3761474ea693812ceed4b39639c', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd014TJZZMUXFLXYGJJJ4M3HY3PTU4PBA22SPDIXDWOGZ7XTUTOWTY4LU3VWN6OKDOJPJOVDINMCJXCSPSG5X2K6KWHNQJQOROB57LDG5EY01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'type': 'text', 'requestData': {'entry': 'sdk_item', 'venderId': '11961298'}, 'content': '你好啊', 'sid': '538fc3761474ea693812ceed4b39639c'}, 'type': 'chat_message', 'clientTime': 1755592905140, 'datetime': '2025-08-19 16:41:45', 'len': 0, 'from': {'app': 'im.customer', 'art': '', 'clientType': 'android', 'pin': 'jd_thpotntctwys'}, 'subType': 'text', 'id': 'd31d369f17f24b05abbe2b7c334f340e', 'to': {'app': 'im.waiter', 'clientType': 'comet', 'pin': 'KLD测试'}, 'lang': 'zh_CN', 'timestamp': 1755592905232}
# {'ver': '4.2', 'mid': 366566984, 'body': {'chatinfo': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10143502227300', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'label': 1, 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': '5b5e044c73ee243b7e67eeca5b11393c', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd01ZC7NRD2EX45UN4Q5IZUQC4VLKXIKR7LWP2HC45VQRBDXEYHACT6U5KFBIAI52JBHYCO5BLMOHXTYUYU35RQPB2XA37HL4MPP7CXET7Q01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'param': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10143502227300', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'label': 1, 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': '5b5e044c73ee243b7e67eeca5b11393c', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd01ZC7NRD2EX45UN4Q5IZUQC4VLKXIKR7LWP2HC45VQRBDXEYHACT6U5KFBIAI52JBHYCO5BLMOHXTYUYU35RQPB2XA37HL4MPP7CXET7Q01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'type': 'text', 'requestData': {'entry': 'sdk_item', 'venderId': '11961298'}, 'content': '您好', 'sid': '5b5e044c73ee243b7e67eeca5b11393c'}, 'type': 'chat_message', 'clientTime': 1755595443735, 'datetime': '2025-08-19 17:24:03', 'len': 0, 'from': {'app': 'im.customer', 'art': '', 'clientType': 'android', 'pin': 'jd_thpotntctwys'}, 'subType': 'text', 'id': '800deaf802f9436b98937bb084ee2a56', 'to': {'app': 'im.waiter', 'clientType': 'comet', 'pin': 'KLD测试'}, 'lang': 'zh_CN', 'timestamp': 1755595443839}
# {'ver': '4.2', 'mid': 366566986, 'body': {'chatinfo': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10143502227300', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'label': 1, 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': '5b5e044c73ee243b7e67eeca5b11393c', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd01ZC7NRD2EX45UN4Q5IZUQC4VLKXIKR7LWP2HC45VQRBDXEYHACT6U5KFBIAI52JBHYCO5BLMOHXTYUYU35RQPB2XA37HL4MPP7CXET7Q01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'param': {'venderId': '11961298', 'isJdSuperMarket': '0', 'pid': '10143502227300', 'source': 'jimitwo_service_smart_sdk', 'deviceNo': 'dd_dvc_aes_30EA91E824A2F36365F7B7193C10B76A8842E4E08C0DA687EC9BEB307FCF7195', 'label': 1, 'IMService': False, 'distinguishPersonJimi': 2, 'proVer': 'smart_android_15.2.0', 'sid': '5b5e044c73ee243b7e67eeca5b11393c', 'entry': 'sdk_item', 'leaveMsgTable': 1, 'venderName': 'TYBOY康路达数字科技专卖店', 'disputeId': -1, 'ddSessionType': '1', 'appId': 'im.waiter', 'systemVer': 'android_13_V2239A', 'eidtoken': 'jdd01ZC7NRD2EX45UN4Q5IZUQC4VLKXIKR7LWP2HC45VQRBDXEYHACT6U5KFBIAI52JBHYCO5BLMOHXTYUYU35RQPB2XA37HL4MPP7CXET7Q01234567', 'auctionType': '0', 'region': 'CN', 'verification': 'slide'}, 'type': 'text', 'requestData': {'entry': 'sdk_item', 'venderId': '11961298'}, 'content': '我先自己看看谢谢您', 'sid': '5b5e044c73ee243b7e67eeca5b11393c'}, 'type': 'chat_message', 'clientTime': 1755595460153, 'datetime': '2025-08-19 17:24:20', 'len': 0, 'from': {'app': 'im.customer', 'art': '', 'clientType': 'android', 'pin': 'jd_thpotntctwys'}, 'subType': 'text', 'id': 'a3d4de984f2d4811952f8ba872850bc2', 'to': {'app': 'im.waiter', 'clientType': 'comet', 'pin': 'KLD测试'}, 'lang': 'zh_CN', 'timestamp': 1755595460274}
except Exception as e:
logger.error(f"接收对应监控消息时候产生特殊错误 , 错误信息为{e}")
finally:
# 清理资源
heartbeat_task.cancel()
try:
await heartbeat_task
except asyncio.CancelledError:
pass
self._log("🔄 连接断开,准备重连", "INFO")
except websockets.ConnectionClosed as e:
self._log(f"🔌 连接已关闭: {e.code} {e.reason}", "WARNING")
if not await self.handle_reconnect(e):
break
except (websockets.WebSocketException, aiohttp.ClientError, OSError) as e:
self._log(f"🌐 网络异常: {type(e).__name__} - {str(e)}", "WARNING")
if not await self.handle_reconnect(e):
break
except Exception as e:
self._log(f"⚠️ 未知异常: {type(e).__name__} - {str(e)}", "ERROR")
if not await self.handle_reconnect(e):
break
# 关闭后端服务连接
if self.backend_connected:
await self.backend_service.close()
self.backend_connected = False
self._log("🛑 消息监听已停止", "INFO")
# 新增: GUI集成包装器类
# class JDListenerForGUI:
# """用于GUI集成的JD监听包装器 ()"""
#
# def __init__(self, log_callback=None):
# self.fix_jd_util = FixJdCookie(log_callback)
# self.log_callback = log_callback
# self.running = False
# self.stop_event = None
# self.username = None
# self.password = None
#
# def _log(self, message, log_type="INFO"):
# """处理日志输出"""
# if self.log_callback:
# self.log_callback(message, log_type)
# else:
# print(f"[{log_type}] {message}")
#
# async def start_listening(self, username, password):
# """启动监听的主方法"""
# try:
# # 存储用户名和密码
# self.username = username
# self.password = password
#
# self._log("🔵 开始JD平台连接流程", "INFO")
# print("🔵 开始JD平台连接流程 - 调试断点1")
#
# # 1. 获取店铺信息
# self._log("🔵 步骤2: 获取店铺信息...", "INFO")
# print("🔵 步骤2: 获取店铺信息... - 调试断点2")
# store_today = self.fix_jd_util.get_today_store()
# if not store_today:
# self._log("❌ 未找到店铺信息", "ERROR")
# print("❌ 未找到店铺信息")
# return False
# self._log(f"✅ 获取到店铺信息: {store_today.get("id", '未知')}", "SUCCESS")
# print(f"✅ 获取到店铺信息: {store_today.get("id", '未知')}")
#
# # 2. 连接后端服务获取cookie
# self._log("🔵 步骤3: 连接后端服务获取cookie...", "INFO")
# store_id = str(store_today.get('id', ''))
#
# jd_cookie = await self.fix_jd_util.connect_backend_service(store_id, username, password)
#
# if not jd_cookie or not jd_cookie.get('status'):
# self._log("❌ 从后端服务获取cookie失败", "ERROR")
# if jd_cookie and jd_cookie.get('verify_link'):
# self._log(f"❌ 需要验证登录,验证链接: {jd_cookie.get('verify_link')}", "ERROR")
# return False
#
# self._log("✅ 从后端服务获取cookie成功", "SUCCESS")
# cookies_str = jd_cookie.get('cookie')
# self._log(f"📦 获取到cookie: {cookies_str[:50] + '...' if cookies_str else '无'}", "DEBUG")
#
# # 3. 获取配置信息
# self._log("🔵 步骤4: 获取配置信息...", "INFO")
# config = None
# for i in range(3):
# try:
# config = self.fix_jd_util.get_config(cookies_str)
# if config and config.get('data'):
# self._log(f"✅ 第{i + 1}次尝试获取配置成功", "SUCCESS")
# break
# else:
# self._log(f"⚠️ 第{i + 1}次尝试获取配置返回空数据", "WARNING")
# except Exception as e:
# self._log(f"获取配置异常({i + 1}/3): {str(e)}", "WARNING")
# await asyncio.sleep(3)
#
# if not config or not config.get('data'):
# self._log("获取配置失败", "ERROR")
# return False
#
# # 4. 提取必要参数
# self._log("🔵 步骤5: 提取配置参数...", "INFO")
# aid = config["data"].get("aid")
# vender_id = config["data"].get("venderId")
# pin_zj = config["data"].get("pin", "").lower()
#
# if not all([aid, vender_id, pin_zj]):
# self._log("❌ 登录信息不完整", "ERROR")
# return False
#
# self._log(f"获取到配置: aid={aid}, vender_id={vender_id}, pin_zj={pin_zj}", "INFO")
#
# # 5. 启动监听
# self._log("🔵 步骤6: 启动消息监听...", "INFO")
# self.stop_event = asyncio.Event()
# self.running = True
#
# self._log("🎉开始监听JD平台消息...", "SUCCESS")
#
# # 调用实际的监听方法
# await self.fix_jd_util.message_monitoring(
# cookies_str=cookies_str,
# aid=aid,
# pin_zj=pin_zj,
# vender_id=vender_id,
# store=store_today,
# stop_event=self.stop_event,
# username=username,
# password=password
# )
#
# return True
#
# except Exception as e:
# self._log(f"监听过程中出现严重错误: {str(e)}", "ERROR")
# import traceback
# self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
# return False
#
# def stop_listening(self):
# """停止监听"""
# if self.stop_event:
# self.stop_event.set()
# self.running = False
# self._log("JD监听已停止", "INFO")
# 新增: GUI集成包装器类
class JDListenerForGUI:
"""用于GUI集成的JD监听包装器 ()"""
def __init__(self, log_callback=None):
self.fix_jd_util = FixJdCookie(log_callback)
self.log_callback = log_callback
self.running = False
self.stop_event = None
def _log(self, message, log_type="INFO"):
"""处理日志输出"""
if self.log_callback:
self.log_callback(message, log_type)
else:
print(f"[{log_type}] {message}")
async def start_listening(self, username, password):
"""启动监听的主方法"""
try:
self._log("🔵 开始JD平台连接流程", "INFO")
print("🔵 开始JD平台连接流程 - 调试断点1")
# 1. 获取店铺信息
self._log("🔵 步骤2: 获取店铺信息...", "INFO")
print("🔵 步骤2: 获取店铺信息... - 调试断点2")
store_today = self.fix_jd_util.get_today_store()
if not store_today:
self._log("❌ 未找到店铺信息", "ERROR")
print("❌ 未找到店铺信息")
return False
self._log(f"✅ 获取到店铺信息: {store_today.get("id", '未知')}", "SUCCESS")
print(f"✅ 获取到店铺信息: {store_today.get("id", '未知')}")
cookie_str = store_today.get('platform_cookie')
self._log(f"📦 当前存储的cookie: {cookie_str[:50] + '...' if cookie_str else ''}", "DEBUG")
# 2. 获取或更新cookie - 在这里设置断点②
self._log("🔵 步骤3: 获取或更新JD cookie...", "INFO")
# 2. 获取或更新cookie
jd_login_cookie = self.fix_jd_util.get_cookies(
username=username,
password=password,
cookies_str=cookie_str
)
if not jd_login_cookie['status']:
fail_status = jd_login_cookie.get('verify_link', None)
if fail_status:
self._log(f"❌ JD登录失败: , 失败类型为二次验证(手机验证码) 对应url:{fail_status}", "ERROR")
else:
# 表示没有返回verify_url 未知错误
self._log(f"❌ JD登录失败: , 失败类型为:{fail_status} 未知错误 请单独测试login方法")
return False
self._log("✅ JD登录成功", "SUCCESS")
self._log(f"📦 获取到cookie: {jd_login_cookie['cookie'][:50] + '...'}", "DEBUG")
self._log("🔵 步骤4: 获取配置信息...", "INFO")
# 3. 获取配置信息
config = None
for i in range(3):
try:
config = self.fix_jd_util.get_config(jd_login_cookie['cookie'])
if config and config.get('data'):
self._log(f"✅ 第{i + 1}次尝试获取配置成功", "SUCCESS")
break
else:
self._log(f"⚠️ 第{i + 1}次尝试获取配置返回空数据", "WARNING")
except Exception as e:
self._log(f"获取配置异常({i + 1}/3): {str(e)}", "WARNING")
await asyncio.sleep(3)
if not config or not config.get('data'):
self._log("获取配置失败", "ERROR")
return False
# 4. 提取必要参数
self._log("🔵 步骤5: 提取配置参数...", "INFO")
aid = config["data"].get("aid")
vender_id = config["data"].get("venderId")
pin_zj = config["data"].get("pin", "").lower()
if not all([aid, vender_id, pin_zj]):
self._log("❌ 登录信息不完整", "ERROR")
return False
self._log(f"获取到配置: aid={aid}, vender_id={vender_id}, pin_zj={pin_zj}", "INFO")
# 5. 启动监听
self._log("🔵 步骤6: 启动消息监听...", "INFO")
self.stop_event = asyncio.Event()
self.running = True
self._log("🎉开始监听JD平台消息...", "SUCCESS")
# 调用实际的监听方法
await self.fix_jd_util.message_monitoring(
cookies_str=jd_login_cookie['cookie'],
aid=aid,
pin_zj=pin_zj,
vender_id=vender_id,
store=store_today,
stop_event=self.stop_event
)
return True
except Exception as e:
self._log(f"监听过程中出现严重错误: {str(e)}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
return False
async def start_with_cookies(self, store_id: str, cookies: str):
"""使用下发的cookies与store_id直接建立JD平台WS并开始监听"""
try:
self._log("🔵 [JD] 收到后端登录指令开始使用cookies连接平台", "INFO")
# 获取平台配置
config = None
for i in range(3):
try:
config = self.fix_jd_util.get_config(cookies)
if config and config.get('data'):
self._log(f"✅ 第{i + 1}次尝试获取配置成功", "SUCCESS")
break
else:
self._log(f"⚠️ 第{i + 1}次尝试获取配置返回空数据", "WARNING")
except Exception as e:
self._log(f"获取配置异常({i + 1}/3): {str(e)}", "WARNING")
await asyncio.sleep(3)
if not config or not config.get('data'):
self._log("获取配置失败", "ERROR")
return False
aid = config["data"].get("aid")
vender_id = config["data"].get("venderId")
pin_zj = config["data"].get("pin", "").lower()
if not all([aid, vender_id, pin_zj]):
self._log("❌ 登录信息不完整", "ERROR")
return False
# 建立与后端的AI通道确保使用GUI的store_id
await self.fix_jd_util.connect_backend_service(store_id)
# 启动监听
self.stop_event = asyncio.Event()
self.running = True
store = {'id': store_id}
self._log("🎉 [JD] 开始监听平台消息", "SUCCESS")
await self.fix_jd_util.message_monitoring(
cookies_str=cookies,
aid=aid,
pin_zj=pin_zj,
vender_id=vender_id,
store=store,
stop_event=self.stop_event
)
return True
except Exception as e:
self._log(f"[JD] 监听过程中出现错误: {str(e)}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
return False
def stop_listening(self):
"""停止监听"""
if self.stop_event:
self.stop_event.set()
self.running = False
self._log("JD监听已停止", "INFO")
async def main():
username = "KLD测试"
password = "kld168168"
fix_jd_util = FixJdCookie()
store_today = fix_jd_util.get_today_store()
# 检查店铺信息
if not store_today:
logger.error("❌ 未找到店铺信息")
return
store_id = str(store_today.get('id', ''))
logger.info(f"✅ 获取到店铺信息: {store_id}")
try:
# 1. 直接连接后端服务获取 cookie
logger.info("🔵 步骤1: 连接后端服务获取 cookie...")
jd_cookie = await fix_jd_util.connect_backend_service(store_id, username, password)
print(f"完整的cookie数据: {jd_cookie}")
if not jd_cookie or not jd_cookie.get('status'):
logger.error("❌ 从后端服务获取 cookie 失败")
if jd_cookie and jd_cookie.get('verify_link'):
logger.error(f"❌ 需要验证登录,验证链接: {jd_cookie.get('verify_link')}")
return
cookies_str = jd_cookie.get('cookie')
logger.info("✅ 从后端服务获取 cookie 成功")
logger.debug(f"📦 获取到 cookie: {cookies_str[:50] + '...' if cookies_str else ''}")
# 测试cookie后端生成的有效性
# cookies_str = "shshshfpa=112082d7-6f59-f35d-093a-e4c035938ab8-1754961318; shshshfpx=112082d7-6f59-f35d-093a-e4c035938ab8-1754961318; __jdu=17549613198151684402245; user-key=13d41f1d-5d8a-447e-a3d1-19c964e2fa13; __jdv=76161171|direct|-|none|-|1756259208627; areaId=18; ipLoc-djd=18-1482-48942-49052; pinId=EifU7rHmf-gwaaxnNveDFw; _tp=%2Bw%2Fs5xFMS9g0SkUd93EGxqh4USt1M5LwIyzTR4TbgCM%3D; _pst=KLD%E6%B5%8B%E8%AF%95; pin=KLD%E6%B5%8B%E8%AF%95; unick=u40d47u5ckctnz; PCSYCityID=CN_430000_430100_0; ceshi3.com=000; sdtoken=AAbEsBpEIOVjqTAKCQtvQu17tqE2au0-pftQOStCUKRw9JX4gfXHESNiN_EgrTvDv8qS5IZHipleuQxIX9JXWojJS8sKju6Vh1Qqt5LjakfSeWqqAOL-HhXeBn9m; shshshfpb=BApXS1gvPG_xA1izHnR4d6bvzjbeOVTtiBhbXFDdg9xJ1Mh6uh462; 3AB9D23F7A4B3CSS=jdd03OFWZGTHWHQYLKSX5BEWHXMTLYAHXEEXC7HBOIZDBIPLMQZT5LTHKICALRZU5ZDL6X6T3NMHRNSJJDX6BTEI6AVJS5IAAAAMZDDKTS5YAAAAACJZVYQP7FYJA3UX; _gia_d=1; wlfstk_smdl=0whbpvk7ixj27lbykeeb8rh6iulon3fo; __jda=95931165.17549613198151684402245.1754961320.1757039732.1757053789.21; __jdb=95931165.30.17549613198151684402245|21.1757053789; __jdc=95931165; 3AB9D23F7A4B3C9B=OFWZGTHWHQYLKSX5BEWHXMTLYAHXEEXC7HBOIZDBIPLMQZT5LTHKICALRZU5ZDL6X6T3NMHRNSJJDX6BTEI6AVJS5I; TrackID=1C24wlDX-QPSyJRaB_1YHqCLhBW6qIo2stht_nwl5g9fGI-lJpP-CZT3TaCr9QVppcvhilhcCe1VhgXMKGWao6Fd3wJ5bQhJ9w9VwWcYOySsXfbCTQbteFWevVN1ZQYp9; thor=4E136F9D9458703D01BE17544D30601F9649F79B89E5FC150CA91054C788CAA1C82670080466F219573AE7FD6EB9ABDF9F52D520671373DAD721CC3B78613FABC99ADA1FAC8E92CDC42F5131682B1F008727F1BA49783B055AFED9349D0B79E53A51F059A1DDE3FC181DD38D1B388D829CE8ADD775D0D30C38A8CAD0519DCD0C; flash=3_KFKaImBVn2spH8stTd9wjKlZQTxgYcZCPXXP_axvJMphR3w29aJNU2c2qPReKxWHRX1lzJ7MfD9iQmHQI-2cKp0dYzs6YsH9eDyB3lQxuu6MtkM8jCiBynVSdRBnr21oDrLKGMeYG6yYlcEsAsbe8OC-yKO69758MJYyMZd_4soV; light_key=AASBKE7rOxgWQziEhC_QY6yakCROyWTrRIF9K9uCpw_IcR8gGNaL7IM6AQuVa-3pJoC9wTze; logining=1; rita=A1EA9FF92ADE7FC61C825E83F126B9E97EF9243BEED9B77E4F7110D6081254A8EEAA66B26BFA00E08CBD8B0C88DD3D292CAD14839A50184501755B761A11F679F63D9DAA76E6785799D2F78AE378F76F32E05C1914C1132995B15CC5F79AFB9314A9D6FE7911DAFE1D958906C016E724"
# 2. 获取配置信息
logger.info("🔵 步骤2: 获取配置信息...")
config = None
for i in range(3):
try:
config = fix_jd_util.get_config(cookies_str)
if config and config.get('data'):
logger.info(f"✅ 第{i + 1}次尝试获取配置成功")
break
else:
logger.warning(f"⚠️ 第{i + 1}次尝试获取配置返回空数据")
except Exception as e:
logger.error(f"获取配置异常({i + 1}/3): {e}")
if i == 2:
return
await asyncio.sleep(3) # 使用异步等待
if not config or not config.get('data'):
logger.error("❌ 获取配置失败")
return
# 3. 提取必要参数
logger.info("🔵 步骤3: 提取配置参数...")
aid = config["data"].get("aid")
vender_id = config["data"].get("venderId")
pin_zj = config["data"].get("pin", "").lower()
if not all([aid, vender_id, pin_zj]):
logger.error("❌ 登录信息不完整,需要重新登录")
return
logger.info(f"✅ 获取到配置: aid={aid}, vender_id={vender_id}, pin_zj={pin_zj}")
# 4. 启动监听
logger.info("🔵 步骤4: 启动消息监听...")
stop_event = asyncio.Event()
try:
await fix_jd_util.message_monitoring(
cookies_str=cookies_str,
aid=aid,
pin_zj=pin_zj,
vender_id=vender_id,
store=store_today,
stop_event=stop_event,
username=username,
password=password
)
except Exception as e:
logger.error(f"❌ 监听过程中出现错误: {e}")
import traceback
logger.error(f"错误详情: {traceback.format_exc()}")
except Exception as e:
logger.error(f"❌ 主程序执行过程中出现错误: {e}")
import traceback
logger.error(f"错误详情: {traceback.format_exc()}")
if __name__ == '__main__':
asyncio.run(main())
# asyncio.run(new_test_login())

View File

Binary file not shown.

Binary file not shown.

3158
dist/main/_internal/Utils/Pdd/PddUtils.py vendored Normal file

File diff suppressed because it is too large Load Diff

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,173 @@
import ctypes
import asyncio
import websockets
import hashlib
import time
import json
# 加载DLL文件
sainiu_api = ctypes.CDLL('SaiNiuApi.dll')
# 定义函数参数类型和返回值类型
sainiu_api.Access_ServerStart.argtypes = [
ctypes.c_int,
ctypes.c_bool,
ctypes.c_bool,
ctypes.c_bool,
ctypes.c_bool,
ctypes.c_int,
ctypes.c_char_p,
ctypes.c_bool,
ctypes.c_bool
]
sainiu_api.Access_ServerStart.restype = ctypes.c_char_p # 返回值类型为文本型(字符串)
# 调用函数时传入的参数
port = 3030 # WS以及HTTP的请求端口默认3030
web_socket = True # 是否开启WebSocket
http_server = True # 是否开启HTTP请求
remote = False # 是否开启远程请求
log = True # 是否开启日志功能
ws_max = 0 # 设置WS客户端最大连接数0 = 不限制
sign_key = b'111111' # 签名key留空表示无需签名请求 调试时建议修改为空
sha256 = True # sign签名方式假 = md5真 = hmac - sha256
version_tip = True # 是否开启SDK版本与DLL不一致时的刷新提示
# 启动服务器
result = sainiu_api.Access_ServerStart(
port,
web_socket,
http_server,
remote,
log,
ws_max,
sign_key,
sha256,
version_tip
)
print(f"Access_ServerStart 服务器启动: {result.decode('gbk')}")
# 计算sign值
def sign_GetSign(signKey, post, time, sha256=False):
# 拼接字符串
data = post + str(time) + signKey
# 将字符串转换为字节集
byte_data = data.encode('utf-8')
# 根据sha256参数选择哈希算法
if sha256:
hash_object = hashlib.sha256(byte_data)
else:
hash_object = hashlib.md5(byte_data)
# 获取十六进制表示并转换为小写
sign = hash_object.hexdigest().lower()
return sign
async def connect_to_websocket():
while True:
try:
# 获取当前的13位时间戳毫秒级
current_time_ws = int(time.time() * 1000)
# 计算签名参数post参数为"ws"不可修改signKey为自定义密匙sha256参数为True选择sha256算法,为False则选择md5算法需要与Access_StartServer的参数一致
ws_sign = sign_GetSign(sign_key.decode('utf-8'), "ws", current_time_ws, sha256)
# 构建URI
uri = f"ws://127.0.0.1:{port}?time={current_time_ws}&sign={ws_sign}"
async with websockets.connect(uri) as websocket:
print("已连接到ws服务器")
# 再次获取当前的13位时间戳毫秒级以避免签名超时
current_time_json = int(time.time() * 1000)
# 计算JSON数据的签名参数post参数为"init"
json_sign = sign_GetSign(sign_key.decode('utf-8'), "Init", current_time_json, sha256)
# 发送JSON数据
message = {
"type": "Invoke_SaiNiu",
"traceId": "Access_Init",
"codeType": "Access",
"post": "Init",
"data": {
"AccessType": 0,
"AccessId": "AccessId", #替换成授权的AccessId,
"AccessKey": "AccessKey", #替换成授权的AccessKey
"ForceLogin": True
},
"time": current_time_json,
"sign": json_sign
}
await websocket.send(json.dumps(message))
print(f"Sent message: {message}")
try:
while True:
# 接收来自服务器的消息
response = await websocket.recv()
print(f"接收来自服务器的消息: {response}")
# 解析响应消息
response_data = json.loads(response)
# 判断traceId是否匹配
if response_data.get("traceId") == "Access_Init":
# 再次获取当前的13位时间戳毫秒级以避免签名超时
current_time_sys_init = int(time.time() * 1000)
# 计算新的JSON数据的签名参数post参数为"SysInit"
sys_init_sign = sign_GetSign(sign_key.decode('utf-8'), "SysInit", current_time_sys_init, sha256)
# 发送新的JSON数据
new_message = {
"type": "Invoke_SaiNiu",
"traceId": "0470bf9489729b2e8a2126a04ab3e272",#自定义返回的traceId
"codeType": "Access",
"post": "SysInit",
"data": {
"number": 20,
"port": 3030,
"token": True,
"connect": True,
"disconnect": True,
"newMessage": True,
"groupMessage": True,
"notice": True,
"event": True,
"assistant": True
},
"time": current_time_sys_init,
"sign": sys_init_sign
}
await websocket.send(json.dumps(new_message))
print(f"Sent message: {new_message}")
except websockets.ConnectionClosed:
print("服务器关闭连接")
except Exception as e:
print(f"出现错误:{e}")
except websockets.InvalidURI as e:
print(f"ws地址无效: {e} 请检查WebSocket地址")
break
except ConnectionRefusedError as e:
print(f"连接被拒绝: {e} 服务器可能未运行或URI不正确。5秒后重试..")
await asyncio.sleep(5)
except OSError as e:
print(f"操作系统错误: {e} 服务器可能无法访问。5秒后重试..")
await asyncio.sleep(5)
except Exception as e:
print(f"发生意外错误:{e} Sign验证错误。5秒后重试..")
await asyncio.sleep(5)
# 运行异步主函数
if __name__ == "__main__":
asyncio.run(connect_to_websocket())

View File

@@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 06974a5ccb59221159504cb0c9d0222b
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="en" data-content_root="./">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Page not found &#8212; Python 3.13.5 documentation</title><meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="/en/latest/_static/pygments.css?v=b86133f3" />
<link rel="stylesheet" type="text/css" href="/en/latest/_static/classic.css?v=234b1a7c" />
<link rel="stylesheet" type="text/css" href="/en/latest/_static/pydoctheme.css?v=5ff89526" />
<link id="pygments_dark_css" media="(prefers-color-scheme: dark)" rel="stylesheet" type="text/css" href="/en/latest/_static/pygments_dark.css?v=5349f25f" />
<script src="/en/latest/_static/documentation_options.js?v=32a6def9"></script>
<script src="/en/latest/_static/doctools.js?v=9bcbadda"></script>
<script src="/en/latest/_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="/en/latest/_static/sidebar.js"></script>
<link rel="search" type="application/opensearchdescription+xml"
title="Search within Python 3.13.5 documentation"
href="/en/latest/_static/opensearch.xml"/>
<link rel="author" title="About these documents" href="/en/latest/about.html" />
<link rel="index" title="Index" href="/en/latest/genindex.html" />
<link rel="search" title="Search" href="/en/latest/search.html" />
<link rel="copyright" title="Copyright" href="/en/latest/copyright.html" />
<link rel="canonical" href="https://docs.python.org/3/404.html">
<style>
@media only screen {
table.full-width-table {
width: 100%;
}
}
</style>
<link rel="stylesheet" href="/en/latest/_static/pydoctheme_dark.css" media="(prefers-color-scheme: dark)" id="pydoctheme_dark_css">
<link rel="shortcut icon" type="image/png" href="/en/latest/_static/py.svg">
<script type="text/javascript" src="/en/latest/_static/copybutton.js"></script>
<script type="text/javascript" src="/en/latest/_static/menu.js"></script>
<script type="text/javascript" src="/en/latest/_static/search-focus.js"></script>
<script type="text/javascript" src="/en/latest/_static/themetoggle.js"></script>
<script type="text/javascript" src="/en/latest/_static/rtd_switcher.js"></script>
<meta name="readthedocs-addons-api-version" content="1">
</head>
<body>
<div class="mobile-nav">
<input type="checkbox" id="menuToggler" class="toggler__input" aria-controls="navigation"
aria-pressed="false" aria-expanded="false" role="button" aria-label="Menu">
<nav class="nav-content" role="navigation">
<label for="menuToggler" class="toggler__label">
<span></span>
</label>
<span class="nav-items-wrapper">
<a href="https://www.python.org/" class="nav-logo">
<img src="/en/latest/_static/py.svg" alt="Python logo">
</a>
<span class="version_switcher_placeholder"></span>
<form role="search" class="search" action="/en/latest/search.html" method="get">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" class="search-icon">
<path fill-rule="nonzero" fill="currentColor" d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 001.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 00-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 005.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path>
</svg>
<input placeholder="Quick search" aria-label="Quick search" type="search" name="q">
<input type="submit" value="Go">
</form>
</span>
</nav>
<div class="menu-wrapper">
<nav class="menu" role="navigation" aria-label="main navigation">
<div class="language_switcher_placeholder"></div>
<label class="theme-selector-label">
Theme
<select class="theme-selector" oninput="activateTheme(this.value)">
<option value="auto" selected>Auto</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
</nav>
</div>
</div>
<div class="related" role="navigation" aria-label="Related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="/en/latest/genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="/en/latest/py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><img src="/en/latest/_static/py.svg" alt="Python logo" style="vertical-align: middle; margin-top: -1px"></li>
<li><a href="https://www.python.org/">Python</a> &#187;</li>
<li class="switchers">
<div class="language_switcher_placeholder"></div>
<div class="version_switcher_placeholder"></div>
</li>
<li>
</li>
<li id="cpython-language-and-version">
<a href="/en/latest/index.html">3.13.5 Documentation</a> &#187;
</li>
<li class="nav-item nav-item-this"><a href="">Page not found</a></li>
<li class="right">
<div class="inline-search" role="search">
<form class="inline-search" action="/en/latest/search.html" method="get">
<input placeholder="Quick search" aria-label="Quick search" type="search" name="q" id="search-box">
<input type="submit" value="Go">
</form>
</div>
|
</li>
<li class="right">
<label class="theme-selector-label">
Theme
<select class="theme-selector" oninput="activateTheme(this.value)">
<option value="auto" selected>Auto</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label> |</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<h1>Page not found</h1>
Unfortunately we couldn't find the content you were looking for.
<div class="clearer"></div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="Main">
<div class="sphinxsidebarwrapper">
</div>
<div id="sidebarbutton" title="Collapse sidebar">
<span>«</span>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="Related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="/en/latest/genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="/en/latest/py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><img src="/en/latest/_static/py.svg" alt="Python logo" style="vertical-align: middle; margin-top: -1px"></li>
<li><a href="https://www.python.org/">Python</a> &#187;</li>
<li class="switchers">
<div class="language_switcher_placeholder"></div>
<div class="version_switcher_placeholder"></div>
</li>
<li>
</li>
<li id="cpython-language-and-version">
<a href="/en/latest/index.html">3.13.5 Documentation</a> &#187;
</li>
<li class="nav-item nav-item-this"><a href="">Page not found</a></li>
<li class="right">
<div class="inline-search" role="search">
<form class="inline-search" action="/en/latest/search.html" method="get">
<input placeholder="Quick search" aria-label="Quick search" type="search" name="q" id="search-box">
<input type="submit" value="Go">
</form>
</div>
|
</li>
<li class="right">
<label class="theme-selector-label">
Theme
<select class="theme-selector" oninput="activateTheme(this.value)">
<option value="auto" selected>Auto</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label> |</li>
</ul>
</div>
<div class="footer">
&copy;
<a href="/en/latest/copyright.html">
Copyright
</a>
2001-2025, Python Software Foundation.
<br>
This page is licensed under the Python Software Foundation License Version 2.
<br>
Examples, recipes, and other code in the documentation are additionally licensed under the Zero Clause BSD License.
<br>
See <a href="/license.html">History and License</a> for more information.<br>
<br>
The Python Software Foundation is a non-profit corporation.
<a href="https://www.python.org/psf/donations/">Please donate.</a>
<br>
<br>
Last updated on Jun 11, 2025 (15:56 UTC).
<a href="/bugs.html">Found a bug</a>?
<br>
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.2.3.
</div>
</body>
</html>

View File

@@ -0,0 +1,175 @@
from datetime import tzinfo, timedelta, datetime
ZERO = timedelta(0)
HOUR = timedelta(hours=1)
SECOND = timedelta(seconds=1)
# A class capturing the platform's idea of local time.
# (May result in wrong values on historical times in
# timezones where UTC offset and/or the DST rules had
# changed in the past.)
import time as _time
STDOFFSET = timedelta(seconds = -_time.timezone)
if _time.daylight:
DSTOFFSET = timedelta(seconds = -_time.altzone)
else:
DSTOFFSET = STDOFFSET
DSTDIFF = DSTOFFSET - STDOFFSET
class LocalTimezone(tzinfo):
def fromutc(self, dt):
assert dt.tzinfo is self
stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND
args = _time.localtime(stamp)[:6]
dst_diff = DSTDIFF // SECOND
# Detect fold
fold = (args == _time.localtime(stamp - dst_diff))
return datetime(*args, microsecond=dt.microsecond,
tzinfo=self, fold=fold)
def utcoffset(self, dt):
if self._isdst(dt):
return DSTOFFSET
else:
return STDOFFSET
def dst(self, dt):
if self._isdst(dt):
return DSTDIFF
else:
return ZERO
def tzname(self, dt):
return _time.tzname[self._isdst(dt)]
def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second,
dt.weekday(), 0, 0)
stamp = _time.mktime(tt)
tt = _time.localtime(stamp)
return tt.tm_isdst > 0
Local = LocalTimezone()
# A complete implementation of current DST rules for major US time zones.
def first_sunday_on_or_after(dt):
days_to_go = 6 - dt.weekday()
if days_to_go:
dt += timedelta(days_to_go)
return dt
# US DST Rules
#
# This is a simplified (i.e., wrong for a few cases) set of rules for US
# DST start and end times. For a complete and up-to-date set of DST rules
# and timezone definitions, visit the Olson Database (or try pytz):
# http://www.twinsun.com/tz/tz-link.htm
# https://sourceforge.net/projects/pytz/ (might not be up-to-date)
#
# In the US, since 2007, DST starts at 2am (standard time) on the second
# Sunday in March, which is the first Sunday on or after Mar 8.
DSTSTART_2007 = datetime(1, 3, 8, 2)
# and ends at 2am (DST time) on the first Sunday of Nov.
DSTEND_2007 = datetime(1, 11, 1, 2)
# From 1987 to 2006, DST used to start at 2am (standard time) on the first
# Sunday in April and to end at 2am (DST time) on the last
# Sunday of October, which is the first Sunday on or after Oct 25.
DSTSTART_1987_2006 = datetime(1, 4, 1, 2)
DSTEND_1987_2006 = datetime(1, 10, 25, 2)
# From 1967 to 1986, DST used to start at 2am (standard time) on the last
# Sunday in April (the one on or after April 24) and to end at 2am (DST time)
# on the last Sunday of October, which is the first Sunday
# on or after Oct 25.
DSTSTART_1967_1986 = datetime(1, 4, 24, 2)
DSTEND_1967_1986 = DSTEND_1987_2006
def us_dst_range(year):
# Find start and end times for US DST. For years before 1967, return
# start = end for no DST.
if 2006 < year:
dststart, dstend = DSTSTART_2007, DSTEND_2007
elif 1986 < year < 2007:
dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006
elif 1966 < year < 1987:
dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986
else:
return (datetime(year, 1, 1), ) * 2
start = first_sunday_on_or_after(dststart.replace(year=year))
end = first_sunday_on_or_after(dstend.replace(year=year))
return start, end
class USTimeZone(tzinfo):
def __init__(self, hours, reprname, stdname, dstname):
self.stdoffset = timedelta(hours=hours)
self.reprname = reprname
self.stdname = stdname
self.dstname = dstname
def __repr__(self):
return self.reprname
def tzname(self, dt):
if self.dst(dt):
return self.dstname
else:
return self.stdname
def utcoffset(self, dt):
return self.stdoffset + self.dst(dt)
def dst(self, dt):
if dt is None or dt.tzinfo is None:
# An exception may be sensible here, in one or both cases.
# It depends on how you want to treat them. The default
# fromutc() implementation (called by the default astimezone()
# implementation) passes a datetime with dt.tzinfo is self.
return ZERO
assert dt.tzinfo is self
start, end = us_dst_range(dt.year)
# Can't compare naive to aware objects, so strip the timezone from
# dt first.
dt = dt.replace(tzinfo=None)
if start + HOUR <= dt < end - HOUR:
# DST is in effect.
return HOUR
if end - HOUR <= dt < end:
# Fold (an ambiguous hour): use dt.fold to disambiguate.
return ZERO if dt.fold else HOUR
if start <= dt < start + HOUR:
# Gap (a non-existent hour): reverse the fold rule.
return HOUR if dt.fold else ZERO
# DST is off.
return ZERO
def fromutc(self, dt):
assert dt.tzinfo is self
start, end = us_dst_range(dt.year)
start = start.replace(tzinfo=self)
end = end.replace(tzinfo=self)
std_time = dt + self.stdoffset
dst_time = std_time + HOUR
if end <= dst_time < end + HOUR:
# Repeated hour
return std_time.replace(fold=1)
if std_time < start or dst_time >= end:
# Standard time
return std_time
if start <= std_time < end - HOUR:
# Daylight saving time
return dst_time
Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
Central = USTimeZone(-6, "Central", "CST", "CDT")
Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -0,0 +1,906 @@
/*
* Sphinx stylesheet -- basic theme.
*/
/* -- main layout ----------------------------------------------------------- */
div.clearer {
clear: both;
}
div.section::after {
display: block;
content: '';
clear: left;
}
/* -- relbar ---------------------------------------------------------------- */
div.related {
width: 100%;
font-size: 90%;
}
div.related h3 {
display: none;
}
div.related ul {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
}
div.related li {
display: inline;
}
div.related li.right {
float: right;
margin-right: 5px;
}
/* -- sidebar --------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 10px 5px 0 10px;
}
div.sphinxsidebar {
float: left;
width: 230px;
margin-left: -100%;
font-size: 90%;
word-wrap: break-word;
overflow-wrap : break-word;
}
div.sphinxsidebar ul {
list-style: none;
}
div.sphinxsidebar ul ul,
div.sphinxsidebar ul.want-points {
margin-left: 20px;
list-style: square;
}
div.sphinxsidebar ul ul {
margin-top: 0;
margin-bottom: 0;
}
div.sphinxsidebar form {
margin-top: 10px;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar #searchbox form.search {
overflow: hidden;
}
div.sphinxsidebar #searchbox input[type="text"] {
float: left;
width: 80%;
padding: 0.25em;
box-sizing: border-box;
}
div.sphinxsidebar #searchbox input[type="submit"] {
float: left;
width: 20%;
border-left: none;
padding: 0.25em;
box-sizing: border-box;
}
img {
border: 0;
max-width: 100%;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin-top: 10px;
}
ul.search li {
padding: 5px 0;
}
ul.search li a {
font-weight: bold;
}
ul.search li p.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
margin-left: auto;
margin-right: auto;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable {
width: 100%;
}
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable ul {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
}
table.indextable > tbody > tr > td > ul {
padding-left: 0em;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
div.modindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
div.genindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
/* -- domain module index --------------------------------------------------- */
table.modindextable td {
padding: 2px;
border-collapse: collapse;
}
/* -- general body styles --------------------------------------------------- */
div.body {
min-width: 360px;
max-width: 800px;
}
div.body p, div.body dd, div.body li, div.body blockquote {
-moz-hyphens: auto;
-ms-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
}
a.headerlink {
visibility: hidden;
}
a:visited {
color: #551A8B;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink,
caption:hover > a.headerlink,
p.caption:hover > a.headerlink,
div.code-block-caption:hover > a.headerlink {
visibility: visible;
}
div.body p.caption {
text-align: inherit;
}
div.body td {
text-align: left;
}
.first {
margin-top: 0 !important;
}
p.rubric {
margin-top: 30px;
font-weight: bold;
}
img.align-left, figure.align-left, .figure.align-left, object.align-left {
clear: left;
float: left;
margin-right: 1em;
}
img.align-right, figure.align-right, .figure.align-right, object.align-right {
clear: right;
float: right;
margin-left: 1em;
}
img.align-center, figure.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
img.align-default, figure.align-default, .figure.align-default {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.align-default {
text-align: center;
}
.align-right {
text-align: right;
}
/* -- sidebars -------------------------------------------------------------- */
div.sidebar,
aside.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
padding: 7px;
background-color: #ffe;
width: 40%;
float: right;
clear: right;
overflow-x: auto;
}
p.sidebar-title {
font-weight: bold;
}
nav.contents,
aside.topic,
div.admonition, div.topic, blockquote {
clear: left;
}
/* -- topics ---------------------------------------------------------------- */
nav.contents,
aside.topic,
div.topic {
border: 1px solid #ccc;
padding: 7px;
margin: 10px 0 10px 0;
}
p.topic-title {
font-size: 1.1em;
font-weight: bold;
margin-top: 10px;
}
/* -- admonitions ----------------------------------------------------------- */
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 7px;
}
div.admonition dt {
font-weight: bold;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
div.body p.centered {
text-align: center;
margin-top: 25px;
}
/* -- content of sidebars/topics/admonitions -------------------------------- */
div.sidebar > :last-child,
aside.sidebar > :last-child,
nav.contents > :last-child,
aside.topic > :last-child,
div.topic > :last-child,
div.admonition > :last-child {
margin-bottom: 0;
}
div.sidebar::after,
aside.sidebar::after,
nav.contents::after,
aside.topic::after,
div.topic::after,
div.admonition::after,
blockquote::after {
display: block;
content: '';
clear: both;
}
/* -- tables ---------------------------------------------------------------- */
table.docutils {
margin-top: 10px;
margin-bottom: 10px;
border: 0;
border-collapse: collapse;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
table.align-default {
margin-left: auto;
margin-right: auto;
}
table caption span.caption-number {
font-style: italic;
}
table caption span.caption-text {
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 1px solid #aaa;
}
th {
text-align: left;
padding-right: 5px;
}
table.citation {
border-left: solid 1px gray;
margin-left: 1px;
}
table.citation td {
border-bottom: none;
}
th > :first-child,
td > :first-child {
margin-top: 0px;
}
th > :last-child,
td > :last-child {
margin-bottom: 0px;
}
/* -- figures --------------------------------------------------------------- */
div.figure, figure {
margin: 0.5em;
padding: 0.5em;
}
div.figure p.caption, figcaption {
padding: 0.3em;
}
div.figure p.caption span.caption-number,
figcaption span.caption-number {
font-style: italic;
}
div.figure p.caption span.caption-text,
figcaption span.caption-text {
}
/* -- field list styles ----------------------------------------------------- */
table.field-list td, table.field-list th {
border: 0 !important;
}
.field-list ul {
margin: 0;
padding-left: 1em;
}
.field-list p {
margin: 0;
}
.field-name {
-moz-hyphens: manual;
-ms-hyphens: manual;
-webkit-hyphens: manual;
hyphens: manual;
}
/* -- hlist styles ---------------------------------------------------------- */
table.hlist {
margin: 1em 0;
}
table.hlist td {
vertical-align: top;
}
/* -- object description styles --------------------------------------------- */
.sig {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
}
.sig-name, code.descname {
background-color: transparent;
font-weight: bold;
}
.sig-name {
font-size: 1.1em;
}
code.descname {
font-size: 1.2em;
}
.sig-prename, code.descclassname {
background-color: transparent;
}
.optional {
font-size: 1.3em;
}
.sig-paren {
font-size: larger;
}
.sig-param.n {
font-style: italic;
}
/* C++ specific styling */
.sig-inline.c-texpr,
.sig-inline.cpp-texpr {
font-family: unset;
}
.sig.c .k, .sig.c .kt,
.sig.cpp .k, .sig.cpp .kt {
color: #0033B3;
}
.sig.c .m,
.sig.cpp .m {
color: #1750EB;
}
.sig.c .s, .sig.c .sc,
.sig.cpp .s, .sig.cpp .sc {
color: #067D17;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
list-style: decimal;
}
ol.loweralpha {
list-style: lower-alpha;
}
ol.upperalpha {
list-style: upper-alpha;
}
ol.lowerroman {
list-style: lower-roman;
}
ol.upperroman {
list-style: upper-roman;
}
:not(li) > ol > li:first-child > :first-child,
:not(li) > ul > li:first-child > :first-child {
margin-top: 0px;
}
:not(li) > ol > li:last-child > :last-child,
:not(li) > ul > li:last-child > :last-child {
margin-bottom: 0px;
}
ol.simple ol p,
ol.simple ul p,
ul.simple ol p,
ul.simple ul p {
margin-top: 0;
}
ol.simple > li:not(:first-child) > p,
ul.simple > li:not(:first-child) > p {
margin-top: 0;
}
ol.simple p,
ul.simple p {
margin-bottom: 0;
}
aside.footnote > span,
div.citation > span {
float: left;
}
aside.footnote > span:last-of-type,
div.citation > span:last-of-type {
padding-right: 0.5em;
}
aside.footnote > p {
margin-left: 2em;
}
div.citation > p {
margin-left: 4em;
}
aside.footnote > p:last-of-type,
div.citation > p:last-of-type {
margin-bottom: 0em;
}
aside.footnote > p:last-of-type:after,
div.citation > p:last-of-type:after {
content: "";
clear: both;
}
dl.field-list {
display: grid;
grid-template-columns: fit-content(30%) auto;
}
dl.field-list > dt {
font-weight: bold;
word-break: break-word;
padding-left: 0.5em;
padding-right: 5px;
}
dl.field-list > dd {
padding-left: 0.5em;
margin-top: 0em;
margin-left: 0em;
margin-bottom: 0em;
}
dl {
margin-bottom: 15px;
}
dd > :first-child {
margin-top: 0px;
}
dd ul, dd table {
margin-bottom: 10px;
}
dd {
margin-top: 3px;
margin-bottom: 10px;
margin-left: 30px;
}
.sig dd {
margin-top: 0px;
margin-bottom: 0px;
}
.sig dl {
margin-top: 0px;
margin-bottom: 0px;
}
dl > dd:last-child,
dl > dd:last-child > :last-child {
margin-bottom: 0;
}
dt:target, span.highlighted {
background-color: #fbe54e;
}
rect.highlighted {
fill: #fbe54e;
}
dl.glossary dt {
font-weight: bold;
font-size: 1.1em;
}
.versionmodified {
font-style: italic;
}
.system-message {
background-color: #fda;
padding: 5px;
border: 3px solid red;
}
.footnote:target {
background-color: #ffa;
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
.classifier {
font-style: oblique;
}
.classifier:before {
font-style: normal;
margin: 0 0.5em;
content: ":";
display: inline-block;
}
abbr, acronym {
border-bottom: dotted 1px;
cursor: help;
}
/* -- code displays --------------------------------------------------------- */
pre {
overflow: auto;
overflow-y: hidden; /* fixes display issues on Chrome browsers */
}
pre, div[class*="highlight-"] {
clear: both;
}
span.pre {
-moz-hyphens: none;
-ms-hyphens: none;
-webkit-hyphens: none;
hyphens: none;
white-space: nowrap;
}
div[class*="highlight-"] {
margin: 1em 0;
}
td.linenos pre {
border: 0;
background-color: transparent;
color: #aaa;
}
table.highlighttable {
display: block;
}
table.highlighttable tbody {
display: block;
}
table.highlighttable tr {
display: flex;
}
table.highlighttable td {
margin: 0;
padding: 0;
}
table.highlighttable td.linenos {
padding-right: 0.5em;
}
table.highlighttable td.code {
flex: 1;
overflow: hidden;
}
.highlight .hll {
display: block;
}
div.highlight pre,
table.highlighttable pre {
margin: 0;
}
div.code-block-caption + div {
margin-top: 0;
}
div.code-block-caption {
margin-top: 1em;
padding: 2px 5px;
font-size: small;
}
div.code-block-caption code {
background-color: transparent;
}
table.highlighttable td.linenos,
span.linenos,
div.highlight span.gp { /* gp: Generic.Prompt */
user-select: none;
-webkit-user-select: text; /* Safari fallback only */
-webkit-user-select: none; /* Chrome/Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+ */
}
div.code-block-caption span.caption-number {
padding: 0.1em 0.3em;
font-style: italic;
}
div.code-block-caption span.caption-text {
}
div.literal-block-wrapper {
margin: 1em 0;
}
code.xref, a code {
background-color: transparent;
font-weight: bold;
}
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
background-color: transparent;
}
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family: sans-serif;
}
div.viewcode-block:target {
margin: -1px -10px;
padding: 0 10px;
}
/* -- math display ---------------------------------------------------------- */
img.math {
vertical-align: middle;
}
div.body div.math p {
text-align: center;
}
span.eqno {
float: right;
}
span.eqno a.headerlink {
position: absolute;
z-index: 1;
}
div.math:hover a.headerlink {
visibility: visible;
}
/* -- printout stylesheet --------------------------------------------------- */
@media print {
div.document,
div.documentwrapper,
div.bodywrapper {
margin: 0 !important;
width: 100%;
}
div.sphinxsidebar,
div.related,
div.footer,
#top-link {
display: none;
}
}

View File

@@ -0,0 +1,59 @@
document.addEventListener("DOMContentLoaded", function () {
// add the search form and bind the events
document
.querySelector("h1")
.insertAdjacentHTML(
"afterend",
[
"<p>Filter entries by content:",
'<input type="text" value="" id="searchbox" style="width: 50%">',
'<input type="submit" id="searchbox-submit" value="Filter"></p>',
].join("\n"),
);
function doFilter() {
let query;
try {
query = new RegExp(document.querySelector("#searchbox").value, "i");
} catch (e) {
return; // not a valid regex (yet)
}
// find headers for the versions (What's new in Python X.Y.Z?)
const h2s = document.querySelectorAll("#changelog h2");
for (const h2 of h2s) {
let sections_found = 0;
// find headers for the sections (Core, Library, etc.)
const h3s = h2.parentNode.querySelectorAll("h3");
for (const h3 of h3s) {
let entries_found = 0;
// find all the entries
const lis = h3.parentNode.querySelectorAll("li");
for (let li of lis) {
// check if the query matches the entry
if (query.test(li.textContent)) {
li.style.display = "block";
entries_found++;
} else {
li.style.display = "none";
}
}
// if there are entries, show the section, otherwise hide it
if (entries_found > 0) {
h3.parentNode.style.display = "block";
sections_found++;
} else {
h3.parentNode.style.display = "none";
}
}
if (sections_found > 0) {
h2.parentNode.style.display = "block";
} else {
h2.parentNode.style.display = "none";
}
}
}
document.querySelector("#searchbox").addEventListener("keyup", doFilter);
document
.querySelector("#searchbox-submit")
.addEventListener("click", doFilter);
});

View File

@@ -0,0 +1,294 @@
/*
* Sphinx stylesheet -- classic theme.
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
html {
/* CSS hack for macOS's scrollbar (see #1125) */
background-color: #FFFFFF;
}
body {
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
font-size: 100%;
background-color: white;
color: #000;
margin: 0;
padding: 0;
}
div.document {
display: flex;
background-color: white;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
div.body {
background-color: white;
color: #222222;
padding: 0 20px 30px 20px;
}
div.footer {
color: #555555;
width: 100%;
padding: 9px 0 9px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #555555;
text-decoration: underline;
}
div.related {
background-color: white;
line-height: 30px;
color: #666666;
}
div.related a {
color: #444444;
}
div.sphinxsidebar {
}
div.sphinxsidebar h3 {
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
color: #444444;
font-size: 1.4em;
font-weight: normal;
margin: 0;
padding: 0;
}
div.sphinxsidebar h3 a {
color: #444444;
}
div.sphinxsidebar h4 {
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
color: #444444;
font-size: 1.3em;
font-weight: normal;
margin: 5px 0 0 0;
padding: 0;
}
div.sphinxsidebar p {
color: #444444;
}
div.sphinxsidebar p.topless {
margin: 5px 10px 10px 10px;
}
div.sphinxsidebar ul {
margin: 10px;
padding: 0;
color: #444444;
}
div.sphinxsidebar a {
color: #444444;
}
div.sphinxsidebar input {
border: 1px solid #444444;
font-family: sans-serif;
font-size: 1em;
}
/* for collapsible sidebar */
#sidebarbutton {
height: 100%;
background-color: #cccccc;
margin-left: 0;
color: #FFFFFF;
border-left: 1px solid white;
font-size: 1.2em;
cursor: pointer;
padding-top: 1px;
float: right;
display: table; /* for vertically centering the <span> */
}
#sidebarbutton:hover {
background-color: white;
}
#sidebarbutton span {
display: table-cell;
vertical-align: middle;
}
div.sphinxsidebarwrapper {
float: left;
margin-right: 0;
}
/* -- hyperlink styles ------------------------------------------------------ */
a {
color: #0090c0;
text-decoration: none;
}
a:visited {
color: #00608f;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* -- body styles ----------------------------------------------------------- */
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
background-color: white;
font-weight: normal;
color: #1a1a1a;
border-bottom: 1px solid #ccc;
margin: 20px -20px 10px -20px;
padding: 3px 0 3px 10px;
}
div.body h1 { margin-top: 0; font-size: 200%; }
div.body h2 { font-size: 160%; }
div.body h3 { font-size: 140%; }
div.body h4 { font-size: 120%; }
div.body h5 { font-size: 110%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #aaaaaa;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #aaaaaa;
color: white;
}
div.body p, div.body dd, div.body li, div.body blockquote {
text-align: justify;
line-height: 130%;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.admonition p {
margin-bottom: 5px;
}
div.admonition pre {
margin-bottom: 5px;
}
div.admonition ul, div.admonition ol {
margin-bottom: 5px;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
nav.contents,
aside.topic,
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 5px;
background-color: #eeffcc;
color: #333333;
line-height: 120%;
border: 1px solid #ac9;
border-left: none;
border-right: none;
}
code {
background-color: #ecf0f3;
padding: 0 1px 0 1px;
font-size: 0.95em;
}
th, dl.field-list > dt {
background-color: #ede;
}
.warning code {
background: #efc2c2;
}
.note code {
background: #d6d6d6;
}
.viewcode-back {
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}
div.code-block-caption {
color: #efefef;
background-color: #1c4e63;
}

View File

@@ -0,0 +1,84 @@
// Extract copyable text from the code block ignoring the
// prompts and output.
function getCopyableText(rootElement) {
rootElement = rootElement.cloneNode(true)
// tracebacks (.gt) contain bare text elements that
// need to be removed
const tracebacks = rootElement.querySelectorAll(".gt")
for (const el of tracebacks) {
while (
el.nextSibling &&
(el.nextSibling.nodeType !== Node.ELEMENT_NODE ||
!el.nextSibling.matches(".gp, .go"))
) {
el.nextSibling.remove()
}
}
// Remove all elements with the "go" (Generic.Output),
// "gp" (Generic.Prompt), or "gt" (Generic.Traceback) CSS class
const elements = rootElement.querySelectorAll(".gp, .go, .gt")
for (const el of elements) {
el.remove()
}
return rootElement.innerText.trim()
}
const loadCopyButton = () => {
const button = document.createElement("button")
button.classList.add("copybutton")
button.type = "button"
button.innerText = _("Copy")
button.title = _("Copy to clipboard")
const makeOnButtonClick = () => {
let timeout = null
// define the behavior of the button when it's clicked
return async event => {
// check if the clipboard is available
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
clearTimeout(timeout)
const buttonEl = event.currentTarget
const codeEl = buttonEl.nextElementSibling
try {
await navigator.clipboard.writeText(getCopyableText(codeEl))
} catch (e) {
console.error(e.message)
return
}
buttonEl.innerText = _("Copied!")
timeout = setTimeout(() => {
buttonEl.innerText = _("Copy")
}, 1500)
}
}
const highlightedElements = document.querySelectorAll(
".highlight-python .highlight,"
+ ".highlight-python3 .highlight,"
+ ".highlight-pycon .highlight,"
+ ".highlight-pycon3 .highlight,"
+ ".highlight-default .highlight"
)
// create and add the button to all the code blocks that contain >>>
highlightedElements.forEach(el => {
el.style.position = "relative"
// if we find a console prompt (.gp), prepend the (deeply cloned) button
const clonedButton = button.cloneNode(true)
// the onclick attribute is not cloned, set it on the new element
clonedButton.onclick = makeOnButtonClick()
el.prepend(clonedButton)
})
}
if (document.readyState !== "loading") {
loadCopyButton()
} else {
document.addEventListener("DOMContentLoaded", loadCopyButton)
}

View File

@@ -0,0 +1 @@
@import url("classic.css");

View File

@@ -0,0 +1,149 @@
/*
* Base JavaScript utilities for all Sphinx HTML documentation.
*/
"use strict";
const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
"TEXTAREA",
"INPUT",
"SELECT",
"BUTTON",
]);
const _ready = (callback) => {
if (document.readyState !== "loading") {
callback();
} else {
document.addEventListener("DOMContentLoaded", callback);
}
};
/**
* Small JavaScript module for the documentation.
*/
const Documentation = {
init: () => {
Documentation.initDomainIndexTable();
Documentation.initOnKeyListeners();
},
/**
* i18n support
*/
TRANSLATIONS: {},
PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
LOCALE: "unknown",
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext: (string) => {
const translated = Documentation.TRANSLATIONS[string];
switch (typeof translated) {
case "undefined":
return string; // no translation
case "string":
return translated; // translation exists
default:
return translated[0]; // (singular, plural) translation tuple exists
}
},
ngettext: (singular, plural, n) => {
const translated = Documentation.TRANSLATIONS[singular];
if (typeof translated !== "undefined")
return translated[Documentation.PLURAL_EXPR(n)];
return n === 1 ? singular : plural;
},
addTranslations: (catalog) => {
Object.assign(Documentation.TRANSLATIONS, catalog.messages);
Documentation.PLURAL_EXPR = new Function(
"n",
`return (${catalog.plural_expr})`
);
Documentation.LOCALE = catalog.locale;
},
/**
* helper function to focus on search bar
*/
focusSearchBar: () => {
document.querySelectorAll("input[name=q]")[0]?.focus();
},
/**
* Initialise the domain index toggle buttons
*/
initDomainIndexTable: () => {
const toggler = (el) => {
const idNumber = el.id.substr(7);
const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
if (el.src.substr(-9) === "minus.png") {
el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
toggledRows.forEach((el) => (el.style.display = "none"));
} else {
el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
toggledRows.forEach((el) => (el.style.display = ""));
}
};
const togglerElements = document.querySelectorAll("img.toggler");
togglerElements.forEach((el) =>
el.addEventListener("click", (event) => toggler(event.currentTarget))
);
togglerElements.forEach((el) => (el.style.display = ""));
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
},
initOnKeyListeners: () => {
// only install a listener if it is really needed
if (
!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
)
return;
document.addEventListener("keydown", (event) => {
// bail for input elements
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
// bail with special keys
if (event.altKey || event.ctrlKey || event.metaKey) return;
if (!event.shiftKey) {
switch (event.key) {
case "ArrowLeft":
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
const prevLink = document.querySelector('link[rel="prev"]');
if (prevLink && prevLink.href) {
window.location.href = prevLink.href;
event.preventDefault();
}
break;
case "ArrowRight":
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
const nextLink = document.querySelector('link[rel="next"]');
if (nextLink && nextLink.href) {
window.location.href = nextLink.href;
event.preventDefault();
}
break;
}
}
// some keyboard layouts may need Shift to get /
switch (event.key) {
case "/":
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
Documentation.focusSearchBar();
event.preventDefault();
}
});
},
};
// quick alias for translations
const _ = Documentation.gettext;
_ready(Documentation.init);

View File

@@ -0,0 +1,13 @@
const DOCUMENTATION_OPTIONS = {
VERSION: '3.13.5',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
FILE_SUFFIX: '.html',
LINK_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt',
NAVIGATION_WITH_KEYS: false,
SHOW_SEARCH_SUMMARY: true,
ENABLE_SEARCH_SHORTCUTS: true,
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,47 @@
"use strict";
const GLOSSARY_PAGE = "glossary.html";
const glossary_search = async () => {
const response = await fetch("_static/glossary.json");
if (!response.ok) {
throw new Error("Failed to fetch glossary.json");
}
const glossary = await response.json();
const params = new URLSearchParams(document.location.search).get("q");
if (!params) {
return;
}
const searchParam = params.toLowerCase();
const glossaryItem = glossary[searchParam];
if (!glossaryItem) {
return;
}
// set up the title text with a link to the glossary page
const glossaryTitle = document.getElementById("glossary-title");
glossaryTitle.textContent = "Glossary: " + glossaryItem.title;
const linkTarget = searchParam.replace(/ /g, "-");
glossaryTitle.href = GLOSSARY_PAGE + "#term-" + linkTarget;
// rewrite any anchor links (to other glossary terms)
// to have a full reference to the glossary page
const glossaryBody = document.getElementById("glossary-body");
glossaryBody.innerHTML = glossaryItem.body;
const anchorLinks = glossaryBody.querySelectorAll('a[href^="#"]');
anchorLinks.forEach(function (link) {
const currentUrl = link.getAttribute("href");
link.href = GLOSSARY_PAGE + currentUrl;
});
const glossaryResult = document.getElementById("glossary-result");
glossaryResult.style.display = "";
};
if (document.readyState !== "loading") {
glossary_search().catch(console.error);
} else {
document.addEventListener("DOMContentLoaded", glossary_search);
}

View File

@@ -0,0 +1,192 @@
/*
* This script contains the language-specific data used by searchtools.js,
* namely the list of stopwords, stemmer, scorer and splitter.
*/
var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
/* Non-minified version is copied as a separate JS file, if available */
/**
* Porter Stemmer
*/
var Stemmer = function() {
var step2list = {
ational: 'ate',
tional: 'tion',
enci: 'ence',
anci: 'ance',
izer: 'ize',
bli: 'ble',
alli: 'al',
entli: 'ent',
eli: 'e',
ousli: 'ous',
ization: 'ize',
ation: 'ate',
ator: 'ate',
alism: 'al',
iveness: 'ive',
fulness: 'ful',
ousness: 'ous',
aliti: 'al',
iviti: 'ive',
biliti: 'ble',
logi: 'log'
};
var step3list = {
icate: 'ic',
ative: '',
alize: 'al',
iciti: 'ic',
ical: 'ic',
ful: '',
ness: ''
};
var c = "[^aeiou]"; // consonant
var v = "[aeiouy]"; // vowel
var C = c + "[^aeiouy]*"; // consonant sequence
var V = v + "[aeiou]*"; // vowel sequence
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
var s_v = "^(" + C + ")?" + v; // vowel in stem
this.stemWord = function (w) {
var stem;
var suffix;
var firstch;
var origword = w;
if (w.length < 3)
return w;
var re;
var re2;
var re3;
var re4;
firstch = w.substr(0,1);
if (firstch == "y")
w = firstch.toUpperCase() + w.substr(1);
// Step 1a
re = /^(.+?)(ss|i)es$/;
re2 = /^(.+?)([^s])s$/;
if (re.test(w))
w = w.replace(re,"$1$2");
else if (re2.test(w))
w = w.replace(re2,"$1$2");
// Step 1b
re = /^(.+?)eed$/;
re2 = /^(.+?)(ed|ing)$/;
if (re.test(w)) {
var fp = re.exec(w);
re = new RegExp(mgr0);
if (re.test(fp[1])) {
re = /.$/;
w = w.replace(re,"");
}
}
else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1];
re2 = new RegExp(s_v);
if (re2.test(stem)) {
w = stem;
re2 = /(at|bl|iz)$/;
re3 = new RegExp("([^aeiouylsz])\\1$");
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re2.test(w))
w = w + "e";
else if (re3.test(w)) {
re = /.$/;
w = w.replace(re,"");
}
else if (re4.test(w))
w = w + "e";
}
}
// Step 1c
re = /^(.+?)y$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(s_v);
if (re.test(stem))
w = stem + "i";
}
// Step 2
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = new RegExp(mgr0);
if (re.test(stem))
w = stem + step2list[suffix];
}
// Step 3
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = new RegExp(mgr0);
if (re.test(stem))
w = stem + step3list[suffix];
}
// Step 4
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
re2 = /^(.+?)(s|t)(ion)$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(mgr1);
if (re.test(stem))
w = stem;
}
else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1] + fp[2];
re2 = new RegExp(mgr1);
if (re2.test(stem))
w = stem;
}
// Step 5
re = /^(.+?)e$/;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = new RegExp(mgr1);
re2 = new RegExp(meq1);
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
w = stem;
}
re = /ll$/;
re2 = new RegExp(mgr1);
if (re.test(w) && re2.test(w)) {
re = /.$/;
w = w.replace(re,"");
}
// and turn initial Y back to y
if (firstch == "y")
w = firstch.toLowerCase() + w.substr(1);
return w;
}
}

View File

@@ -0,0 +1,57 @@
document.addEventListener("DOMContentLoaded", function () {
// Make tables responsive by wrapping them in a div and making them scrollable
const tables = document.querySelectorAll("table.docutils")
tables.forEach(function(table){
table.outerHTML = '<div class="responsive-table__container">' + table.outerHTML + "</div>"
})
const togglerInput = document.querySelector(".toggler__input")
const togglerLabel = document.querySelector(".toggler__label")
const sideMenu = document.querySelector(".menu-wrapper")
const menuItems = document.querySelectorAll(".menu")
const doc = document.querySelector(".document")
const body = document.querySelector("body")
function closeMenu() {
togglerInput.checked = false
sideMenu.setAttribute("aria-expanded", "false")
sideMenu.setAttribute("aria-hidden", "true")
togglerLabel.setAttribute("aria-pressed", "false")
body.style.overflow = "visible"
}
function openMenu() {
togglerInput.checked = true
sideMenu.setAttribute("aria-expanded", "true")
sideMenu.setAttribute("aria-hidden", "false")
togglerLabel.setAttribute("aria-pressed", "true")
body.style.overflow = "hidden"
}
// Close menu when link on the sideMenu is clicked
sideMenu.addEventListener("click", function (event) {
let target = event.target
if (target.tagName.toLowerCase() !== "a") {
return
}
closeMenu()
})
// Add accessibility data when sideMenu is opened/closed
togglerInput.addEventListener("change", function (_event) {
togglerInput.checked ? openMenu() : closeMenu()
})
// Make sideMenu links tabbable only when visible
for(let menuItem of menuItems) {
if(togglerInput.checked) {
menuItem.setAttribute("tabindex", "0")
} else {
menuItem.setAttribute("tabindex", "-1")
}
}
// Close sideMenu when document body is clicked
doc.addEventListener("click", function () {
if (togglerInput.checked) {
closeMenu()
}
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Python</ShortName>
<Description>Search Python 3.13.5 documentation</Description>
<InputEncoding>utf-8</InputEncoding>
<Url type="text/html" method="get"
template="https://docs.python.org/3.13/search.html?q={searchTerms}"/>
<LongName>Python 3.13.5 documentation</LongName>
<Image height="16" width="16" type="image/x-icon">https://www.python.org/images/favicon16x16.ico</Image>
</OpenSearchDescription>

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

View File

@@ -0,0 +1,14 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.90472 0.00013087C7.24498 0.00316295 6.61493 0.0588153 6.06056 0.15584C4.42744 0.441207 4.13093 1.0385 4.13093 2.14002V3.59479H7.99018V4.07971H4.13093H2.68259C1.56098 4.07971 0.578874 4.7465 0.271682 6.01495C-0.0826595 7.4689 -0.0983765 8.37618 0.271682 9.89434C0.546011 11.0244 1.20115 11.8296 2.32275 11.8296H3.64965V10.0856C3.64965 8.82574 4.75178 7.71441 6.06056 7.71441H9.91531C10.9883 7.71441 11.8449 6.84056 11.8449 5.77472V2.14002C11.8449 1.10556 10.9626 0.328486 9.91531 0.15584C9.25235 0.046687 8.56447 -0.00290121 7.90472 0.00013087ZM5.81767 1.17017C6.2163 1.17017 6.54184 1.49742 6.54184 1.89978C6.54184 2.30072 6.2163 2.62494 5.81767 2.62494C5.41761 2.62494 5.0935 2.30072 5.0935 1.89978C5.0935 1.49742 5.41761 1.17017 5.81767 1.17017Z" fill="url(#paint0_linear)"/>
<path d="M12.3262 4.07971V5.77472C12.3262 7.08883 11.1998 8.19488 9.9153 8.19488H6.06055C5.00466 8.19488 4.13092 9.0887 4.13092 10.1346V13.7693C4.13092 14.8037 5.04038 15.4122 6.06055 15.709C7.28217 16.0642 8.45364 16.1285 9.9153 15.709C10.8869 15.4307 11.8449 14.8708 11.8449 13.7693V12.3145H7.99017V11.8296H11.8449H13.7746C14.8962 11.8296 15.3141 11.0558 15.7042 9.89434C16.1071 8.69865 16.09 7.5488 15.7042 6.01495C15.427 4.91058 14.8976 4.07971 13.7746 4.07971H12.3262ZM10.1582 13.2843C10.5583 13.2843 10.8824 13.6086 10.8824 14.0095C10.8824 14.4119 10.5583 14.7391 10.1582 14.7391C9.75955 14.7391 9.43402 14.4119 9.43402 14.0095C9.43402 13.6086 9.75955 13.2843 10.1582 13.2843Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="1.25961e-08" y1="1.08223e-08" x2="8.81664" y2="7.59597" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A9FD4"/>
<stop offset="1" stop-color="#306998"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="10.0654" y1="13.8872" x2="6.91912" y2="9.42957" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD43B"/>
<stop offset="1" stop-color="#FFE873"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,773 @@
/* Common colours */
:root {
--good-color: rgb(41 100 51);
--good-border: rgb(79 196 100);
--middle-color: rgb(133 72 38);
--middle-border: rgb(244, 227, 76);
--bad-color: rgb(159 49 51);
--bad-border: rgb(244, 76, 78);
}
/* unset some styles from the classic stylesheet */
div.document,
div.body,
div.related,
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6,
div.sphinxsidebar a,
div.sphinxsidebar p,
div.sphinxsidebar ul,
div.sphinxsidebar h3,
div.sphinxsidebar h3 a,
div.sphinxsidebar h4,
.menu a,
.menu p,
.menu ul,
.menu h3,
.menu h3 a,
.menu h4,
table.docutils td,
table.indextable tr.cap,
pre {
background-color: inherit;
color: inherit;
}
/* Add underlines to links */
a[href] {
text-decoration: underline 1px;
}
/* Increase the underline offset for code to avoid obscuring underscores */
a[href]:has(> code) {
text-underline-offset: 0.25em;
}
/* No underline for navigation */
a.headerlink,
div.genindex-jumpbox a,
div.modindex-jumpbox a,
div#search-results a,
div.sphinxsidebar a,
div.toctree-wrapper a,
div[role=navigation] a,
table.contentstable a,
table.indextable a {
text-decoration: none;
}
/* Except when hovered */
div.genindex-jumpbox a:hover,
div.modindex-jumpbox a:hover,
div#search-results a:hover,
div.sphinxsidebar a:hover,
div.toctree-wrapper a:hover,
div[role=navigation] a:hover,
table.contentstable a:hover,
table.indextable a:hover {
text-decoration: underline;
text-underline-offset: auto;
}
body {
margin-left: 1em;
margin-right: 1em;
}
.mobile-nav,
.menu-wrapper {
display: none;
}
div.related {
margin-top: 0.5em;
margin-bottom: 1.2em;
padding: 0.5em 0;
border-width: 1px;
border-color: #ccc;
}
.mobile-nav + div.related {
border-bottom-style: solid;
}
.document + div.related {
border-top-style: solid;
}
div.related a:hover {
color: #0095c4;
}
.related .switchers {
display: inline-flex;
}
.switchers > div {
margin-right: 5px;
}
div.related ul::after {
content: '';
clear: both;
display: block;
}
.inline-search,
form.inline-search input {
display: inline;
}
form.inline-search input[type='submit'] {
width: 40px;
}
div.document {
display: flex;
/* Don't let long code literals extend beyond the right side of the screen */
overflow-wrap: break-word;
}
/* Don't let long code literals extend beyond the right side of the screen */
span.pre {
white-space: unset;
}
div.sphinxsidebar {
display: flex;
width: min(25vw, 350px);
float: none;
position: sticky;
top: 0;
max-height: 100vh;
color: #444;
background-color: #eee;
border-radius: 5px;
line-height: 130%;
font-size: smaller;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
margin-top: 1.5em;
}
div.bodywrapper {
margin-left: min(25vw, 350px);
}
div.sphinxsidebarwrapper {
box-sizing: border-box;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
float: none;
flex-grow: 1;
}
div.sphinxsidebarwrapper > h3:first-child {
margin-top: 0.2em;
}
div.sphinxsidebarwrapper > ul > li > ul > li {
margin-bottom: 0.4em;
}
div.sphinxsidebar a:hover {
color: #0095c4;
}
form.inline-search input,
div.sphinxsidebar input,
div.related input {
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
border: 1px solid #999999;
font-size: smaller;
border-radius: 3px;
}
div.sphinxsidebar input[type='text'] {
max-width: 150px;
}
#sidebarbutton {
display: flex;
justify-content: center;
align-items: center;
width: 12px;
min-width: 12px;
border-radius: 0 5px 5px 0;
border-left: none;
}
#sidebarbutton:hover {
background-color: #AAAAAA;
}
div.body {
padding: 0 0 0 1.2em;
}
div.body p, div.body dd, div.body li, div.body blockquote {
text-align: left;
line-height: 1.6;
}
div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 {
margin: 0;
border: 0;
padding: 0.3em 0;
}
div.body hr {
border: 0;
background-color: #ccc;
height: 1px;
}
div.body pre {
border-radius: 3px;
border: 1px solid #ac9;
}
/* Admonitions */
:root {
--admonition-background: #eee;
--admonition-border: #ccc;
--admonition-color: black;
--attention-background: #bbddff5c;
--attention-border: #0000ff36;
--caution-background: #ffc;
--caution-border: #dd6;
--danger-background: #ffe4e4;
--danger-border: red;
--error-background: #ffe4e4;
--error-border: red;
--hint-background: #dfd;
--hint-border: green;
--seealso-background: #ffc;
--seealso-border: #dd6;
--tip-background: #dfd;
--tip-border: green;
--warning-background: #ffe4e4;
--warning-border: red;
}
div.body div.admonition {
background-color: var(--admonition-background);
border: 1px solid var(--admonition-border);
border-radius: 3px;
color: var(--admonition-color);
}
div.body div.admonition.attention {
background-color: var(--attention-background);
border-color: var(--attention-border);
}
div.body div.admonition.caution {
background-color: var(--caution-background);
border-color: var(--caution-border);
}
div.body div.admonition.danger {
background-color: var(--danger-background);
border-color: var(--danger-border);
}
div.body div.admonition.error {
background-color: var(--error-background);
border-color: var(--error-border);
}
div.body div.admonition.hint {
background-color: var(--hint-background);
border-color: var(--hint-border);
}
div.body div.admonition.seealso {
background-color: var(--seealso-background);
border-color: var(--seealso-border);
}
div.body div.admonition.tip {
background-color: var(--tip-background);
border-color: var(--tip-border);
}
div.body div.admonition.warning {
background-color: var(--warning-background);
border-color: var(--warning-border);
}
div.body div.impl-detail {
border-radius: 3px;
}
div.body div.impl-detail > p {
margin: 0;
}
div.body a {
color: #0072aa;
}
div.body a:visited {
color: #6363bb;
}
div.body a:hover {
color: #00b0e4;
}
tt, code, pre {
font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
font-size: 96.5%;
}
div.body pre {
line-height: 120%;
}
div.body tt,
div.body code {
border-radius: 3px;
}
div.body tt.descname,
div.body code.descname {
font-size: 120%;
}
div.body tt.xref,
div.body a tt,
div.body code.xref,
div.body a code {
font-weight: normal;
}
table.docutils {
border: 1px solid #ddd;
min-width: 20%;
border-radius: 3px;
margin-top: 10px;
margin-bottom: 10px;
}
table.docutils td,
table.docutils th {
border: 1px solid #ddd !important;
border-radius: 3px;
padding: 0.3em 0.5em;
}
table p,
table li {
text-align: left !important;
}
table.docutils th {
background-color: #eee;
}
table.footnote,
table.footnote td {
border: 0 !important;
}
div.footer {
line-height: 150%;
text-align: right;
width: auto;
margin-right: 10px;
}
div.footer a {
text-underline-offset: auto;
}
div.footer a:hover {
color: #0095c4;
}
/* C API return value annotations */
:root {
--refcount: var(--good-color);
--refcount-return-borrowed-ref: var(--middle-color);
}
.refcount {
color: var(--refcount);
}
.refcount.return_borrowed_ref {
color: var(--refcount-return-borrowed-ref)
}
.stableabi {
color: #229;
}
dl > dt span ~ em,
.sig {
font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
}
.toctree-wrapper ul {
padding-left: 20px;
}
.theme-selector {
margin-left: .5em;
}
div.genindex-jumpbox,
div.genindex-jumpbox > p {
display: inline-flex;
flex-wrap: wrap;
}
div.genindex-jumpbox a {
margin: 0 5px;
min-width: 30px;
text-align: center;
}
.copybutton {
cursor: pointer;
position: absolute;
top: 0;
right: 0;
font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
font-size: 80%;
padding-left: .5em;
padding-right: .5em;
height: 100%;
max-height: min(100%, 2.4em);
border-radius: 0 3px 0 0;
color: #000;
background-color: #fff;
border: 1px solid #ac9; /* follows div.body pre */
display: none;
}
.copybutton:hover {
background-color: #eee;
}
.copybutton:active {
background-color: #ddd;
}
.highlight:active .copybutton {
display: block;
}
.highlight:hover .copybutton {
display: block;
}
@media (max-width: 1023px) {
/* Body layout */
div.body {
min-width: 100%;
padding: 0;
font-size: 0.875rem;
}
div.bodywrapper {
margin: 0;
}
/* Typography */
div.body h1 {
font-size: 1.625rem;
}
div.body h2 {
font-size: 1.25rem;
}
div.body h3, div.body h4, div.body h5 {
font-size: 1rem;
}
/* Override default styles to make more readable */
div.body ul {
padding-inline-start: 1rem;
}
div.body blockquote {
margin-inline-start: 1rem;
margin-inline-end: 0;
}
/* Remove sidebar and top related bar */
div.related, div.sphinxsidebar {
display: none;
}
/* Anchorlinks are not hidden by fixed-positioned navbar when scrolled to */
html {
scroll-padding-top: 40px;
}
body {
margin-top: 40px;
}
/* Top navigation bar */
.mobile-nav {
display: block;
height: 40px;
width: 100%;
position: fixed;
top: 0;
left: 0;
box-shadow: rgba(0, 0, 0, 0.25) 0 0 2px 0;
z-index: 1;
}
.mobile-nav * {
box-sizing: border-box;
}
.nav-content {
position: absolute;
z-index: 1;
height: 40px;
width: 100%;
display: flex;
background-color: white;
}
.nav-items-wrapper {
display: flex;
flex: auto;
padding: .25rem;
align-items: stretch;
}
.nav-logo {
margin-right: 1rem;
flex-shrink: 0;
align-self: center;
}
.nav-content img {
display: block;
width: 20px;
}
.version_switcher_placeholder {
margin-right: 1rem;
}
.version_switcher_placeholder > select {
height: 100%;
}
.nav-content .search {
display: flex;
flex: auto;
border: 1px solid #a9a9a9;
align-items: stretch;
}
.nav-content .search input[type=search] {
border: 0;
padding-left: 24px;
width: 100%;
flex: 1;
}
.nav-content .search input[type=submit] {
height: 100%;
box-shadow: none;
border: 0;
border-left: 1px solid #a9a9a9;
cursor: pointer;
margin-right: 0;
}
.nav-content .search svg {
position: absolute;
align-self: center;
padding-left: 4px;
}
.toggler__input {
display: none;
}
.toggler__label {
width: 40px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
flex-shrink: 0;
}
.toggler__label:hover, .toggler__label:focus {
background-color: rgba(127 127 127 / 50%);
}
.toggler__label > span {
position: relative;
flex: none;
height: 2px;
width: 100%;
background: currentColor;
transition: all 400ms ease;
}
.toggler__label > span::before,
.toggler__label > span::after {
content: '';
height: 2px;
width: 100%;
background: inherit;
position: absolute;
left: 0;
top: -8px;
}
.toggler__label > span::after {
top: 8px;
}
.toggler__input:checked ~ nav > .toggler__label span {
transform: rotate(135deg);
}
.toggler__input:checked ~ nav > .toggler__label span::before {
transform: rotate(90deg);
}
.toggler__input:checked ~ nav > .toggler__label span::before,
.toggler__input:checked ~ nav > .toggler__label span::after {
top: 0;
}
.toggler__input:checked:hover ~ nav > .toggler__label span {
transform: rotate(315deg);
}
.toggler__input:checked ~ .menu-wrapper {
visibility: visible;
left: 0;
}
/* Sliding side menu */
.menu-wrapper {
display: block;
position: fixed;
top: 0;
transition: left 400ms ease;
left: -310px;
width: 300px;
height: 100%;
background-color: #eee;
color: #444444;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
overflow-y: auto;
}
.menu-wrapper.open {
visibility: visible;
left: 0;
}
.menu {
padding: 40px 10px 30px 20px;
}
.menu-wrapper h3,
.menu-wrapper h4 {
margin-bottom: 0;
font-weight: normal;
}
.menu-wrapper h4 {
font-size: 1.3em;
}
.menu-wrapper h3 {
font-size: 1.4em;
}
.menu-wrapper h3 + p,
.menu-wrapper h4 + p {
margin-top: 0.5rem;
}
.menu a {
font-size: smaller;
text-decoration: none;
}
.menu ul {
list-style: none;
line-height: 1.4;
overflow-wrap: break-word;
padding-left: 0;
}
.menu ul ul {
margin-left: 20px;
list-style: square;
}
.menu ul li {
margin-bottom: 0.5rem;
}
.language_switcher_placeholder {
margin-top: 2rem;
}
.language_switcher_placeholder select {
width: 100%;
}
.document {
position: relative;
z-index: 0;
}
/*Responsive tables*/
.responsive-table__container {
width: 100%;
overflow-x: auto;
}
.menu .theme-selector-label {
margin-top: .5em;
display: flex;
width: 100%;
}
.menu .theme-selector {
flex: auto;
}
}
@media (min-width: 1024px) {
div.footer {
margin-top: -2em;
}
}
/* Version change directives */
:root {
--versionadded: var(--good-color);
--versionchanged: var(--middle-color);
--deprecated: var(--bad-color);
--versionadded-border: var(--good-border);
--versionchanged-border: var(--middle-border);
--deprecated-border: var(--bad-border);
}
div.versionadded,
div.versionchanged,
div.deprecated,
div.deprecated-removed {
border-left: 3px solid;
padding: 0 1rem;
}
div.versionadded {
border-left-color: var(--versionadded-border);
}
div.versionchanged {
border-left-color: var(--versionchanged-border);
}
div.deprecated,
div.deprecated-removed,
div.versionremoved {
border-left-color: var(--deprecated-border);
}
div.versionadded .versionmodified {
color: var(--versionadded);
}
div.versionchanged .versionmodified {
color: var(--versionchanged);
}
div.deprecated .versionmodified,
div.deprecated-removed .versionmodified,
div.versionremoved .versionmodified {
color: var(--deprecated);
}
/* Hide header when printing */
@media print {
div.mobile-nav {
display: none;
}
}

View File

@@ -0,0 +1,191 @@
/* Common colours */
:root {
--good-color: rgb(79 196 100);
--good-border: var(--good-color);
--middle-color: rgb(244, 227, 76);
--middle-border: var(--middle-color);
--bad-color: rgb(244, 76, 78);
--bad-border: var(--bad-color);
}
/* Browser elements */
:root {
scrollbar-color: #616161 transparent;
color-scheme: dark;
}
html,
body {
background-color: #222;
color: rgba(255, 255, 255, 0.87);
}
div.related {
color: rgba(255, 255, 255, 0.7); /* classic overwrite */
border-color: #424242;
}
/* SIDEBAR */
div.sphinxsidebar, .menu-wrapper {
background-color: #333;
color: inherit;
}
#sidebarbutton {
/* important to overwrite style attribute */
background-color: #555 !important;
color: inherit !important;
}
div.sidebar, aside.sidebar {
background-color: #424242;
border-color: #616161;
}
/* ANCHORS AND HIGHLIGHTS */
div.body a {
color: #7af;
}
div.body a:visited {
color: #09e;
}
a.headerlink:hover {
background-color: #424242;
}
div.related a {
color: currentColor;
}
div.footer,
div.footer a {
color: currentColor; /* classic overwrites */
}
dt:target,
span.highlighted {
background-color: #616161;
}
.footnote:target {
background-color: #2c3e50;
}
/* Below for most things in text */
dl.field-list > dt {
background-color: #434;
}
table.docutils td,
table.docutils th {
border-color: #616161 !important;
}
table.docutils th {
background-color: #424242;
}
.stableabi {
color: #bbf;
}
div.body pre {
border-color: #616161;
}
code {
background-color: #424242;
}
div.body div.seealso {
background-color: rgba(255, 255, 0, 0.1);
}
div.warning {
background-color: rgba(255, 0, 0, 0.2);
}
.warning code {
background-color: rgba(255, 0, 0, 0.5);
}
/* Admonitions */
:root {
--admonition-background: #ffffff1a;
--admonition-border: currentColor;
--admonition-color: #ffffffde;
--attention-background: #ffffff1a;
--attention-border: currentColor;
--caution-background: #ffff001a;
--caution-border: #dd6;
--danger-background: #f003;
--danger-border: #f66;
--error-background: #f003;
--error-border: #f66;
--hint-background: #0044117a;
--hint-border: green;
--seealso-background: #ffff001a;
--seealso-border: #dd6;
--tip-background: #0044117a;
--tip-border: green;
--warning-background: #ff000033;
--warning-border: #ff6666;
}
aside.topic,
div.topic,
div.note,
nav.contents {
background-color: rgba(255, 255, 255, 0.1);
border-color: currentColor;
}
.note code {
background-color: rgba(255, 255, 255, 0.1);
}
.mobile-nav {
box-shadow: rgba(255, 255, 255, 0.25) 0 0 2px 0;
}
.nav-content {
background-color: black;
}
img.invert-in-dark-mode {
filter: invert(1) hue-rotate(.5turn);
}
/* -- object description styles --------------------------------------------- */
/* C++ specific styling */
/* Override Sphinx's basic.css to fix colour contrast */
.sig.c .k, .sig.c .kt,
.sig.cpp .k, .sig.cpp .kt {
color: #5283ff;
}
/* Version change directives */
:root {
--versionadded: var(--good-color);
--versionchanged: var(--middle-color);
--deprecated: var(--bad-color);
}
.copybutton {
color: #ac9; /* follows div.body pre */
background-color: #222222; /* follows body */
}
.copybutton:hover {
background-color: #434343;
}
.copybutton:active {
background-color: #656565;
}

View File

@@ -0,0 +1,75 @@
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight { background: #f8f8f8; }
.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #F00 } /* Error */
.highlight .k { color: #008000; font-weight: bold } /* Keyword */
.highlight .o { color: #666 } /* Operator */
.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #9C6500 } /* Comment.Preproc */
.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #E40000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #008400 } /* Generic.Inserted */
.highlight .go { color: #717171 } /* Generic.Output */
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #04D } /* Generic.Traceback */
.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008000 } /* Keyword.Pseudo */
.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #B00040 } /* Keyword.Type */
.highlight .m { color: #666 } /* Literal.Number */
.highlight .s { color: #BA2121 } /* Literal.String */
.highlight .na { color: #687822 } /* Name.Attribute */
.highlight .nb { color: #008000 } /* Name.Builtin */
.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */
.highlight .no { color: #800 } /* Name.Constant */
.highlight .nd { color: #A2F } /* Name.Decorator */
.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #00F } /* Name.Function */
.highlight .nl { color: #767600 } /* Name.Label */
.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #19177C } /* Name.Variable */
.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */
.highlight .w { color: #BBB } /* Text.Whitespace */
.highlight .mb { color: #666 } /* Literal.Number.Bin */
.highlight .mf { color: #666 } /* Literal.Number.Float */
.highlight .mh { color: #666 } /* Literal.Number.Hex */
.highlight .mi { color: #666 } /* Literal.Number.Integer */
.highlight .mo { color: #666 } /* Literal.Number.Oct */
.highlight .sa { color: #BA2121 } /* Literal.String.Affix */
.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
.highlight .sc { color: #BA2121 } /* Literal.String.Char */
.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
.highlight .sx { color: #008000 } /* Literal.String.Other */
.highlight .sr { color: #A45A77 } /* Literal.String.Regex */
.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
.highlight .ss { color: #19177C } /* Literal.String.Symbol */
.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #00F } /* Name.Function.Magic */
.highlight .vc { color: #19177C } /* Name.Variable.Class */
.highlight .vg { color: #19177C } /* Name.Variable.Global */
.highlight .vi { color: #19177C } /* Name.Variable.Instance */
.highlight .vm { color: #19177C } /* Name.Variable.Magic */
.highlight .il { color: #666 } /* Literal.Number.Integer.Long */

View File

@@ -0,0 +1,85 @@
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #49483e }
.highlight { background: #272822; color: #F8F8F2 }
.highlight .c { color: #959077 } /* Comment */
.highlight .err { color: #ED007E; background-color: #1E0010 } /* Error */
.highlight .esc { color: #F8F8F2 } /* Escape */
.highlight .g { color: #F8F8F2 } /* Generic */
.highlight .k { color: #66D9EF } /* Keyword */
.highlight .l { color: #AE81FF } /* Literal */
.highlight .n { color: #F8F8F2 } /* Name */
.highlight .o { color: #FF4689 } /* Operator */
.highlight .x { color: #F8F8F2 } /* Other */
.highlight .p { color: #F8F8F2 } /* Punctuation */
.highlight .ch { color: #959077 } /* Comment.Hashbang */
.highlight .cm { color: #959077 } /* Comment.Multiline */
.highlight .cp { color: #959077 } /* Comment.Preproc */
.highlight .cpf { color: #959077 } /* Comment.PreprocFile */
.highlight .c1 { color: #959077 } /* Comment.Single */
.highlight .cs { color: #959077 } /* Comment.Special */
.highlight .gd { color: #FF4689 } /* Generic.Deleted */
.highlight .ge { color: #F8F8F2; font-style: italic } /* Generic.Emph */
.highlight .ges { color: #F8F8F2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #F8F8F2 } /* Generic.Error */
.highlight .gh { color: #F8F8F2 } /* Generic.Heading */
.highlight .gi { color: #A6E22E } /* Generic.Inserted */
.highlight .go { color: #66D9EF } /* Generic.Output */
.highlight .gp { color: #FF4689; font-weight: bold } /* Generic.Prompt */
.highlight .gs { color: #F8F8F2; font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #959077 } /* Generic.Subheading */
.highlight .gt { color: #F8F8F2 } /* Generic.Traceback */
.highlight .kc { color: #66D9EF } /* Keyword.Constant */
.highlight .kd { color: #66D9EF } /* Keyword.Declaration */
.highlight .kn { color: #FF4689 } /* Keyword.Namespace */
.highlight .kp { color: #66D9EF } /* Keyword.Pseudo */
.highlight .kr { color: #66D9EF } /* Keyword.Reserved */
.highlight .kt { color: #66D9EF } /* Keyword.Type */
.highlight .ld { color: #E6DB74 } /* Literal.Date */
.highlight .m { color: #AE81FF } /* Literal.Number */
.highlight .s { color: #E6DB74 } /* Literal.String */
.highlight .na { color: #A6E22E } /* Name.Attribute */
.highlight .nb { color: #F8F8F2 } /* Name.Builtin */
.highlight .nc { color: #A6E22E } /* Name.Class */
.highlight .no { color: #66D9EF } /* Name.Constant */
.highlight .nd { color: #A6E22E } /* Name.Decorator */
.highlight .ni { color: #F8F8F2 } /* Name.Entity */
.highlight .ne { color: #A6E22E } /* Name.Exception */
.highlight .nf { color: #A6E22E } /* Name.Function */
.highlight .nl { color: #F8F8F2 } /* Name.Label */
.highlight .nn { color: #F8F8F2 } /* Name.Namespace */
.highlight .nx { color: #A6E22E } /* Name.Other */
.highlight .py { color: #F8F8F2 } /* Name.Property */
.highlight .nt { color: #FF4689 } /* Name.Tag */
.highlight .nv { color: #F8F8F2 } /* Name.Variable */
.highlight .ow { color: #FF4689 } /* Operator.Word */
.highlight .pm { color: #F8F8F2 } /* Punctuation.Marker */
.highlight .w { color: #F8F8F2 } /* Text.Whitespace */
.highlight .mb { color: #AE81FF } /* Literal.Number.Bin */
.highlight .mf { color: #AE81FF } /* Literal.Number.Float */
.highlight .mh { color: #AE81FF } /* Literal.Number.Hex */
.highlight .mi { color: #AE81FF } /* Literal.Number.Integer */
.highlight .mo { color: #AE81FF } /* Literal.Number.Oct */
.highlight .sa { color: #E6DB74 } /* Literal.String.Affix */
.highlight .sb { color: #E6DB74 } /* Literal.String.Backtick */
.highlight .sc { color: #E6DB74 } /* Literal.String.Char */
.highlight .dl { color: #E6DB74 } /* Literal.String.Delimiter */
.highlight .sd { color: #E6DB74 } /* Literal.String.Doc */
.highlight .s2 { color: #E6DB74 } /* Literal.String.Double */
.highlight .se { color: #AE81FF } /* Literal.String.Escape */
.highlight .sh { color: #E6DB74 } /* Literal.String.Heredoc */
.highlight .si { color: #E6DB74 } /* Literal.String.Interpol */
.highlight .sx { color: #E6DB74 } /* Literal.String.Other */
.highlight .sr { color: #E6DB74 } /* Literal.String.Regex */
.highlight .s1 { color: #E6DB74 } /* Literal.String.Single */
.highlight .ss { color: #E6DB74 } /* Literal.String.Symbol */
.highlight .bp { color: #F8F8F2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #A6E22E } /* Name.Function.Magic */
.highlight .vc { color: #F8F8F2 } /* Name.Variable.Class */
.highlight .vg { color: #F8F8F2 } /* Name.Variable.Global */
.highlight .vi { color: #F8F8F2 } /* Name.Variable.Instance */
.highlight .vm { color: #F8F8F2 } /* Name.Variable.Magic */
.highlight .il { color: #AE81FF } /* Literal.Number.Integer.Long */

View File

@@ -0,0 +1,55 @@
function onSwitch(event) {
const option = event.target.selectedIndex;
const item = event.target.options[option];
window.location.href = item.dataset.url;
}
document.addEventListener("readthedocs-addons-data-ready", function(event) {
const config = event.detail.data()
const versionSelect = `
<select id="version_select" aria-label="Python version">
${ config.versions.active.map(
(version) => `
<option
value="${ version.slug }"
${ config.versions.current.slug === version.slug ? 'selected="selected"' : '' }
data-url="${ version.urls.documentation }">
${ version.slug }
</option>`
).join("\n") }
</select>
`;
// Prepend the current language to the options on the selector
let languages = config.projects.translations.concat(config.projects.current);
languages = languages.sort((a, b) => a.language.name.localeCompare(b.language.name));
const languageSelect = `
<select id="language_select" aria-label="Language">
${ languages.map(
(translation) => `
<option
value="${ translation.slug }"
${ config.projects.current.slug === translation.slug ? 'selected="selected"' : '' }
data-url="${ translation.urls.documentation }">
${ translation.language.name }
</option>`
).join("\n") }
</select>
`;
// Query all the placeholders because there are different ones for Desktop/Mobile
const versionPlaceholders = document.querySelectorAll(".version_switcher_placeholder");
for (placeholder of versionPlaceholders) {
placeholder.innerHTML = versionSelect;
let selectElement = placeholder.querySelector("select");
selectElement.addEventListener("change", onSwitch);
}
const languagePlaceholders = document.querySelectorAll(".language_switcher_placeholder");
for (placeholder of languagePlaceholders) {
placeholder.innerHTML = languageSelect;
let selectElement = placeholder.querySelector("select");
selectElement.addEventListener("change", onSwitch);
}
});

Some files were not shown because too many files have changed in this diff Show More