nonebot-plugin-weather-fighter 0.4.0__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.
- nonebot_plugin_weather_fighter/__init__.py +87 -0
- nonebot_plugin_weather_fighter/config.py +25 -0
- nonebot_plugin_weather_fighter/consts.py +127 -0
- nonebot_plugin_weather_fighter/game/__init__.py +1 -0
- nonebot_plugin_weather_fighter/game/amount.py +47 -0
- nonebot_plugin_weather_fighter/game/engine.py +1282 -0
- nonebot_plugin_weather_fighter/game/wxi.py +592 -0
- nonebot_plugin_weather_fighter/matcher.py +405 -0
- nonebot_plugin_weather_fighter/models.py +134 -0
- nonebot_plugin_weather_fighter/render/__init__.py +1 -0
- nonebot_plugin_weather_fighter/render/charts.py +407 -0
- nonebot_plugin_weather_fighter/render/renderer.py +39 -0
- nonebot_plugin_weather_fighter/render/static/echarts.min.js +45 -0
- nonebot_plugin_weather_fighter/render/static/fonts/OFL.txt +92 -0
- nonebot_plugin_weather_fighter/render/static/fonts/weather-fighter.woff2 +0 -0
- nonebot_plugin_weather_fighter/render/static/theme.css +136 -0
- nonebot_plugin_weather_fighter/render/templates/main_chart.html +61 -0
- nonebot_plugin_weather_fighter/resources/provinces.json +810 -0
- nonebot_plugin_weather_fighter/resources/regions.json +6810 -0
- nonebot_plugin_weather_fighter/services/__init__.py +1 -0
- nonebot_plugin_weather_fighter/services/cache.py +110 -0
- nonebot_plugin_weather_fighter/services/caiyun.py +40 -0
- nonebot_plugin_weather_fighter/services/coords.py +13 -0
- nonebot_plugin_weather_fighter/services/http.py +48 -0
- nonebot_plugin_weather_fighter/services/market_time.py +146 -0
- nonebot_plugin_weather_fighter/services/openmeteo.py +480 -0
- nonebot_plugin_weather_fighter/services/provinces.py +90 -0
- nonebot_plugin_weather_fighter/services/regions.py +118 -0
- nonebot_plugin_weather_fighter/services/store.py +839 -0
- nonebot_plugin_weather_fighter/services/weather.py +112 -0
- nonebot_plugin_weather_fighter-0.4.0.dist-info/METADATA +146 -0
- nonebot_plugin_weather_fighter-0.4.0.dist-info/RECORD +34 -0
- nonebot_plugin_weather_fighter-0.4.0.dist-info/WHEEL +4 -0
- nonebot_plugin_weather_fighter-0.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from nonebot import get_driver, get_plugin_config, logger, require
|
|
8
|
+
from nonebot.plugin import PluginMetadata
|
|
9
|
+
|
|
10
|
+
require("nonebot_plugin_localstore")
|
|
11
|
+
|
|
12
|
+
_driver = get_driver()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _playwright_cache_dir() -> str:
|
|
16
|
+
configured = os.environ.get("PLAYWRIGHT_BROWSERS_PATH")
|
|
17
|
+
if configured:
|
|
18
|
+
return configured
|
|
19
|
+
if platform.system() == "Windows":
|
|
20
|
+
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
21
|
+
return str(base / "ms-playwright")
|
|
22
|
+
if platform.system() == "Darwin":
|
|
23
|
+
return str(Path.home() / "Library" / "Caches" / "ms-playwright")
|
|
24
|
+
return str(Path.home() / ".cache" / "ms-playwright")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if getattr(_driver.config, "render", None) is None:
|
|
28
|
+
render_config: dict[str, object] = {"provider": "playwright"}
|
|
29
|
+
executable = os.environ.get("PLAYWRIGHT_CHROMIUM_EXECUTABLE")
|
|
30
|
+
if executable:
|
|
31
|
+
render_config["provider_config"] = {
|
|
32
|
+
"executable_path": executable,
|
|
33
|
+
"launch_args": os.environ.get(
|
|
34
|
+
"PLAYWRIGHT_CHROMIUM_ARGS", "--no-sandbox --disable-crashpad"
|
|
35
|
+
),
|
|
36
|
+
}
|
|
37
|
+
else:
|
|
38
|
+
render_config["provider_config"] = {"storage_path": _playwright_cache_dir()}
|
|
39
|
+
_driver.config.render = render_config # type: ignore[attr-defined]
|
|
40
|
+
|
|
41
|
+
require("nonebot_plugin_htmlrender")
|
|
42
|
+
|
|
43
|
+
from . import consts # noqa: E402
|
|
44
|
+
from . import matcher as matcher # noqa: E402
|
|
45
|
+
from .config import Config # noqa: E402
|
|
46
|
+
from .game.engine import get_engine # noqa: E402
|
|
47
|
+
from .services.http import close_http # noqa: E402
|
|
48
|
+
from .services.openmeteo import OpenMeteoConfig, configure_client # noqa: E402
|
|
49
|
+
from .services.provinces import resolve_province # noqa: E402
|
|
50
|
+
from .services.store import close_store, init_store # noqa: E402
|
|
51
|
+
|
|
52
|
+
__plugin_meta__ = PluginMetadata(
|
|
53
|
+
name="天气战士",
|
|
54
|
+
description="老实看天气,很枯燥吧",
|
|
55
|
+
usage=consts.HELP_TEXT,
|
|
56
|
+
type="application",
|
|
57
|
+
config=Config,
|
|
58
|
+
homepage="https://github.com/qwq12738qwq/nonebot-plugin-beatsaberscore",
|
|
59
|
+
supported_adapters={"~onebot.v11"},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@_driver.on_startup
|
|
64
|
+
async def _startup() -> None:
|
|
65
|
+
config = get_plugin_config(Config)
|
|
66
|
+
configure_client(
|
|
67
|
+
OpenMeteoConfig(
|
|
68
|
+
bucket_seconds=config.weather_fighter_bucket_bars,
|
|
69
|
+
stale_seconds=config.weather_fighter_stale_seconds,
|
|
70
|
+
timeout=config.weather_fighter_http_timeout,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
await init_store()
|
|
74
|
+
province = resolve_province(config.weather_fighter_default_province)
|
|
75
|
+
if province is None:
|
|
76
|
+
logger.warning("默认省份 {} 无效,跳过启动开盘", config.weather_fighter_default_province)
|
|
77
|
+
return
|
|
78
|
+
try:
|
|
79
|
+
await get_engine().open_board(province)
|
|
80
|
+
except Exception as exc: # noqa: BLE001 - 启动开盘失败时等首次命令重试
|
|
81
|
+
logger.opt(exception=exc).warning("启动开盘失败,首次命令时会重试")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@_driver.on_shutdown
|
|
85
|
+
async def _shutdown() -> None:
|
|
86
|
+
await close_store()
|
|
87
|
+
await close_http()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Config(BaseModel):
|
|
7
|
+
"""插件配置;环境变量前缀见 .env.example。"""
|
|
8
|
+
|
|
9
|
+
weather_fighter_bucket_bars: int = Field(default=1800, ge=60)
|
|
10
|
+
weather_fighter_bucket_realtime: int = Field(default=300, ge=30)
|
|
11
|
+
weather_fighter_bucket_hourly: int = Field(default=3600, ge=60)
|
|
12
|
+
weather_fighter_bucket_daily: int = Field(default=10800, ge=300)
|
|
13
|
+
weather_fighter_stale_seconds: int = Field(default=6 * 3600, ge=60)
|
|
14
|
+
weather_fighter_http_timeout: float = Field(default=30.0, gt=0)
|
|
15
|
+
weather_fighter_preload_season_series: bool = True
|
|
16
|
+
weather_fighter_caiyun_token: str = ""
|
|
17
|
+
weather_fighter_command_cooldown: float = Field(default=5.0, ge=0)
|
|
18
|
+
weather_fighter_principal: float = Field(default=10_000.0, ge=1_000)
|
|
19
|
+
weather_fighter_leverage: float = Field(default=100.0, ge=1, le=100)
|
|
20
|
+
weather_fighter_safety_entry_pct: float = Field(default=20.0, ge=1, le=100)
|
|
21
|
+
weather_fighter_safety_grant_pct: float = Field(default=5.0, gt=0, le=100)
|
|
22
|
+
weather_fighter_safety_target_pct: float = Field(default=35.0, gt=0, le=100)
|
|
23
|
+
weather_fighter_chart_bars: int = Field(default=64, ge=8, le=1000)
|
|
24
|
+
weather_fighter_enable_region: bool = True
|
|
25
|
+
weather_fighter_default_province: str = "河北"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
PLUGIN_NAME = "weather_fighter"
|
|
4
|
+
PLUGIN_VERSION = "0.4.0"
|
|
5
|
+
|
|
6
|
+
CMD_WEATHER = "天气"
|
|
7
|
+
CMD_LONG = "买入"
|
|
8
|
+
CMD_SHORT = "卖出"
|
|
9
|
+
CMD_CLOSE = "平仓"
|
|
10
|
+
CMD_ORDER = "到价"
|
|
11
|
+
CMD_CANCEL = "撤单"
|
|
12
|
+
CMD_LEVERAGE = "倍数"
|
|
13
|
+
CMD_HELP = "帮助"
|
|
14
|
+
|
|
15
|
+
DB_FILE = "game.db"
|
|
16
|
+
PROVINCES_FILE = "provinces.json"
|
|
17
|
+
REGIONS_FILE = "regions.json"
|
|
18
|
+
|
|
19
|
+
BASE_PRICE = 1000.0
|
|
20
|
+
BARS_PER_DAY = 16
|
|
21
|
+
OPENMETEO_PAST_DAYS = 92
|
|
22
|
+
|
|
23
|
+
WEATHER_NAMES = {
|
|
24
|
+
0: "晴",
|
|
25
|
+
1: "基本晴",
|
|
26
|
+
2: "多云",
|
|
27
|
+
3: "阴",
|
|
28
|
+
45: "雾",
|
|
29
|
+
48: "雾凇",
|
|
30
|
+
51: "毛毛雨",
|
|
31
|
+
53: "毛毛雨",
|
|
32
|
+
55: "毛毛雨",
|
|
33
|
+
56: "冻毛雨",
|
|
34
|
+
57: "冻毛雨",
|
|
35
|
+
61: "小雨",
|
|
36
|
+
63: "中雨",
|
|
37
|
+
65: "大雨",
|
|
38
|
+
66: "冻雨",
|
|
39
|
+
67: "冻雨",
|
|
40
|
+
71: "小雪",
|
|
41
|
+
73: "中雪",
|
|
42
|
+
75: "大雪",
|
|
43
|
+
77: "米雪",
|
|
44
|
+
80: "阵雨",
|
|
45
|
+
81: "阵雨",
|
|
46
|
+
82: "强阵雨",
|
|
47
|
+
85: "阵雪",
|
|
48
|
+
86: "强阵雪",
|
|
49
|
+
95: "雷暴",
|
|
50
|
+
96: "雷暴夹雹",
|
|
51
|
+
99: "强雷暴",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
WEATHER_ICONS = {
|
|
55
|
+
0: "☀",
|
|
56
|
+
1: "🌤",
|
|
57
|
+
2: "⛅",
|
|
58
|
+
3: "☁",
|
|
59
|
+
45: "🌫",
|
|
60
|
+
48: "🌫",
|
|
61
|
+
51: "🌦",
|
|
62
|
+
53: "🌦",
|
|
63
|
+
55: "🌦",
|
|
64
|
+
56: "🌧",
|
|
65
|
+
57: "🌧",
|
|
66
|
+
61: "🌧",
|
|
67
|
+
63: "🌧",
|
|
68
|
+
65: "🌧",
|
|
69
|
+
66: "🌧",
|
|
70
|
+
67: "🌧",
|
|
71
|
+
71: "🌨",
|
|
72
|
+
73: "🌨",
|
|
73
|
+
75: "❄",
|
|
74
|
+
77: "🌨",
|
|
75
|
+
80: "🌦",
|
|
76
|
+
81: "🌧",
|
|
77
|
+
82: "⛈",
|
|
78
|
+
85: "🌨",
|
|
79
|
+
86: "❄",
|
|
80
|
+
95: "⛈",
|
|
81
|
+
96: "⛈",
|
|
82
|
+
99: "⛈",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_HELP_TEXT = """天气战士 · 所有群共用一个盘
|
|
86
|
+
|
|
87
|
+
主图 + 天气(天气 / 行情 / 大盘 / K线 同一命令,不用开盘):
|
|
88
|
+
天气 [省份] / 天气 [市级地] —— 主图 + 现在天气 + 今日高低温
|
|
89
|
+
|
|
90
|
+
交易(按真实时间自动运行,不需要开盘):
|
|
91
|
+
买入 [省份] 1000 / 卖出 河北 1万 —— 顾名思义
|
|
92
|
+
买入 [省份] 1000 杠杆 5 —— 下单时指定倍数
|
|
93
|
+
卖出 [省份] —— 不写金额默认全卖出
|
|
94
|
+
金额:1000 / 1.5万 / 20% / 全;不填默认 20%
|
|
95
|
+
到价 [省份] 16.5 买入 1000 —— 价格碰到 16.5℃ 自动买入
|
|
96
|
+
到价 [省份] 16.5 卖出 1000 —— 价格碰到 16.5℃ 自动卖出
|
|
97
|
+
到价 [省份] 18.0 平 —— 价格碰到 18.0℃ 自动平掉河北
|
|
98
|
+
平仓 [省] —— 平掉该省;不带省则平掉全部持仓
|
|
99
|
+
撤单 —— 取消自己的到价单
|
|
100
|
+
倍数 10 —— 设置杠杆倍数(1~上限,有持仓时不能改)
|
|
101
|
+
|
|
102
|
+
规则:
|
|
103
|
+
· 能吃保底
|
|
104
|
+
· 每个玩家自己的余额、持仓、到价单互相独立。
|
|
105
|
+
· 倍数默认 1 倍,可下单时带「杠杆 5」或先用「倍数 5」设置;
|
|
106
|
+
· 按照 A 股时间:09:30-11:30、13:00-15:00;午休和周末停盘。
|
|
107
|
+
· 默认余额 10,000。低于 20%(2,000)时进入保底:每个真实自然日补 5%(500),
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
HELP_TEXT = _HELP_TEXT
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def weather_name(code: int) -> str:
|
|
114
|
+
return WEATHER_NAMES.get(code, f"码 {code}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def weather_icon(code: int) -> str:
|
|
118
|
+
return WEATHER_ICONS.get(code, "🌡")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def wind_level(gust: float) -> int:
|
|
122
|
+
"""把阵风 m/s 粗换成风力等级,给玩家一个直观数字。"""
|
|
123
|
+
thresholds = (0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8, 24.5, 28.5, 32.7)
|
|
124
|
+
for level, limit in enumerate(thresholds):
|
|
125
|
+
if gust < limit:
|
|
126
|
+
return level
|
|
127
|
+
return 12
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""游戏引擎(会话推进、结算)在 M1 接入。"""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
UNITS: tuple[tuple[str, float], ...] = (
|
|
4
|
+
("亿", 100_000_000.0),
|
|
5
|
+
("万", 10_000.0),
|
|
6
|
+
("w", 10_000.0),
|
|
7
|
+
("W", 10_000.0),
|
|
8
|
+
("千", 1_000.0),
|
|
9
|
+
("k", 1_000.0),
|
|
10
|
+
("K", 1_000.0),
|
|
11
|
+
)
|
|
12
|
+
ALL_IN = {"全", "全仓", "梭哈", "all", "ALL"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AmountError(ValueError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_amount(text: str | None, cash: float, *, default_pct: float = 20.0) -> float:
|
|
20
|
+
"""把“1000 / 1.5万 / 30% / 全”解析成投入金额,并校验不超过余额。"""
|
|
21
|
+
raw = (text or "").strip().replace(",", "").replace(",", "").replace("元", "")
|
|
22
|
+
if not raw:
|
|
23
|
+
amount = cash * default_pct / 100.0
|
|
24
|
+
elif raw in ALL_IN:
|
|
25
|
+
amount = cash
|
|
26
|
+
elif raw.endswith("%"):
|
|
27
|
+
try:
|
|
28
|
+
percent = float(raw[:-1])
|
|
29
|
+
except ValueError as exc:
|
|
30
|
+
raise AmountError("金额格式不对,例如:1000 / 1万 / 30% / 全") from exc
|
|
31
|
+
amount = cash * percent / 100.0
|
|
32
|
+
else:
|
|
33
|
+
scale = 1.0
|
|
34
|
+
for unit, multiplier in UNITS:
|
|
35
|
+
if raw.endswith(unit):
|
|
36
|
+
raw = raw[: -len(unit)]
|
|
37
|
+
scale = multiplier
|
|
38
|
+
break
|
|
39
|
+
try:
|
|
40
|
+
amount = float(raw) * scale
|
|
41
|
+
except ValueError as exc:
|
|
42
|
+
raise AmountError("金额格式不对,例如:1000 / 1万 / 30% / 全") from exc
|
|
43
|
+
if amount <= 0:
|
|
44
|
+
raise AmountError("还有反向消费?")
|
|
45
|
+
if amount > cash + 1e-6:
|
|
46
|
+
raise AmountError(f"穷了,只有 {cash:,.0f}")
|
|
47
|
+
return amount
|