pymecli 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api/__init__.py +0 -0
- api/v1/__init__.py +9 -0
- api/v1/clash.py +34 -0
- api/v1/redis.py +26 -0
- cli/__init__.py +0 -0
- cli/bitget.py +55 -0
- cli/example.py +32 -0
- cli/fast.py +169 -0
- cli/gate.py +16 -0
- cli/util.py +112 -0
- core/__init__.py +0 -0
- core/clash.py +267 -0
- core/config.py +34 -0
- core/redis_client.py +137 -0
- crypto/__init__.py +0 -0
- crypto/bitget.py +124 -0
- crypto/gate.py +35 -0
- data/__init__.py +0 -0
- data/dou_dict.py +172 -0
- data/dou_list.py +93 -0
- data/main.py +5 -0
- data/template.yaml +150 -0
- models/__init__.py +0 -0
- models/douzero_model.py +45 -0
- models/ocr_model.py +57 -0
- models/response.py +37 -0
- pymecli-0.2.5.dist-info/METADATA +45 -0
- pymecli-0.2.5.dist-info/RECORD +39 -0
- pymecli-0.2.5.dist-info/WHEEL +4 -0
- pymecli-0.2.5.dist-info/entry_points.txt +6 -0
- utils/__init__.py +0 -0
- utils/elapsed.py +18 -0
- utils/helper.py +9 -0
- utils/logger.py +26 -0
- utils/mysql.py +79 -0
- utils/pd.py +20 -0
- utils/sleep.py +16 -0
- utils/text.py +33 -0
- utils/toml.py +6 -0
api/__init__.py
ADDED
|
File without changes
|
api/v1/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
|
|
3
|
+
from api.v1.clash import router as clash_router
|
|
4
|
+
from api.v1.redis import router as redis_router
|
|
5
|
+
|
|
6
|
+
api_router = APIRouter()
|
|
7
|
+
|
|
8
|
+
api_router.include_router(clash_router, prefix="/clash")
|
|
9
|
+
api_router.include_router(redis_router, prefix="/redis")
|
api/v1/clash.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
from fastapi import APIRouter, Query, Response
|
|
3
|
+
|
|
4
|
+
from core.clash import ClashYamlGenerator, get_generator_dependency
|
|
5
|
+
|
|
6
|
+
router = APIRouter()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@router.get(
|
|
10
|
+
"/sub",
|
|
11
|
+
summary="转换订阅(可以转换多个订阅)",
|
|
12
|
+
description="根据提供的URL、User-Agent和名称获取并处理订阅信息,返回YAML格式的Clash配置",
|
|
13
|
+
response_description="返回YAML格式的Clash配置文件",
|
|
14
|
+
)
|
|
15
|
+
async def sub(
|
|
16
|
+
urls: str = Query(..., description="订阅URL,逗号分隔"),
|
|
17
|
+
agents: str = Query(
|
|
18
|
+
"clash-verge/v2.4.3",
|
|
19
|
+
description="User-Agent,逗号分隔(根据客户段选择)",
|
|
20
|
+
),
|
|
21
|
+
names: str = Query(
|
|
22
|
+
"订阅1", description="订阅名称,逗号分隔(可选,在客户端中的显示名)"
|
|
23
|
+
),
|
|
24
|
+
generator: ClashYamlGenerator = get_generator_dependency(),
|
|
25
|
+
):
|
|
26
|
+
# 将JSON字符串解析为Python对象
|
|
27
|
+
sub_list = generator.query2sub(urls, agents, names)
|
|
28
|
+
yaml_content, userinfo = generator.gen(sub_list)
|
|
29
|
+
yaml_string = yaml.dump(yaml_content, allow_unicode=True, default_flow_style=False)
|
|
30
|
+
return Response(
|
|
31
|
+
headers={"Subscription-Userinfo": userinfo},
|
|
32
|
+
content=yaml_string,
|
|
33
|
+
media_type="text/yaml",
|
|
34
|
+
)
|
api/v1/redis.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException, Query, Request
|
|
4
|
+
|
|
5
|
+
from models.response import SuccessResponse
|
|
6
|
+
|
|
7
|
+
router = APIRouter()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@router.get(
|
|
11
|
+
"/get",
|
|
12
|
+
summary="根据redis.key获取数据",
|
|
13
|
+
response_description="返回json数据",
|
|
14
|
+
)
|
|
15
|
+
async def redis(
|
|
16
|
+
request: Request,
|
|
17
|
+
key: str = Query(..., description="redis.key"),
|
|
18
|
+
):
|
|
19
|
+
value = await request.app.state.redis_client.get(key)
|
|
20
|
+
|
|
21
|
+
if value is None:
|
|
22
|
+
raise HTTPException(status_code=404, detail=f"Key '{key}' not found in Redis")
|
|
23
|
+
|
|
24
|
+
json_data = json.loads(value)
|
|
25
|
+
|
|
26
|
+
return SuccessResponse(data=json_data)
|
cli/__init__.py
ADDED
|
File without changes
|
cli/bitget.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from crypto.bitget import (
|
|
4
|
+
bitget_sf_close,
|
|
5
|
+
bitget_sf_open,
|
|
6
|
+
grid_close,
|
|
7
|
+
grid_open,
|
|
8
|
+
mix_tickers,
|
|
9
|
+
spot_tickers,
|
|
10
|
+
)
|
|
11
|
+
from utils.mysql import get_database_engine
|
|
12
|
+
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
def sync(env_path: str = "d:/.env"):
|
|
18
|
+
"""同步mysql中grid数据到csv文件"""
|
|
19
|
+
engine = get_database_engine(env_path)
|
|
20
|
+
grid_open(engine, "d:/github/meme2046/data/bitget_0.csv")
|
|
21
|
+
grid_close(engine, "d:/github/meme2046/data/bitget_0.csv")
|
|
22
|
+
bitget_sf_open(engine, "d:/github/meme2046/data/bitget_sf_0.csv")
|
|
23
|
+
bitget_sf_close(engine, "d:/github/meme2046/data/bitget_sf_0.csv")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.command()
|
|
27
|
+
def spot(
|
|
28
|
+
symbols: str,
|
|
29
|
+
proxy: str = typer.Option(
|
|
30
|
+
None, "--proxy", "-p", help="代理服务器地址,例如: http://127.0.0.1:7890"
|
|
31
|
+
),
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
从bitget获取加密货币现货价格.
|
|
35
|
+
|
|
36
|
+
参数:
|
|
37
|
+
symbols:加密货币符号,可以是多个,用逗号分隔,例如:"BTCUSDT,ETHUSDT"
|
|
38
|
+
"""
|
|
39
|
+
spot_tickers(symbols.split(","), proxy)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
def mix(
|
|
44
|
+
symbols: str,
|
|
45
|
+
proxy: str = typer.Option(
|
|
46
|
+
None, "--proxy", "-p", help="代理服务器地址,例如: http://127.0.0.1:7890"
|
|
47
|
+
),
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
从bitget获取加密货币合约价格.
|
|
51
|
+
|
|
52
|
+
参数:
|
|
53
|
+
symbols:加密货币符号,可以是多个,用逗号分隔,例如:"BTCUSDT,ETHUSDT"
|
|
54
|
+
"""
|
|
55
|
+
mix_tickers(symbols.split(","), proxy)
|
cli/example.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
app = typer.Typer()
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@app.command()
|
|
7
|
+
def hello(
|
|
8
|
+
name: str = typer.Argument(
|
|
9
|
+
"from My CLI!",
|
|
10
|
+
help="Name of the person to greet",
|
|
11
|
+
),
|
|
12
|
+
):
|
|
13
|
+
print(f"Hello {name}!")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
def goodbye(
|
|
18
|
+
name: str = typer.Argument(
|
|
19
|
+
"from My CLI!",
|
|
20
|
+
help="Name of the person to goodbye",
|
|
21
|
+
),
|
|
22
|
+
formal: bool = typer.Option(
|
|
23
|
+
False,
|
|
24
|
+
"--formal",
|
|
25
|
+
"-f",
|
|
26
|
+
help="是否为正式场合回答",
|
|
27
|
+
),
|
|
28
|
+
):
|
|
29
|
+
if formal:
|
|
30
|
+
print(f"Goodbye Ms. {name}. Have a good day.")
|
|
31
|
+
else:
|
|
32
|
+
print(f"Bye {name}!")
|
cli/fast.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
|
|
4
|
+
import redis.asyncio as redis
|
|
5
|
+
import typer
|
|
6
|
+
import uvicorn
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from fastapi.exceptions import RequestValidationError
|
|
9
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
10
|
+
from fastapi.responses import JSONResponse, PlainTextResponse
|
|
11
|
+
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
12
|
+
|
|
13
|
+
from api.v1 import api_router
|
|
14
|
+
from core.clash import ClashConfig, init_generator
|
|
15
|
+
from core.config import settings
|
|
16
|
+
from models.response import SuccessResponse
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@asynccontextmanager
|
|
20
|
+
async def lifespan(app: FastAPI):
|
|
21
|
+
"""管理应用生命周期的上下文管理器"""
|
|
22
|
+
redis_pool = redis.ConnectionPool(
|
|
23
|
+
host=os.getenv("REDIS_HOST", "192.168.123.7"),
|
|
24
|
+
port=int(os.getenv("REDIS_PORT", 6379)),
|
|
25
|
+
db=int(os.getenv("REDIS_DB", 0)),
|
|
26
|
+
password=os.getenv("REDIS_PASSWORD"),
|
|
27
|
+
max_connections=20, # 根据需要调整最大连接数
|
|
28
|
+
decode_responses=True,
|
|
29
|
+
)
|
|
30
|
+
redis_client = redis.Redis(connection_pool=redis_pool)
|
|
31
|
+
|
|
32
|
+
# 启动时
|
|
33
|
+
app.state.redis_client = redis_client
|
|
34
|
+
yield
|
|
35
|
+
# 关闭时
|
|
36
|
+
if redis_client:
|
|
37
|
+
await redis_client.aclose()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
typer_app = typer.Typer()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
app = FastAPI(
|
|
44
|
+
title=settings.NAME,
|
|
45
|
+
description=settings.DESCRIPTION,
|
|
46
|
+
version=settings.VERSION,
|
|
47
|
+
lifespan=lifespan,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# 注册 v1 版本的所有路由
|
|
51
|
+
app.include_router(api_router, prefix=settings.API_V1_STR)
|
|
52
|
+
app.add_middleware(
|
|
53
|
+
CORSMiddleware,
|
|
54
|
+
allow_origins=["*"], # 允许所有源,生产环境建议设置具体的源
|
|
55
|
+
allow_credentials=True,
|
|
56
|
+
allow_methods=["*"], # 允许所有 HTTP 方法
|
|
57
|
+
allow_headers=["*"], # 允许所有 headers
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.exception_handler(StarletteHTTPException)
|
|
62
|
+
async def http_exception_handler(request, exc):
|
|
63
|
+
return JSONResponse(
|
|
64
|
+
status_code=exc.status_code,
|
|
65
|
+
content={"error": exc.detail, "success": False, "data": None},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.exception_handler(RequestValidationError)
|
|
70
|
+
async def validation_exception_handler(request, exc):
|
|
71
|
+
# 将错误信息转换为可序列化的格式
|
|
72
|
+
errors = []
|
|
73
|
+
for error in exc.errors():
|
|
74
|
+
errors.append(
|
|
75
|
+
{
|
|
76
|
+
"type": error.get("type"),
|
|
77
|
+
"loc": error.get("loc"),
|
|
78
|
+
"msg": error.get("msg"),
|
|
79
|
+
"input": error.get("input"),
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return JSONResponse(
|
|
84
|
+
status_code=422,
|
|
85
|
+
content={
|
|
86
|
+
"error": errors,
|
|
87
|
+
"success": False,
|
|
88
|
+
"data": None,
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.exception_handler(Exception)
|
|
94
|
+
async def general_exception_handler(request, exc):
|
|
95
|
+
return JSONResponse(
|
|
96
|
+
status_code=500,
|
|
97
|
+
content={
|
|
98
|
+
"error": "Internal server error",
|
|
99
|
+
"success": False,
|
|
100
|
+
"data": None,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.get("/")
|
|
106
|
+
async def root():
|
|
107
|
+
return SuccessResponse(data=f"Welcome to {settings.DESCRIPTION}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.get("/ping", response_class=PlainTextResponse)
|
|
111
|
+
async def pingpong():
|
|
112
|
+
return "pong"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@typer_app.command()
|
|
116
|
+
def run_app(
|
|
117
|
+
host: str = typer.Argument(
|
|
118
|
+
"0.0.0.0",
|
|
119
|
+
help="fastapi监听的<ip>地址",
|
|
120
|
+
),
|
|
121
|
+
port: int = typer.Option(
|
|
122
|
+
80,
|
|
123
|
+
"--port",
|
|
124
|
+
help="fastapi监听的端口号",
|
|
125
|
+
),
|
|
126
|
+
ssl_keyfile: str = typer.Option(
|
|
127
|
+
None,
|
|
128
|
+
"--ssl-keyfile",
|
|
129
|
+
"-sk",
|
|
130
|
+
help="ssl keyfile",
|
|
131
|
+
),
|
|
132
|
+
ssl_certfile: str = typer.Option(
|
|
133
|
+
None,
|
|
134
|
+
"--ssl-certfile",
|
|
135
|
+
"-sc",
|
|
136
|
+
help="ssl certfile",
|
|
137
|
+
),
|
|
138
|
+
rule: str = typer.Option(
|
|
139
|
+
"https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release",
|
|
140
|
+
"--rule",
|
|
141
|
+
"-r",
|
|
142
|
+
help="clash Rule base URL",
|
|
143
|
+
),
|
|
144
|
+
my_rule: str = typer.Option(
|
|
145
|
+
"https://raw.githubusercontent.com/meme2046/data/refs/heads/main/clash",
|
|
146
|
+
"--my-rule",
|
|
147
|
+
"-mr",
|
|
148
|
+
help="my clash rule base URL(自定义规则)",
|
|
149
|
+
),
|
|
150
|
+
proxy: str = typer.Option(
|
|
151
|
+
None,
|
|
152
|
+
"--proxy",
|
|
153
|
+
"-p",
|
|
154
|
+
help="服务器代理,传入则通过代理转换Clash订阅,比如:socks5://127.0.0.1:7890",
|
|
155
|
+
),
|
|
156
|
+
):
|
|
157
|
+
settings.reload()
|
|
158
|
+
|
|
159
|
+
clash_config = ClashConfig(rule, my_rule, proxy)
|
|
160
|
+
init_generator(clash_config)
|
|
161
|
+
|
|
162
|
+
uvicorn.run(
|
|
163
|
+
"cli.fast:app",
|
|
164
|
+
host=host,
|
|
165
|
+
port=port,
|
|
166
|
+
reload=False,
|
|
167
|
+
ssl_keyfile=ssl_keyfile,
|
|
168
|
+
ssl_certfile=ssl_certfile,
|
|
169
|
+
)
|
cli/gate.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from crypto.gate import gate_close, gate_open
|
|
4
|
+
from utils.mysql import get_database_engine
|
|
5
|
+
|
|
6
|
+
app = typer.Typer()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command()
|
|
10
|
+
def sync(
|
|
11
|
+
env_path: str = "d:/.env", csv_path: str = "d:/github/meme2046/data/gate_0.csv"
|
|
12
|
+
):
|
|
13
|
+
"""同步mysql中grid数据到csv文件"""
|
|
14
|
+
engine = get_database_engine(env_path)
|
|
15
|
+
gate_open(engine, csv_path)
|
|
16
|
+
gate_close(engine, csv_path)
|
cli/util.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import secrets
|
|
3
|
+
import string
|
|
4
|
+
import sys
|
|
5
|
+
import uuid as u
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
import arrow
|
|
9
|
+
import pytz
|
|
10
|
+
import typer
|
|
11
|
+
from babel.dates import format_datetime
|
|
12
|
+
from cowsay.__main__ import cli
|
|
13
|
+
|
|
14
|
+
app = typer.Typer()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command()
|
|
18
|
+
def sid(
|
|
19
|
+
length: int = typer.Argument(30, help="生成secure_id的长度"),
|
|
20
|
+
):
|
|
21
|
+
chars = string.ascii_letters + string.digits
|
|
22
|
+
id = "".join(secrets.choice(chars) for _ in range(length))
|
|
23
|
+
print(id)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.command()
|
|
27
|
+
def uuid():
|
|
28
|
+
print(u.uuid4())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def os():
|
|
33
|
+
print(platform.system())
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.command()
|
|
37
|
+
def ts():
|
|
38
|
+
timestamp = arrow.now().timestamp()
|
|
39
|
+
print(int(timestamp))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
def ms():
|
|
44
|
+
timestamp = arrow.now().timestamp()
|
|
45
|
+
print(int(timestamp * 1000))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@app.command()
|
|
49
|
+
def v():
|
|
50
|
+
print(f"🧊 python:{sys.version}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def strf_time(zone: str):
|
|
54
|
+
tz = pytz.timezone(zone)
|
|
55
|
+
now = datetime.now(tz)
|
|
56
|
+
# locale="zh_CN" 会使月份和星期的名称显示为中文
|
|
57
|
+
# locale="en_US" 则会显示为英文
|
|
58
|
+
return format_datetime(
|
|
59
|
+
now, "yyyy年MM月dd日 HH:mm:ss EEEE ZZZZ zzzz", locale="zh_CN"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command()
|
|
64
|
+
def st():
|
|
65
|
+
t0 = strf_time("UTC")
|
|
66
|
+
t1 = strf_time("America/New_York")
|
|
67
|
+
t2 = strf_time("Asia/Shanghai")
|
|
68
|
+
|
|
69
|
+
print(t0)
|
|
70
|
+
print(t1)
|
|
71
|
+
print(t2)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command()
|
|
75
|
+
def stoken(
|
|
76
|
+
a: str = typer.Argument(..., help="第一个Token地址"),
|
|
77
|
+
b: str = typer.Argument(..., help="第二个Token地址"),
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
返回按照 Uniswap 规则排序后的 token0 和 token1
|
|
81
|
+
"""
|
|
82
|
+
# 移除可能存在的 "0x" 前缀
|
|
83
|
+
addr_a = a.lower().replace("0x", "")
|
|
84
|
+
addr_b = b.lower().replace("0x", "")
|
|
85
|
+
|
|
86
|
+
# 验证地址格式是否正确
|
|
87
|
+
if len(addr_a) != 40 or len(addr_b) != 40:
|
|
88
|
+
print("错误: 地址必须是40位十六进制字符串")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# 按照Uniswap的方式进行比较(基于数值大小而不是字符串排序)
|
|
92
|
+
if int(addr_a, 16) < int(addr_b, 16):
|
|
93
|
+
token0, token1 = a, b
|
|
94
|
+
else:
|
|
95
|
+
token0, token1 = b, a
|
|
96
|
+
|
|
97
|
+
print(token0, token1)
|
|
98
|
+
return token0, token1
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def say():
|
|
102
|
+
cli()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
107
|
+
weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
108
|
+
usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
|
109
|
+
xaut = "0x68749665ff8d2d112fa859aa293f07a622782f38"
|
|
110
|
+
uni = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"
|
|
111
|
+
aave = "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9"
|
|
112
|
+
stoken(usdt, xaut)
|
core/__init__.py
ADDED
|
File without changes
|