mirror of
http://shenjack.top:5100/shenjack/icalingua-python-bot.git
synced 2025-04-20 15:39:53 +08:00
Compare commits
No commits in common. "a3ed392179701e2933e2e51c8163e719e897c00c" and "d246913b4cbd3fae2e1acc4a45004b3ab108c1dc" have entirely different histories.
a3ed392179
...
d246913b4c
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -640,7 +640,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ica-rs"
|
name = "ica-rs"
|
||||||
version = "0.4.12"
|
version = "0.4.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"colored",
|
"colored",
|
||||||
|
|
|
@ -15,4 +15,3 @@ filter_list = [0]
|
||||||
|
|
||||||
# python 插件路径
|
# python 插件路径
|
||||||
py_plugin_path = "/path/to/your/plugin"
|
py_plugin_path = "/path/to/your/plugin"
|
||||||
py_config_path = "/path/to/your/config"
|
|
||||||
|
|
1
ica-rs/.gitignore
vendored
1
ica-rs/.gitignore
vendored
|
@ -1,3 +1,2 @@
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
config/
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ica-rs"
|
name = "ica-rs"
|
||||||
version = "0.4.12"
|
version = "0.4.11"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -19,7 +19,7 @@ colored = "2.1.0"
|
||||||
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
pyo3 = "0.20.3"
|
pyo3 = "0.20.2"
|
||||||
# pyo3-async = "0.3.2"
|
# pyo3-async = "0.3.2"
|
||||||
pyo3-asyncio = { version = "0.20.0", features = ["attributes", "tokio-runtime"] }
|
pyo3-asyncio = { version = "0.20.0", features = ["attributes", "tokio-runtime"] }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Python 兼容版本 3.8+
|
# Python 兼容版本 3.8+
|
||||||
|
|
||||||
from typing import Callable, Tuple
|
from typing import Callable
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pub type RoomId = i64;
|
pub type RoomId = i64;
|
||||||
|
@ -105,19 +105,12 @@ class IcaClient:
|
||||||
"""
|
"""
|
||||||
def send_message(self, message: SendMessage) -> bool:
|
def send_message(self, message: SendMessage) -> bool:
|
||||||
...
|
...
|
||||||
def send_and_warn(self, message: SendMessage) -> bool:
|
|
||||||
"""发送消息, 并在日志中输出警告信息"""
|
|
||||||
self.warn(message.content)
|
|
||||||
return self.send_message(message)
|
|
||||||
def delete_message(self, message: DeleteMessage) -> bool:
|
def delete_message(self, message: DeleteMessage) -> bool:
|
||||||
...
|
...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status() -> IcaStatus:
|
def status() -> IcaStatus:
|
||||||
...
|
...
|
||||||
@property
|
|
||||||
def version() -> str:
|
|
||||||
...
|
|
||||||
|
|
||||||
def debug(self, message: str) -> None:
|
def debug(self, message: str) -> None:
|
||||||
"""向日志中输出调试信息"""
|
"""向日志中输出调试信息"""
|
||||||
|
@ -127,13 +120,6 @@ class IcaClient:
|
||||||
"""向日志中输出警告信息"""
|
"""向日志中输出警告信息"""
|
||||||
|
|
||||||
|
|
||||||
class ConfigData:
|
|
||||||
def __getitem__(self, key: str):
|
|
||||||
...
|
|
||||||
def have_key(self, key: str) -> bool:
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
on_load = Callable[[IcaClient], None]
|
on_load = Callable[[IcaClient], None]
|
||||||
# def on_load(client: IcaClient) -> None:
|
# def on_load(client: IcaClient) -> None:
|
||||||
# ...
|
# ...
|
||||||
|
@ -145,7 +131,3 @@ on_message = Callable[[NewMessage, IcaClient], None]
|
||||||
on_delete_message = Callable[[MessageId, IcaClient], None]
|
on_delete_message = Callable[[MessageId, IcaClient], None]
|
||||||
# def on_delete_message(msg_id: MessageId, client: IcaClient) -> None:
|
# def on_delete_message(msg_id: MessageId, client: IcaClient) -> None:
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
on_config = Callable[[None], Tuple[str, str]]
|
|
||||||
|
|
||||||
CONFIG_DATA: ConfigData = ConfigData()
|
|
||||||
|
|
|
@ -6,10 +6,10 @@ else:
|
||||||
NewMessage = TypeVar("NewMessage")
|
NewMessage = TypeVar("NewMessage")
|
||||||
IcaClient = TypeVar("IcaClient")
|
IcaClient = TypeVar("IcaClient")
|
||||||
|
|
||||||
_version_ = "1.3.0"
|
_version_ = "1.1.0"
|
||||||
|
|
||||||
def on_message(msg: NewMessage, client: IcaClient) -> None:
|
def on_message(msg: NewMessage, client: IcaClient) -> None:
|
||||||
if not (msg.is_from_self or msg.is_reply):
|
if not (msg.is_from_self or msg.is_reply):
|
||||||
if msg.content == "/bot":
|
if msg.content == "/bot":
|
||||||
reply = msg.reply_with(f"ica-async-rs({client.version})-sync-py {_version_}")
|
reply = msg.reply_with(f"ica-async-rs-sync-py {_version_}")
|
||||||
client.send_message(reply)
|
client.send_message(reply)
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
import io
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
import traceback
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, TypeVar, Optional, Tuple
|
from typing import TYPE_CHECKING, TypeVar, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ica_typing import NewMessage, IcaClient, ConfigData
|
from ica_typing import NewMessage, IcaClient
|
||||||
CONFIG_DATA: ConfigData
|
|
||||||
else:
|
else:
|
||||||
CONFIG_DATA = None
|
|
||||||
NewMessage = TypeVar("NewMessage")
|
NewMessage = TypeVar("NewMessage")
|
||||||
IcaClient = TypeVar("IcaClient")
|
IcaClient = TypeVar("IcaClient")
|
||||||
|
|
||||||
_version_ = "2.2.0-rs"
|
_version_ = "2.1.2-rs"
|
||||||
|
|
||||||
def format_data_size(data_bytes: float) -> str:
|
def format_data_size(data_bytes: float) -> str:
|
||||||
data_lens = ["B", "KB", "MB", "GB", "TB"]
|
data_lens = ["B", "KB", "MB", "GB", "TB"]
|
||||||
|
@ -58,31 +54,24 @@ def format_hit_count(count: int) -> str:
|
||||||
|
|
||||||
|
|
||||||
def wrap_request(url: str, msg: NewMessage, client: IcaClient) -> Optional[dict]:
|
def wrap_request(url: str, msg: NewMessage, client: IcaClient) -> Optional[dict]:
|
||||||
# if CONFIG_DATA
|
|
||||||
try:
|
try:
|
||||||
cookie = CONFIG_DATA["cookie"]
|
|
||||||
if cookie == "填写你的 cookie" or cookie is None:
|
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
else:
|
except requests.exceptions.RequestException as e:
|
||||||
response = requests.get(url, cookies={"openbmclapi-jwt": cookie})
|
client.warn(
|
||||||
except requests.exceptions.RequestException:
|
f"数据请求失败, 请检查网络\n{e}"
|
||||||
warn_msg = f"数据请求失败, 请检查网络\n{traceback.format_exc()}"
|
)
|
||||||
reply = msg.reply_with(warn_msg)
|
reply = msg.reply_with(f"数据请求失败, 请检查网络\n{e}")
|
||||||
client.send_and_warn(reply)
|
client.send_message(reply)
|
||||||
return None
|
|
||||||
except Exception as _:
|
|
||||||
warn_msg = f"数据请求中发生未知错误, 请呼叫 shenjack\n{traceback.format_exc()}"
|
|
||||||
reply = msg.reply_with(warn_msg)
|
|
||||||
client.send_and_warn(reply)
|
|
||||||
return None
|
return None
|
||||||
if not response.status_code == 200 or response.reason != "OK":
|
if not response.status_code == 200 or response.reason != "OK":
|
||||||
warn_msg = f"请求失败, 请检查网络\n{response.status_code} {response.reason}"
|
client.warn(
|
||||||
reply = msg.reply_with(warn_msg)
|
f"数据请求失败, 请检查网络\n{response.status}"
|
||||||
client.send_and_warn(reply)
|
)
|
||||||
return None
|
return None
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def bmcl_dashboard(msg: NewMessage, client: IcaClient) -> None:
|
def bmcl_dashboard(msg: NewMessage, client: IcaClient) -> None:
|
||||||
req_time = time.time()
|
req_time = time.time()
|
||||||
# 记录请求时间
|
# 记录请求时间
|
||||||
|
@ -111,102 +100,56 @@ def bmcl_dashboard(msg: NewMessage, client: IcaClient) -> None:
|
||||||
client.send_message(reply)
|
client.send_message(reply)
|
||||||
|
|
||||||
|
|
||||||
def check_is_full_data(data: list) -> bool:
|
def parse_rank(data: dict) -> dict:
|
||||||
return 'user' in data[0]
|
rank_data = {"hits": 0, "bytes": 0}
|
||||||
|
if "metric" in data:
|
||||||
|
rank_data["hits"] = data["metric"]["hits"]
|
||||||
|
rank_data["bytes"] = data["metric"]["bytes"]
|
||||||
|
return {
|
||||||
|
"name": data["name"],
|
||||||
|
"start": data["isEnabled"],
|
||||||
|
# "full": "全量" if "fullSize" in data else "分片",
|
||||||
|
# "version": data["version"] if "version" in data else "未知版本",
|
||||||
|
"owner": data["sponsor"]["name"] if "sponsor" in data else "未知",
|
||||||
|
"rank": rank_data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def display_rank_min(ranks: list, req_time) -> str:
|
def bmcl_rank(msg: NewMessage, client: IcaClient, name: Optional[str]) -> None:
|
||||||
cache = io.StringIO()
|
|
||||||
cache.write(f"bmclapi v{_version_}-排名({len(ranks)})")
|
|
||||||
if check_is_full_data(ranks):
|
|
||||||
cache.write("完整\n")
|
|
||||||
for rank in ranks:
|
|
||||||
cache.write('✅' if rank['isEnabled'] else '❌')
|
|
||||||
if 'fullSize' in rank:
|
|
||||||
cache.write('🌕' if rank['fullSize'] else '🌘')
|
|
||||||
cache.write(f"-{rank['index']+1:3}")
|
|
||||||
cache.write(f"|{rank['name']}\n")
|
|
||||||
else:
|
|
||||||
cache.write("无cookie\n")
|
|
||||||
for rank in ranks:
|
|
||||||
cache.write('✅' if rank['isEnabled'] else '❌')
|
|
||||||
cache.write(f"-{rank['index']+1:3}")
|
|
||||||
cache.write(f"|{rank['name']}\n")
|
|
||||||
cache.write(f"请求时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(req_time))}")
|
|
||||||
return cache.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
def display_rank_full(ranks: list, req_time) -> str:
|
|
||||||
cache = io.StringIO()
|
|
||||||
cache.write(f"bmclapi v{_version_}-排名({len(ranks)})")
|
|
||||||
if check_is_full_data(ranks):
|
|
||||||
cache.write("完整\n")
|
|
||||||
for rank in ranks:
|
|
||||||
# 基本信息
|
|
||||||
cache.write('✅' if rank['isEnabled'] else '❌')
|
|
||||||
if 'fullSize' in rank:
|
|
||||||
cache.write('🌕' if rank['fullSize'] else '🌘')
|
|
||||||
cache.write(f"|{rank['index']+1:3}|")
|
|
||||||
cache.write(f"{rank['name']}")
|
|
||||||
if 'version' in rank:
|
|
||||||
cache.write(f"|{rank['version']}")
|
|
||||||
cache.write('\n')
|
|
||||||
# 用户/赞助信息
|
|
||||||
if ('user' in rank) and (rank['user'] is not None):
|
|
||||||
cache.write(f"所有者:{rank['user']['name']}")
|
|
||||||
if 'sponsor' in rank:
|
|
||||||
cache.write(f"|赞助者:{rank['sponsor']['name']}")
|
|
||||||
if 'sponsor' in rank or ('user' in rank and rank['user'] is not None):
|
|
||||||
cache.write('\n')
|
|
||||||
# 数据信息
|
|
||||||
if 'metric' in rank:
|
|
||||||
hits = format_hit_count(rank['metric']['hits'])
|
|
||||||
data = format_data_size(rank['metric']['bytes'])
|
|
||||||
cache.write(f"hit/data|{hits}|{data}")
|
|
||||||
cache.write('\n')
|
|
||||||
else:
|
|
||||||
cache.write("无cookie\n")
|
|
||||||
for rank in ranks:
|
|
||||||
cache.write('✅' if rank['isEnabled'] else '❌')
|
|
||||||
cache.write(f"-{rank['index']+1:3}")
|
|
||||||
cache.write(f"|{rank['name']}|\n")
|
|
||||||
if 'sponsor' in rank:
|
|
||||||
cache.write(f"赞助者: {rank['sponsor']['name']}|")
|
|
||||||
if 'metric' in rank:
|
|
||||||
cache.write(f"hit/data|{format_hit_count(rank['metric']['hits'])}|{format_data_size(rank['metric']['bytes'])}\n")
|
|
||||||
cache.write(f"请求时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(req_time))}")
|
|
||||||
return cache.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
def bmcl_rank_general(msg, client):
|
|
||||||
req_time = time.time()
|
req_time = time.time()
|
||||||
# 记录请求时间
|
# 记录请求时间
|
||||||
rank_data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/rank", msg, client)
|
rank_data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/rank", msg, client)
|
||||||
if rank_data is None:
|
if rank_data is None:
|
||||||
return
|
return
|
||||||
# 预处理数据
|
ranks = [parse_rank(data) for data in rank_data]
|
||||||
for i, r in enumerate(rank_data):
|
if name is None:
|
||||||
r['index'] = i
|
# 全部排名
|
||||||
# 显示前3名
|
# 显示前3名
|
||||||
ranks = rank_data[:3]
|
limit = 3
|
||||||
# ranks = rank_data
|
if len(ranks) < limit:
|
||||||
report_msg = display_rank_full(ranks, req_time)
|
show_ranks = ranks
|
||||||
|
else:
|
||||||
|
show_ranks = ranks[:limit]
|
||||||
|
rank_msg = (
|
||||||
|
f"{'✅' if r['start'] else '❌'}名称: {r['name']}\n"
|
||||||
|
# f"-{rank['full']} \n"
|
||||||
|
# f"版本: {r['version']}\n"
|
||||||
|
f"赞助者: {r['owner']}|"
|
||||||
|
f"h/d {format_hit_count(r['rank']['hits'])}|{format_data_size(r['rank']['bytes'])}"
|
||||||
|
for r in show_ranks
|
||||||
|
)
|
||||||
|
rank_msg = "\n".join(rank_msg)
|
||||||
|
report_msg = (
|
||||||
|
f"OpenBMCLAPI 面板v{_version_}-排名\n{rank_msg}\n"
|
||||||
|
f"请求时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(req_time))}\n"
|
||||||
|
)
|
||||||
|
reply = msg.reply_with(report_msg)
|
||||||
client.info(report_msg)
|
client.info(report_msg)
|
||||||
reply = msg.reply_with(display_rank_full(ranks, req_time))
|
|
||||||
client.send_message(reply)
|
client.send_message(reply)
|
||||||
|
|
||||||
|
|
||||||
def bmcl_rank(msg: NewMessage, client: IcaClient, name: str) -> None:
|
|
||||||
req_time = time.time()
|
|
||||||
# 记录请求时间
|
|
||||||
rank_data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/rank", msg, client)
|
|
||||||
if rank_data is None:
|
|
||||||
return
|
return
|
||||||
# 预处理数据
|
else:
|
||||||
for i, r in enumerate(rank_data):
|
|
||||||
r['index'] = i
|
|
||||||
# 搜索是否有这个名字的节点
|
# 搜索是否有这个名字的节点
|
||||||
names = [r["name"].lower() for r in rank_data]
|
names = [r["name"].lower() for r in ranks]
|
||||||
try:
|
try:
|
||||||
finds = [re.search(name.lower(), n) for n in names]
|
finds = [re.search(name.lower(), n) for n in names]
|
||||||
except re.error as e:
|
except re.error as e:
|
||||||
|
@ -218,22 +161,38 @@ def bmcl_rank(msg: NewMessage, client: IcaClient, name: str) -> None:
|
||||||
client.send_message(reply)
|
client.send_message(reply)
|
||||||
return
|
return
|
||||||
# 如果找到 > 3 个节点, 则提示 不显示
|
# 如果找到 > 3 个节点, 则提示 不显示
|
||||||
counts = [f for f in finds if f]
|
counts = [True for find in finds if find]
|
||||||
ranks = [rank_data[i] for i, f in enumerate(finds) if f]
|
|
||||||
if len(counts) > 3:
|
if len(counts) > 3:
|
||||||
if len(counts) > 10:
|
if len(counts) > 10:
|
||||||
reply = msg.reply_with(f"搜索|{name}|到{len(counts)}个节点, 请用更精确的名字")
|
reply = msg.reply_with(f"搜索|{name}|到{len(counts)}个节点, 请用更精确的名字")
|
||||||
else:
|
else:
|
||||||
# 4~10 个节点 只显示名称和次序
|
# 4~10 个节点 只显示名称和次序
|
||||||
report_msg = display_rank_min(ranks, req_time)
|
find_msg = [f"{'✅' if r['start'] else '❌'}{r['name']}-No.{i+1}" for i, r in enumerate(ranks) if finds[i]]
|
||||||
|
find_msg = "\n".join(find_msg)
|
||||||
|
report_msg = f"OpenBMCLAPI 面板v{_version_}-搜索|{name}|\n{find_msg}"
|
||||||
reply = msg.reply_with(report_msg)
|
reply = msg.reply_with(report_msg)
|
||||||
client.send_message(reply)
|
client.send_message(reply)
|
||||||
return
|
return
|
||||||
# 如果找到 <= 3 个节点, 则显示全部信息
|
rank_msgs = []
|
||||||
report_msg = display_rank_full(ranks, req_time)
|
for i, find in enumerate(finds):
|
||||||
client.info(report_msg)
|
if find:
|
||||||
|
rank = ranks[i]
|
||||||
|
rank_msg = (
|
||||||
|
f"{'✅' if rank['start'] else '❌'}名称: {rank['name']}-No.{i+1}\n"
|
||||||
|
# f"-{rank['full']} \n"
|
||||||
|
# f"版本: {rank['version']}\n"
|
||||||
|
f"赞助商: {rank['owner']}|"
|
||||||
|
f"h/d {format_hit_count(rank['rank']['hits'])}|{format_data_size(rank['rank']['bytes'])}"
|
||||||
|
)
|
||||||
|
rank_msgs.append(rank_msg)
|
||||||
|
rank_msgs = "\n".join(rank_msgs)
|
||||||
|
report_msg = f"OpenBMCLAPI 面板v{_version_}-排名\n{rank_msgs}"
|
||||||
reply = msg.reply_with(report_msg)
|
reply = msg.reply_with(report_msg)
|
||||||
|
client.info(report_msg)
|
||||||
client.send_message(reply)
|
client.send_message(reply)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
help = """/bmcl -> dashboard
|
help = """/bmcl -> dashboard
|
||||||
|
@ -249,14 +208,11 @@ help = """/bmcl -> dashboard
|
||||||
|
|
||||||
def on_message(msg: NewMessage, client: IcaClient) -> None:
|
def on_message(msg: NewMessage, client: IcaClient) -> None:
|
||||||
if not (msg.is_from_self or msg.is_reply):
|
if not (msg.is_from_self or msg.is_reply):
|
||||||
if '\n' in msg.content:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
if msg.content.startswith("/bmcl"):
|
if msg.content.startswith("/bmcl"):
|
||||||
if msg.content == "/bmcl":
|
if msg.content == "/bmcl":
|
||||||
bmcl_dashboard(msg, client)
|
bmcl_dashboard(msg, client)
|
||||||
elif msg.content == "/bmcl rank":
|
elif msg.content == "/bmcl rank":
|
||||||
bmcl_rank_general(msg, client)
|
bmcl_rank(msg, client, None)
|
||||||
elif msg.content.startswith("/bmcl rank") and len(msg.content) > 11:
|
elif msg.content.startswith("/bmcl rank") and len(msg.content) > 11:
|
||||||
name = msg.content[11:]
|
name = msg.content[11:]
|
||||||
bmcl_rank(msg, client, name)
|
bmcl_rank(msg, client, name)
|
||||||
|
@ -272,14 +228,3 @@ def on_message(msg: NewMessage, client: IcaClient) -> None:
|
||||||
if len(name) > 1:
|
if len(name) > 1:
|
||||||
name = name[1]
|
name = name[1]
|
||||||
bmcl_rank(msg, client, name)
|
bmcl_rank(msg, client, name)
|
||||||
except: # noqa
|
|
||||||
report_msg = f"bmcl插件发生错误,请呼叫shenjack\n{traceback.format_exc()}"
|
|
||||||
reply = msg.reply_with(report_msg)
|
|
||||||
client.send_and_warn(reply)
|
|
||||||
|
|
||||||
|
|
||||||
def on_config() -> Tuple[str, str]:
|
|
||||||
return (
|
|
||||||
"bmcl.toml",
|
|
||||||
"""cookie = \"填写你的 cookie\""""
|
|
||||||
)
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ else:
|
||||||
NewMessage = TypeVar("NewMessage")
|
NewMessage = TypeVar("NewMessage")
|
||||||
IcaClient = TypeVar("IcaClient")
|
IcaClient = TypeVar("IcaClient")
|
||||||
|
|
||||||
def safe_eval(code: str, msg: NewMessage) -> str:
|
def safe_eval(code: str) -> str:
|
||||||
try:
|
try:
|
||||||
# code = code.replace('help', '坏东西!\n')
|
# code = code.replace('help', '坏东西!\n')
|
||||||
# code = code.replace('bytes', '坏东西!\n')
|
# code = code.replace('bytes', '坏东西!\n')
|
||||||
|
@ -48,7 +48,6 @@ def safe_eval(code: str, msg: NewMessage) -> str:
|
||||||
"return": "别惦记你那个 return 了",
|
"return": "别惦记你那个 return 了",
|
||||||
"getattr": "<built-in function getattr>",
|
"getattr": "<built-in function getattr>",
|
||||||
"setattr": "<built-in function setattr>",
|
"setattr": "<built-in function setattr>",
|
||||||
"msg": msg,
|
|
||||||
}
|
}
|
||||||
os.system = "不许"
|
os.system = "不许"
|
||||||
result = str(eval(code, global_val, {}))
|
result = str(eval(code, global_val, {}))
|
||||||
|
@ -74,6 +73,6 @@ def on_message(message: NewMessage, client: IcaClient) -> None:
|
||||||
if not (message.is_from_self or message.is_reply):
|
if not (message.is_from_self or message.is_reply):
|
||||||
if message.content.startswith("/="):
|
if message.content.startswith("/="):
|
||||||
code = message.content[2:]
|
code = message.content[2:]
|
||||||
result = safe_eval(code, message)
|
result = safe_eval(code)
|
||||||
reply = message.reply_with(result)
|
reply = message.reply_with(result)
|
||||||
client.send_message(reply)
|
client.send_message(reply)
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::config::IcaConfig;
|
||||||
use crate::data_struct::all_rooms::Room;
|
use crate::data_struct::all_rooms::Room;
|
||||||
use crate::data_struct::messages::{DeleteMessage, SendMessage};
|
use crate::data_struct::messages::{DeleteMessage, SendMessage};
|
||||||
use crate::data_struct::online_data::OnlineData;
|
use crate::data_struct::online_data::OnlineData;
|
||||||
|
use crate::data_struct::RoomId;
|
||||||
use crate::ClientStatus;
|
use crate::ClientStatus;
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
@ -43,8 +44,7 @@ pub async fn delete_message(client: &Client, message: &DeleteMessage) -> bool {
|
||||||
/// ```typescript
|
/// ```typescript
|
||||||
/// async fetchHistory(messageId: string, roomId: number, currentLoadedMessagesCount: number)
|
/// async fetchHistory(messageId: string, roomId: number, currentLoadedMessagesCount: number)
|
||||||
/// ```
|
/// ```
|
||||||
// #[allow(dead_code)]
|
pub async fn fetch_history(client: &Client, roomd_id: RoomId) -> bool { false }
|
||||||
// pub async fn fetch_history(client: &Client, roomd_id: RoomId) -> bool { false }
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IcalinguaStatus {
|
pub struct IcalinguaStatus {
|
||||||
|
|
|
@ -23,8 +23,6 @@ pub struct IcaConfig {
|
||||||
pub filter_list: Vec<i64>,
|
pub filter_list: Vec<i64>,
|
||||||
/// Python 插件路径
|
/// Python 插件路径
|
||||||
pub py_plugin_path: Option<String>,
|
pub py_plugin_path: Option<String>,
|
||||||
/// Python 配置文件路径
|
|
||||||
pub py_config_path: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IcaConfig {
|
impl IcaConfig {
|
||||||
|
|
|
@ -72,7 +72,7 @@ async fn main() {
|
||||||
room.clone(),
|
room.clone(),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
std::thread::sleep(Duration::from_secs(1));
|
||||||
info!("发送启动消息到房间: {}", room);
|
info!("发送启动消息到房间: {}", room);
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
socket.emit("sendMessage", serde_json::to_value(startup_msg).unwrap()).await
|
socket.emit("sendMessage", serde_json::to_value(startup_msg).unwrap()).await
|
||||||
|
@ -82,7 +82,7 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
std::thread::sleep(Duration::from_secs(3));
|
||||||
// 等待一个输入
|
// 等待一个输入
|
||||||
info!("Press any key to exit");
|
info!("Press any key to exit");
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
|
|
@ -2,12 +2,11 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use rust_socketio::asynchronous::Client;
|
use rust_socketio::asynchronous::Client;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::client::IcalinguaStatus;
|
|
||||||
use crate::data_struct::messages::NewMessage;
|
use crate::data_struct::messages::NewMessage;
|
||||||
use crate::data_struct::MessageId;
|
use crate::data_struct::MessageId;
|
||||||
use crate::py::{class, PyPlugin, PyStatus};
|
use crate::py::{class, verify_plugins, PyStatus};
|
||||||
|
|
||||||
pub fn get_func<'py>(py_module: &'py PyAny, path: &PathBuf, name: &'py str) -> Option<&'py PyAny> {
|
pub fn get_func<'py>(py_module: &'py PyAny, path: &PathBuf, name: &'py str) -> Option<&'py PyAny> {
|
||||||
// 要处理的情况:
|
// 要处理的情况:
|
||||||
|
@ -43,58 +42,20 @@ pub fn get_func<'py>(py_module: &'py PyAny, path: &PathBuf, name: &'py str) -> O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_plugins() {
|
|
||||||
let mut need_reload_files: Vec<PathBuf> = Vec::new();
|
|
||||||
let plugin_path = IcalinguaStatus::get_config().py_plugin_path.as_ref();
|
|
||||||
if let None = plugin_path {
|
|
||||||
warn!("未配置 Python 插件路径");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let plugin_path = plugin_path.unwrap();
|
|
||||||
for entry in std::fs::read_dir(&plugin_path).unwrap() {
|
|
||||||
if let Ok(entry) = entry {
|
|
||||||
let path = entry.path();
|
|
||||||
if let Some(ext) = path.extension() {
|
|
||||||
if ext == "py" {
|
|
||||||
if !PyStatus::verify_file(&path) {
|
|
||||||
need_reload_files.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if need_reload_files.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
info!("file change list: {:?}", need_reload_files);
|
|
||||||
for reload_file in need_reload_files {
|
|
||||||
match PyPlugin::new_from_path(&reload_file) {
|
|
||||||
Some(plugin) => {
|
|
||||||
PyStatus::add_file(reload_file.clone(), plugin);
|
|
||||||
info!("重载 Python 插件: {:?}", reload_file);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
warn!("重载 Python 插件: {:?} 失败", reload_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 执行 new message 的 python 插件
|
/// 执行 new message 的 python 插件
|
||||||
pub async fn new_message_py(message: &NewMessage, client: &Client) {
|
pub async fn new_message_py(message: &NewMessage, client: &Client) {
|
||||||
// 验证插件是否改变
|
// 验证插件是否改变
|
||||||
verify_plugins();
|
verify_plugins();
|
||||||
|
|
||||||
let plugins = PyStatus::get_files();
|
let plugins = PyStatus::get_files();
|
||||||
for (path, plugin) in plugins.iter() {
|
for (path, (_, py_module)) in plugins.iter() {
|
||||||
let msg = class::NewMessagePy::new(message);
|
let msg = class::NewMessagePy::new(message);
|
||||||
let client = class::IcaClientPy::new(client);
|
let client = class::IcaClientPy::new(client);
|
||||||
let args = (msg, client);
|
|
||||||
// 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱)
|
// 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱)
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
if let Some(py_func) = get_func(plugin.py_module.as_ref(py), &path, "on_message") {
|
let args = (msg, client);
|
||||||
|
if let Some(py_func) = get_func(py_module.as_ref(py), &path, "on_message") {
|
||||||
if let Err(e) = py_func.call1(args) {
|
if let Err(e) = py_func.call1(args) {
|
||||||
warn!("failed to call function<on_message>: {:?}", e);
|
warn!("failed to call function<on_message>: {:?}", e);
|
||||||
}
|
}
|
||||||
|
@ -106,17 +67,14 @@ pub async fn new_message_py(message: &NewMessage, client: &Client) {
|
||||||
|
|
||||||
pub async fn delete_message_py(msg_id: MessageId, client: &Client) {
|
pub async fn delete_message_py(msg_id: MessageId, client: &Client) {
|
||||||
verify_plugins();
|
verify_plugins();
|
||||||
|
|
||||||
let plugins = PyStatus::get_files();
|
let plugins = PyStatus::get_files();
|
||||||
for (path, plugin) in plugins.iter() {
|
for (path, (_, py_module)) in plugins.iter() {
|
||||||
let msg_id = msg_id.clone();
|
let msg_id = msg_id.clone();
|
||||||
let client = class::IcaClientPy::new(client);
|
let client = class::IcaClientPy::new(client);
|
||||||
let args = (msg_id.clone(), client);
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
if let Some(py_func) =
|
let args = (msg_id.clone(), client);
|
||||||
get_func(plugin.py_module.as_ref(py), &path, "on_delete_message")
|
if let Some(py_func) = get_func(py_module.as_ref(py), &path, "on_delete_message") {
|
||||||
{
|
|
||||||
if let Err(e) = py_func.call1(args) {
|
if let Err(e) = py_func.call1(args) {
|
||||||
warn!("failed to call function<on_delete_message>: {:?}", e);
|
warn!("failed to call function<on_delete_message>: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use rust_socketio::asynchronous::Client;
|
use rust_socketio::asynchronous::Client;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use toml::Value as TomlValue;
|
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use crate::client::{delete_message, send_message, IcalinguaStatus};
|
use crate::client::{delete_message, send_message, IcalinguaStatus};
|
||||||
|
@ -162,14 +161,6 @@ impl IcaClientPy {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_and_warn(&self, message: SendMessagePy) -> bool {
|
|
||||||
warn!(message.msg.content);
|
|
||||||
tokio::task::block_in_place(|| {
|
|
||||||
let rt = Runtime::new().unwrap();
|
|
||||||
rt.block_on(send_message(&self.client, &message.msg))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_message(&self, message: DeleteMessagePy) -> bool {
|
pub fn delete_message(&self, message: DeleteMessagePy) -> bool {
|
||||||
tokio::task::block_in_place(|| {
|
tokio::task::block_in_place(|| {
|
||||||
let rt = Runtime::new().unwrap();
|
let rt = Runtime::new().unwrap();
|
||||||
|
@ -193,8 +184,6 @@ impl IcaClientPy {
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
pub fn get_status(&self) -> IcaStatusPy { IcaStatusPy::new() }
|
pub fn get_status(&self) -> IcaStatusPy { IcaStatusPy::new() }
|
||||||
#[getter]
|
|
||||||
pub fn get_version(&self) -> String { crate::VERSION.to_string() }
|
|
||||||
|
|
||||||
pub fn debug(&self, content: String) {
|
pub fn debug(&self, content: String) {
|
||||||
debug!("{}", content);
|
debug!("{}", content);
|
||||||
|
@ -216,54 +205,3 @@ impl IcaClientPy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
#[pyclass]
|
|
||||||
#[pyo3(name = "ConfigRequest")]
|
|
||||||
pub struct ConfigRequestPy {
|
|
||||||
pub path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pymethods]
|
|
||||||
impl ConfigRequestPy {
|
|
||||||
#[new]
|
|
||||||
pub fn py_new(path: String) -> Self { Self { path } }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
#[pyclass]
|
|
||||||
#[pyo3(name = "ConfigData")]
|
|
||||||
pub struct ConfigDataPy {
|
|
||||||
pub data: TomlValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pymethods]
|
|
||||||
impl ConfigDataPy {
|
|
||||||
pub fn __getitem__(self_: PyRef<'_, Self>, key: String) -> Option<Py<PyAny>> {
|
|
||||||
match self_.data.get(&key) {
|
|
||||||
Some(value) => match value {
|
|
||||||
TomlValue::String(s) => Some(s.into_py(self_.py())),
|
|
||||||
TomlValue::Integer(i) => Some(i.into_py(self_.py())),
|
|
||||||
TomlValue::Float(f) => Some(f.into_py(self_.py())),
|
|
||||||
TomlValue::Boolean(b) => Some(b.into_py(self_.py())),
|
|
||||||
TomlValue::Array(a) => {
|
|
||||||
let new_self = Self::new(TomlValue::Array(a.clone()));
|
|
||||||
let py_value = new_self.into_py(self_.py());
|
|
||||||
Some(py_value)
|
|
||||||
}
|
|
||||||
TomlValue::Table(t) => {
|
|
||||||
let new_self = Self::new(TomlValue::Table(t.clone()));
|
|
||||||
let py_value = new_self.into_py(self_.py());
|
|
||||||
Some(py_value)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn have_key(&self, key: String) -> bool { self.data.get(&key).is_some() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigDataPy {
|
|
||||||
pub fn new(data: TomlValue) -> Self { Self { data } }
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ use std::time::SystemTime;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyTuple;
|
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use crate::client::IcalinguaStatus;
|
use crate::client::IcalinguaStatus;
|
||||||
|
@ -13,138 +12,11 @@ use crate::config::IcaConfig;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PyStatus {
|
pub struct PyStatus {
|
||||||
pub files: Option<HashMap<PathBuf, PyPlugin>>,
|
pub files: Option<HashMap<PathBuf, (Option<SystemTime>, Py<PyAny>)>>,
|
||||||
}
|
|
||||||
|
|
||||||
pub type PyPluginData = HashMap<PathBuf, PyPlugin>;
|
|
||||||
pub type RawPyPlugin = (PathBuf, Option<SystemTime>, String);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PyPlugin {
|
|
||||||
pub file_path: PathBuf,
|
|
||||||
pub changed_time: Option<SystemTime>,
|
|
||||||
pub py_module: Py<PyAny>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PyPlugin {
|
|
||||||
pub fn new_from_path(path: &PathBuf) -> Option<Self> {
|
|
||||||
let raw_file = load_py_file(&path);
|
|
||||||
match raw_file {
|
|
||||||
Ok(raw_file) => match Self::try_from(raw_file) {
|
|
||||||
Ok(plugin) => Some(plugin),
|
|
||||||
Err(e) => {
|
|
||||||
warn!("加载 Python 插件文件{:?}: {:?} 失败", path, e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
warn!("加载插件 {:?}: {:?} 失败", path, e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn verifiy(&self) -> bool {
|
|
||||||
match get_change_time(&self.file_path) {
|
|
||||||
None => false,
|
|
||||||
Some(time) => {
|
|
||||||
if let Some(changed_time) = self.changed_time {
|
|
||||||
time.eq(&changed_time)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<RawPyPlugin> for PyPlugin {
|
|
||||||
type Error = PyErr;
|
|
||||||
fn try_from(value: RawPyPlugin) -> Result<Self, Self::Error> {
|
|
||||||
let (path, changed_time, content) = value;
|
|
||||||
let py_module = py_module_from_code(&content, &path);
|
|
||||||
if let Err(e) = py_module {
|
|
||||||
warn!("加载 Python 插件: {:?} 失败", e);
|
|
||||||
return Err(e);
|
|
||||||
};
|
|
||||||
let py_module = py_module.unwrap();
|
|
||||||
Python::with_gil(|py| {
|
|
||||||
let module = py_module.as_ref(py);
|
|
||||||
if let Some(config_func) = call::get_func(module, &path, "on_config") {
|
|
||||||
match config_func.call0() {
|
|
||||||
Ok(config) => {
|
|
||||||
if config.is_instance_of::<PyTuple>() {
|
|
||||||
let (config, default) = config.extract::<(String, String)>().unwrap();
|
|
||||||
let base_path =
|
|
||||||
IcalinguaStatus::get_config().py_config_path.as_ref().unwrap();
|
|
||||||
|
|
||||||
let mut base_path: PathBuf = PathBuf::from(base_path);
|
|
||||||
|
|
||||||
if !base_path.exists() {
|
|
||||||
warn!("python 插件路径不存在, 创建: {:?}", base_path);
|
|
||||||
std::fs::create_dir_all(&base_path)?;
|
|
||||||
}
|
|
||||||
base_path.push(&config);
|
|
||||||
|
|
||||||
let config_value = if base_path.exists() {
|
|
||||||
info!("加载 {:?} 的配置文件 {:?} 中", path, base_path);
|
|
||||||
let content = std::fs::read_to_string(&base_path)?;
|
|
||||||
toml::from_str(&content)
|
|
||||||
} else {
|
|
||||||
warn!("配置文件 {:?} 不存在, 创建默认配置", base_path);
|
|
||||||
// 写入默认配置
|
|
||||||
std::fs::write(base_path, &default)?;
|
|
||||||
toml::from_str(&default)
|
|
||||||
};
|
|
||||||
match config_value {
|
|
||||||
Ok(config) => {
|
|
||||||
let py_config = class::ConfigDataPy::new(config);
|
|
||||||
let py_config = PyCell::new(py, py_config).unwrap();
|
|
||||||
module.setattr("CONFIG_DATA", py_config).unwrap();
|
|
||||||
Ok(PyPlugin {
|
|
||||||
file_path: path,
|
|
||||||
changed_time,
|
|
||||||
py_module: module.into_py(py),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
|
||||||
"加载 Python 插件 {:?} 的配置文件信息时失败:{:?}",
|
|
||||||
path, e
|
|
||||||
);
|
|
||||||
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(format!(
|
|
||||||
"加载 Python 插件 {:?} 的配置文件信息时失败:{:?}",
|
|
||||||
path, e
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
"加载 Python 插件 {:?} 的配置文件信息时失败:返回的不是 [str, str]",
|
|
||||||
path
|
|
||||||
);
|
|
||||||
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(format!(
|
|
||||||
"返回的不是 [str, str]"
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("加载 Python 插件 {:?} 的配置文件信息时失败:{:?}", path, e);
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(PyPlugin {
|
|
||||||
file_path: path,
|
|
||||||
changed_time,
|
|
||||||
py_module: module.into_py(py),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PyStatus {
|
impl PyStatus {
|
||||||
pub fn get_files() -> &'static PyPluginData {
|
pub fn get_files() -> &'static HashMap<PathBuf, (Option<SystemTime>, Py<PyAny>)> {
|
||||||
unsafe {
|
unsafe {
|
||||||
match PYSTATUS.files.as_ref() {
|
match PYSTATUS.files.as_ref() {
|
||||||
Some(files) => files,
|
Some(files) => files,
|
||||||
|
@ -156,15 +28,15 @@ impl PyStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_file(path: PathBuf, plugin: PyPlugin) {
|
pub fn add_file(path: PathBuf, changed_time: Option<SystemTime>, py_module: Py<PyAny>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
match PYSTATUS.files.as_mut() {
|
match PYSTATUS.files.as_mut() {
|
||||||
Some(files) => {
|
Some(files) => {
|
||||||
files.insert(path, plugin);
|
files.insert(path, (changed_time, py_module));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let mut files: PyPluginData = HashMap::new();
|
let mut files = HashMap::new();
|
||||||
files.insert(path, plugin);
|
files.insert(path, (changed_time, py_module));
|
||||||
PYSTATUS.files = Some(files);
|
PYSTATUS.files = Some(files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +47,16 @@ impl PyStatus {
|
||||||
unsafe {
|
unsafe {
|
||||||
match PYSTATUS.files.as_ref() {
|
match PYSTATUS.files.as_ref() {
|
||||||
Some(files) => match files.get(path) {
|
Some(files) => match files.get(path) {
|
||||||
Some(plugin) => plugin.verifiy(),
|
Some((changed_time, _)) => {
|
||||||
|
if let Some(changed_time) = changed_time {
|
||||||
|
if let Some(new_changed_time) = get_change_time(path) {
|
||||||
|
if new_changed_time != *changed_time {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
None => false,
|
None => false,
|
||||||
},
|
},
|
||||||
None => false,
|
None => false,
|
||||||
|
@ -186,7 +67,7 @@ impl PyStatus {
|
||||||
|
|
||||||
pub static mut PYSTATUS: PyStatus = PyStatus { files: None };
|
pub static mut PYSTATUS: PyStatus = PyStatus { files: None };
|
||||||
|
|
||||||
pub fn load_py_plugins(path: &PathBuf) -> () {
|
pub fn load_py_plugins(path: &PathBuf) {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
info!("finding plugins in: {:?}", path);
|
info!("finding plugins in: {:?}", path);
|
||||||
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
|
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
|
||||||
|
@ -196,12 +77,18 @@ pub fn load_py_plugins(path: &PathBuf) -> () {
|
||||||
}
|
}
|
||||||
Ok(dir) => {
|
Ok(dir) => {
|
||||||
for entry in dir {
|
for entry in dir {
|
||||||
let entry = entry.unwrap();
|
if let Ok(entry) = entry {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if let Some(ext) = path.extension() {
|
if let Some(ext) = path.extension() {
|
||||||
if ext == "py" {
|
if ext == "py" {
|
||||||
if let Some(plugin) = PyPlugin::new_from_path(&path) {
|
match load_py_module(&path) {
|
||||||
PyStatus::add_file(path, plugin);
|
Some((changed_time, py_module)) => {
|
||||||
|
PyStatus::add_file(path.clone(), changed_time, py_module);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
warn!("加载 Python 插件: {:?} 失败", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,17 +105,49 @@ pub fn load_py_plugins(path: &PathBuf) -> () {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn verify_plugins() {
|
||||||
|
let mut need_reload_files: Vec<PathBuf> = Vec::new();
|
||||||
|
let plugin_path = IcalinguaStatus::get_config().py_plugin_path.as_ref().unwrap().to_owned();
|
||||||
|
for entry in std::fs::read_dir(&plugin_path).unwrap() {
|
||||||
|
if let Ok(entry) = entry {
|
||||||
|
let path = entry.path();
|
||||||
|
if let Some(ext) = path.extension() {
|
||||||
|
if ext == "py" {
|
||||||
|
if !PyStatus::verify_file(&path) {
|
||||||
|
need_reload_files.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if need_reload_files.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
info!("file change list: {:?}", need_reload_files);
|
||||||
|
for reload_file in need_reload_files {
|
||||||
|
match load_py_module(&reload_file) {
|
||||||
|
Some((changed_time, py_module)) => {
|
||||||
|
PyStatus::add_file(reload_file.clone(), changed_time, py_module);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
warn!("重载 Python 插件: {:?} 失败", reload_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_change_time(path: &PathBuf) -> Option<SystemTime> {
|
pub fn get_change_time(path: &PathBuf) -> Option<SystemTime> {
|
||||||
path.metadata().ok()?.modified().ok()
|
path.metadata().ok()?.modified().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn py_module_from_code(content: &str, path: &PathBuf) -> PyResult<Py<PyAny>> {
|
pub fn py_module_from_code(content: &str, path: &str) -> PyResult<Py<PyAny>> {
|
||||||
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
|
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
|
||||||
let module: PyResult<Py<PyAny>> = PyModule::from_code(
|
let module: PyResult<Py<PyAny>> = PyModule::from_code(
|
||||||
py,
|
py,
|
||||||
&content,
|
&content,
|
||||||
&path.to_string_lossy(),
|
&path,
|
||||||
&path.file_name().unwrap().to_string_lossy(),
|
&path,
|
||||||
// !!!! 请注意, 一定要给他一个名字, cpython 会自动把后面的重名模块覆盖掉前面的
|
// !!!! 请注意, 一定要给他一个名字, cpython 会自动把后面的重名模块覆盖掉前面的
|
||||||
)
|
)
|
||||||
.map(|module| module.into());
|
.map(|module| module.into());
|
||||||
|
@ -238,10 +157,38 @@ pub fn py_module_from_code(content: &str, path: &PathBuf) -> PyResult<Py<PyAny>>
|
||||||
|
|
||||||
/// 传入文件路径
|
/// 传入文件路径
|
||||||
/// 返回 hash 和 文件内容
|
/// 返回 hash 和 文件内容
|
||||||
pub fn load_py_file(path: &PathBuf) -> std::io::Result<RawPyPlugin> {
|
pub fn load_py_file(path: &PathBuf) -> std::io::Result<(Option<SystemTime>, String)> {
|
||||||
let changed_time = get_change_time(&path);
|
let changed_time = get_change_time(&path);
|
||||||
let content = std::fs::read_to_string(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
Ok((path.clone(), changed_time, content))
|
Ok((changed_time, content))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_py_module(path: &PathBuf) -> Option<(Option<SystemTime>, Py<PyAny>)> {
|
||||||
|
let (changed_time, content) = match load_py_file(&path) {
|
||||||
|
Ok((changed_time, content)) => (changed_time, content),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("failed to load file: {:?} | e: {:?}", path, e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let py_module: PyResult<Py<PyAny>> = Python::with_gil(|py| -> PyResult<Py<PyAny>> {
|
||||||
|
let module: PyResult<Py<PyAny>> = PyModule::from_code(
|
||||||
|
py,
|
||||||
|
&content,
|
||||||
|
&path.to_string_lossy(),
|
||||||
|
&path.to_string_lossy(),
|
||||||
|
// !!!! 请注意, 一定要给他一个名字, cpython 会自动把后面的重名模块覆盖掉前面的
|
||||||
|
)
|
||||||
|
.map(|module| module.into());
|
||||||
|
module
|
||||||
|
});
|
||||||
|
match py_module {
|
||||||
|
Ok(py_module) => Some((changed_time, py_module)),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("failed to load file: {:?} | e: {:?}", path, e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_py(config: &IcaConfig) {
|
pub fn init_py(config: &IcaConfig) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user