Compare commits

..

No commits in common. "2ab3f0d77bb7a7f385fbf16dc51e72498707797b" and "a574dcaa8a0c6a18546c183196027036a3c6a68b" have entirely different histories.

18 changed files with 882 additions and 731 deletions

683
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
[package]
name = "ica-rs"
version = "0.5.3"
version = "0.5.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["ica", "matrix"]
default = ["ica"]
ica = ["dep:ed25519", "dep:ed25519-dalek", "dep:hex", "dep:rust_socketio"]
matrix = ["dep:matrix-sdk", "dep:url"]
@ -14,7 +14,7 @@ matrix = ["dep:matrix-sdk", "dep:url"]
# matrix
url = { version = "2.5.0", optional = true }
matrix-sdk = { version = "0.7.1", optional = true, default-features = false, features = ["rustls-tls"] }
matrix-sdk = { version = "0.7.1", optional = true }
# ica
ed25519 = { version = "2.2.3", optional = true }

View File

@ -47,18 +47,18 @@ class IcaStatus:
...
class IcaReplyMessage:
class ReplyMessage:
...
class IcaSendMessage:
class SendMessage:
@property
def content(self) -> str:
...
@content.setter
def content(self, value: str) -> None:
...
def with_content(self, content: str) -> "IcaSendMessage":
def with_content(self, content: str) -> "SendMessage":
"""
为了链式调用, 返回自身
"""
@ -66,15 +66,15 @@ class IcaSendMessage:
return self
class IcaDeleteMessage:
class DeleteMessage:
def __str__(self):
...
class IcaNewMessage:
def reply_with(self, message: str) -> IcaSendMessage:
class NewMessage:
def reply_with(self, message: str) -> SendMessage:
...
def as_deleted(self) -> IcaDeleteMessage:
def as_deleted(self) -> DeleteMessage:
...
def __str__(self) -> str:
...
@ -93,15 +93,6 @@ class IcaNewMessage:
@property
def is_reply(self) -> bool:
...
@property
def is_room_msg(self) -> bool:
...
@property
def is_chat_msg(self) -> bool:
...
@property
def room_id(self) -> RoomId:
...
class IcaClient:
@ -112,13 +103,13 @@ class IcaClient:
# (因为目前来说, rust调用 Python端没法启动一个异步运行时
# 所以只能 tokio::task::block_in_place 转换成同步调用)
# """
def send_message(self, message: IcaSendMessage) -> bool:
def send_message(self, message: SendMessage) -> bool:
...
def send_and_warn(self, message: IcaSendMessage) -> bool:
def send_and_warn(self, message: SendMessage) -> bool:
"""发送消息, 并在日志中输出警告信息"""
self.warn(message.content)
return self.send_message(message)
def delete_message(self, message: IcaDeleteMessage) -> bool:
def delete_message(self, message: DeleteMessage) -> bool:
...
@property
@ -153,11 +144,11 @@ on_load = Callable[[IcaClient], None]
# def on_load(client: IcaClient) -> None:
# ...
on_ica_message = Callable[[IcaNewMessage, IcaClient], None]
on_message = Callable[[NewMessage, IcaClient], None]
# def on_message(msg: NewMessage, client: IcaClient) -> None:
# ...
on_ica_delete_message = Callable[[MessageId, IcaClient], None]
on_delete_message = Callable[[MessageId, IcaClient], None]
# def on_delete_message(msg_id: MessageId, client: IcaClient) -> None:
# ...

View File

@ -1,12 +1,12 @@
from typing import TYPE_CHECKING, TypeVar
if TYPE_CHECKING:
from ica_typing import IcaNewMessage, IcaClient
from ica_typing import NewMessage, IcaClient
else:
IcaNewMessage = TypeVar("NewMessage")
NewMessage = TypeVar("NewMessage")
IcaClient = TypeVar("IcaClient")
def on_ica_message(msg: IcaNewMessage, client: IcaClient) -> None:
def on_message(msg: NewMessage, client: IcaClient) -> None:
if not (msg.is_from_self or msg.is_reply):
if msg.content == "/bot":
reply = msg.reply_with(f"ica-async-rs({client.version})-sync-py {client.ica_version}")

View File

@ -7,11 +7,11 @@ import traceback
from typing import TYPE_CHECKING, TypeVar, Optional, Tuple
if TYPE_CHECKING:
from ica_typing import IcaNewMessage, IcaClient, ConfigData
from ica_typing import NewMessage, IcaClient, ConfigData
CONFIG_DATA: ConfigData
else:
CONFIG_DATA = None
IcaNewMessage = TypeVar("NewMessage")
NewMessage = TypeVar("NewMessage")
IcaClient = TypeVar("IcaClient")
_version_ = "2.2.0-rs"
@ -57,7 +57,7 @@ def format_hit_count(count: int) -> str:
return count_str
def wrap_request(url: str, msg: IcaNewMessage, client: IcaClient) -> Optional[dict]:
def wrap_request(url: str, msg: NewMessage, client: IcaClient) -> Optional[dict]:
# if CONFIG_DATA
try:
cookie = CONFIG_DATA["cookie"]
@ -83,7 +83,7 @@ def wrap_request(url: str, msg: IcaNewMessage, client: IcaClient) -> Optional[di
return response.json()
def bmcl_dashboard(msg: IcaNewMessage, client: IcaClient) -> None:
def bmcl_dashboard(msg: NewMessage, client: IcaClient) -> None:
req_time = time.time()
# 记录请求时间
data = wrap_request("https://bd.bangbang93.com/openbmclapi/metric/dashboard", msg, client)
@ -196,7 +196,7 @@ def bmcl_rank_general(msg, client):
client.send_message(reply)
def bmcl_rank(msg: IcaNewMessage, client: IcaClient, name: str) -> None:
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)
@ -247,7 +247,7 @@ help = """/bmcl -> dashboard
"""
def on_ica_message(msg: IcaNewMessage, client: IcaClient) -> None:
def on_message(msg: NewMessage, client: IcaClient) -> None:
if not (msg.is_from_self or msg.is_reply):
if '\n' in msg.content:
return

View File

@ -5,12 +5,12 @@ import traceback
from typing import TYPE_CHECKING, TypeVar
if TYPE_CHECKING:
from ica_typing import IcaNewMessage, IcaClient
from ica_typing import NewMessage, IcaClient
else:
IcaNewMessage = TypeVar("NewMessage")
NewMessage = TypeVar("NewMessage")
IcaClient = TypeVar("IcaClient")
def safe_eval(code: str, msg: IcaNewMessage) -> str:
def safe_eval(code: str, msg: NewMessage) -> str:
try:
# code = code.replace('help', '坏东西!\n')
# code = code.replace('bytes', '坏东西!\n')
@ -71,7 +71,7 @@ def safe_eval(code: str, msg: IcaNewMessage) -> str:
return result
def on_message(message: IcaNewMessage, client: IcaClient) -> None:
def on_message(message: NewMessage, client: IcaClient) -> None:
if not (message.is_from_self or message.is_reply):
if message.content.startswith("/="):
code = message.content[2:]

View File

@ -73,16 +73,7 @@ impl BotConfig {
ret
}
pub fn new_from_cli() -> Self {
// let config_file_path = env::args().nth(1).expect("No config path given");
// -c <config_file_path>
let mut config_file_path = String::new();
let mut args = env::args();
while let Some(arg) = args.next() {
if arg == "-c" {
config_file_path = args.next().expect("No config path given");
break;
}
}
let config_file_path = env::args().nth(1).expect("No config path given");
Self::new_from_path(config_file_path)
}

View File

@ -1,7 +1,7 @@
use crate::data_struct::ica::files::MessageFile;
use crate::data_struct::ica::{MessageId, RoomId, UserId};
use chrono::DateTime;
use chrono::{DateTime, NaiveDateTime};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value as JsonValue};
use tracing::warn;

View File

@ -1,6 +1,6 @@
use std::fmt::Display;
use chrono::DateTime;
use chrono::{DateTime, NaiveDateTime};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;

View File

@ -1,61 +0,0 @@
pub type ClientResult<T, E> = Result<T, E>;
#[derive(Debug)]
pub enum IcaError {
/// Socket IO 链接错误
SocketIoError(rust_socketio::error::Error),
}
#[derive(Debug)]
pub enum MatrixError {
/// Homeserver Url 错误
HomeserverUrlError(url::ParseError),
/// Http 请求错误
HttpError(matrix_sdk::HttpError),
/// Matrix Error
MatrixError(matrix_sdk::Error),
}
impl From<rust_socketio::Error> for IcaError {
fn from(e: rust_socketio::Error) -> Self { IcaError::SocketIoError(e) }
}
impl From<matrix_sdk::Error> for MatrixError {
fn from(e: matrix_sdk::Error) -> Self { MatrixError::MatrixError(e) }
}
impl std::fmt::Display for IcaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IcaError::SocketIoError(e) => write!(f, "Socket IO 链接错误: {}", e),
}
}
}
impl std::error::Error for IcaError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
IcaError::SocketIoError(e) => Some(e),
}
}
}
impl std::fmt::Display for MatrixError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MatrixError::HomeserverUrlError(e) => write!(f, "Homeserver Url 错误: {}", e),
MatrixError::HttpError(e) => write!(f, "Http 请求错误: {}", e),
MatrixError::MatrixError(e) => write!(f, "Matrix Error: {}", e),
}
}
}
impl std::error::Error for MatrixError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
MatrixError::HomeserverUrlError(e) => Some(e),
MatrixError::HttpError(e) => Some(e),
MatrixError::MatrixError(e) => Some(e),
}
}
}

View File

@ -4,19 +4,14 @@ pub mod events;
use futures_util::FutureExt;
use rust_socketio::asynchronous::{Client, ClientBuilder};
use rust_socketio::{Event, Payload, TransportType};
use tracing::{event, span, Level};
use tracing::{event, info, Level};
use crate::config::IcaConfig;
use crate::error::{ClientResult, IcaError};
use crate::{wrap_any_callback, wrap_callback, StopGetter};
use crate::{wrap_any_callback, wrap_callback};
pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientResult<(), IcaError> {
let span = span!(Level::INFO, "Icalingua Client");
let _enter = span.enter();
event!(Level::INFO, "ica-async-rs v{} initing", crate::ICA_VERSION);
let socket = match ClientBuilder::new(config.host.clone())
pub async fn start_ica(config: &IcaConfig, stop_reciver: tokio::sync::oneshot::Receiver<()>) {
event!(Level::INFO, "ica-async-rs v{} start ica", crate::ICA_VERSION);
let socket = ClientBuilder::new(config.host.clone())
.transport_type(TransportType::Websocket)
.on_any(wrap_any_callback!(events::any_event))
.on("requireAuth", wrap_callback!(client::sign_callback))
@ -32,62 +27,27 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
.on("deleteMessage", wrap_callback!(events::delete_message))
.connect()
.await
{
Ok(client) => {
event!(Level::INFO, "socketio connected");
client
}
Err(e) => {
event!(Level::ERROR, "socketio connect failed: {}", e);
return Err(IcaError::SocketIoError(e));
}
};
.expect("Connection failed");
info!("Connected");
if config.notice_start {
for room in config.notice_room.iter() {
let startup_msg = crate::data_struct::ica::messages::SendMessage::new(
format!("shenbot v {}\nica-async-rs v{}", crate::VERSION, crate::ICA_VERSION),
format!("shenbot v {}\nica-async-rs bot v{}", crate::VERSION, crate::ICA_VERSION),
*room,
None,
);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
event!(Level::INFO, "发送启动消息到房间: {}", room);
info!("发送启动消息到房间: {}", room);
if let Err(e) =
socket.emit("sendMessage", serde_json::to_value(startup_msg).unwrap()).await
{
event!(Level::INFO, "启动信息发送失败 房间:{}|e:{}", room, e);
info!("启动信息发送失败 房间:{}|e:{}", room, e);
}
}
}
// 等待停止信号
stop_reciver.await.ok();
event!(Level::INFO, "socketio client stopping");
match socket.disconnect().await {
Ok(_) => {
event!(Level::INFO, "socketio client stopped");
Ok(())
}
Err(e) => {
// 单独处理 SocketIoError(IncompleteResponseFromEngineIo(WebsocketError(AlreadyClosed)))
match e {
rust_socketio::Error::IncompleteResponseFromEngineIo(inner_e) => {
if inner_e.to_string().contains("AlreadyClosed") {
event!(Level::INFO, "socketio client stopped");
return Ok(());
} else {
event!(Level::ERROR, "socketio client stopped with error: {:?}", inner_e);
Err(IcaError::SocketIoError(
rust_socketio::Error::IncompleteResponseFromEngineIo(inner_e),
))
}
}
e => {
event!(Level::ERROR, "socketio client stopped with error: {}", e);
Err(IcaError::SocketIoError(e))
}
}
}
}
socket.disconnect().await.expect("Disconnect failed");
}

View File

@ -2,7 +2,6 @@ use std::time::Duration;
mod config;
mod data_struct;
mod error;
#[cfg(feature = "ica")]
mod ica;
#[cfg(feature = "matrix")]
@ -11,7 +10,7 @@ mod py;
mod status;
use config::BotConfig;
use tracing::{event, info, span, Level};
use tracing::{event, info, Level};
pub static mut MAIN_STATUS: status::BotStatus = status::BotStatus {
config: None,
@ -21,8 +20,6 @@ pub static mut MAIN_STATUS: status::BotStatus = status::BotStatus {
pub type MainStatus = status::BotStatus;
pub type StopGetter = tokio::sync::oneshot::Receiver<()>;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ICA_VERSION: &str = "1.4.0";
pub const MATRIX_VERSION: &str = "0.1.0";
@ -43,22 +40,8 @@ macro_rules! wrap_any_callback {
#[tokio::main]
async fn main() {
// -d -> debug
// none -> info
let level = {
let args = std::env::args();
if args.collect::<Vec<String>>().contains(&"-d".to_string()) {
Level::DEBUG
} else {
Level::INFO
}
};
tracing_subscriber::fmt().with_max_level(level).init();
let span = span!(Level::INFO, "Shenbot Main");
let _enter = span.enter();
event!(Level::INFO, "shenbot-async-rs v{} starting", VERSION);
tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).init();
event!(Level::INFO, "shenbot-async-rs v{} main", VERSION);
let bot_config = BotConfig::new_from_cli();
MainStatus::static_init(bot_config);
@ -67,30 +50,16 @@ async fn main() {
py::init_py();
// 准备一个用于停止 socket 的变量
event!(Level::INFO, "启动 ICA");
let (ica_send, ica_recv) = tokio::sync::oneshot::channel::<()>();
let (send, recv) = tokio::sync::oneshot::channel::<()>();
if bot_config.check_ica() {
event!(Level::INFO, "启动 ica");
info!("启动 ica");
let config = bot_config.ica();
tokio::spawn(async move {
ica::start_ica(&config, ica_recv).await.unwrap();
ica::start_ica(&config, recv).await;
});
} else {
event!(Level::INFO, "未启用 ica");
}
event!(Level::INFO, "启动 Matrix");
let (matrix_send, matrix_recv) = tokio::sync::oneshot::channel::<()>();
if bot_config.check_matrix() {
event!(Level::INFO, "启动 Matrix");
let config = bot_config.matrix();
tokio::spawn(async move {
matrix::start_matrix(&config, matrix_recv).await.unwrap();
});
} else {
event!(Level::INFO, "未启用 Matrix");
info!("未启用 ica");
}
tokio::time::sleep(Duration::from_secs(2)).await;
@ -99,8 +68,7 @@ async fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
ica_send.send(()).ok();
matrix_send.send(()).ok();
send.send(()).ok();
info!("Disconnected");
}

View File

@ -1,152 +1 @@
pub mod events;
use std::str::FromStr;
use matrix_sdk::{
config::SyncSettings,
ruma::{
api::client::message::send_message_event,
events::room::message::{
AddMentions, ForwardThread, MessageType, OriginalSyncRoomMessageEvent,
RoomMessageEventContent,
},
OwnedRoomId, TransactionId,
},
Client, Room, RoomState,
};
use tracing::{event, span, Level};
use url::Url;
use crate::config::MatrixConfig;
use crate::error::{ClientResult, MatrixError};
use crate::StopGetter;
pub async fn start_matrix(
config: &MatrixConfig,
stop_reciver: StopGetter,
) -> ClientResult<(), MatrixError> {
let span = span!(Level::INFO, "Matrix Client");
let _enter = span.enter();
let homeserver_url = match Url::parse(&config.home_server) {
Ok(url) => url,
Err(e) => {
event!(Level::ERROR, "Homeserver Url 错误: {}", e);
return Err(MatrixError::HomeserverUrlError(e));
}
};
let password = &config.bot_password;
let username = &config.bot_id;
let client = match Client::new(homeserver_url).await {
Ok(client) => {
event!(Level::INFO, "Logged in as {}", username);
client
}
Err(error) => {
event!(Level::ERROR, "Failed to log in as {}: {}", username, error);
return Err(MatrixError::HttpError(error));
}
};
let display_name = format!("shenbot-matrix v{}", crate::MATRIX_VERSION);
match client
.matrix_auth()
.login_username(&username, &password)
.initial_device_display_name(&display_name)
.await
{
Ok(_) => {
event!(Level::INFO, "Logged in as {}", username);
}
Err(error) => {
event!(Level::ERROR, "Failed to log in as {}: {}", username, error);
return Err(MatrixError::MatrixError(error));
}
}
// 发送启动消息
if config.notice_start {
for room in config.notice_room.iter() {
let startup_msg = RoomMessageEventContent::text_plain(format!(
"shenbot v {}\nmatrix-rs v{} started!",
crate::VERSION,
crate::MATRIX_VERSION
));
let startup_req: send_message_event::v3::Request =
send_message_event::v3::Request::new(
OwnedRoomId::from_str(&room).unwrap(),
TransactionId::new(),
&startup_msg,
)
.unwrap();
event!(Level::INFO, "发送启动消息到房间: {}", room);
if let Err(e) = client.send::<send_message_event::v3::Request>(startup_req, None).await
{
event!(Level::INFO, "启动信息发送失败 房间:{}|e:{}", room, e);
}
}
} else {
event!(Level::INFO, "未启用启动消息");
}
client.add_event_handler(on_room_message);
match client.sync_once(SyncSettings::new()).await {
Ok(_) => {
event!(Level::INFO, "Synced");
}
Err(error) => {
event!(Level::ERROR, "Failed to sync: {}", error);
return Err(MatrixError::MatrixError(error));
}
}
client.sync(SyncSettings::default()).await?;
// while stop_reciver.await.is_err() {
// event!(Level::INFO, "Matrix client is running");
// tokio::time::sleep(std::time::Duration::from_secs(1)).await;
// }
event!(Level::INFO, "Matrix is not implemented yet");
stop_reciver.await.ok();
event!(Level::INFO, "Matrix client stopping");
// some stop
event!(Level::INFO, "Matrix client stopped");
Ok(())
}
pub async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
// We only want to listen to joined rooms.
if room.state() != RoomState::Joined {
return;
}
// We only want to log text messages.
let MessageType::Text(msgtype) = &event.content.msgtype else {
return;
};
// 匹配消息
// /bot
if msgtype.body == "/bot" {
let pong = format!("shenbot v {}\nmatrix-rs v{}", crate::VERSION, crate::MATRIX_VERSION);
let reply = RoomMessageEventContent::text_plain(pong);
let reply = reply.make_reply_to(
&event.into_full_event(room.room_id().to_owned()),
ForwardThread::No,
AddMentions::No,
);
room.send(reply).await.expect("Failed to send message");
return;
}
// 发给 Python 处理剩下的
}

View File

@ -1 +0,0 @@

View File

@ -73,11 +73,6 @@ pub fn verify_plugins() {
}
}
pub const ICA_NEW_MESSAGE_FUNC: &str = "on_ica_message";
pub const ICA_DELETE_MESSAGE_FUNC: &str = "on_ica_delete_message";
pub const MATRIX_NEW_MESSAGE_FUNC: &str = "on_matrix_message";
/// 执行 new message 的 python 插件
pub async fn ica_new_message_py(message: &NewMessage, client: &Client) {
// 验证插件是否改变
@ -85,17 +80,15 @@ pub async fn ica_new_message_py(message: &NewMessage, client: &Client) {
let plugins = PyStatus::get_files();
for (path, plugin) in plugins.iter() {
let msg = class::ica::NewMessagePy::new(message);
let client = class::ica::IcaClientPy::new(client);
let msg = class::NewMessagePy::new(message);
let client = class::IcaClientPy::new(client);
let args = (msg, client);
// 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱)
tokio::spawn(async move {
Python::with_gil(|py| {
if let Some(py_func) =
get_func(plugin.py_module.as_ref(py), path, ICA_NEW_MESSAGE_FUNC)
{
if let Some(py_func) = get_func(plugin.py_module.as_ref(py), path, "on_message") {
if let Err(e) = py_func.call1(args) {
warn!("failed to call function<{}>: {:?}", ICA_NEW_MESSAGE_FUNC, e);
warn!("failed to call function<on_message>: {:?}", e);
}
}
})
@ -109,34 +102,15 @@ pub async fn ica_delete_message_py(msg_id: MessageId, client: &Client) {
let plugins = PyStatus::get_files();
for (path, plugin) in plugins.iter() {
let msg_id = msg_id.clone();
let client = class::ica::IcaClientPy::new(client);
let client = class::IcaClientPy::new(client);
let args = (msg_id.clone(), client);
tokio::spawn(async move {
Python::with_gil(|py| {
if let Some(py_func) =
get_func(plugin.py_module.as_ref(py), path, ICA_DELETE_MESSAGE_FUNC)
get_func(plugin.py_module.as_ref(py), path, "on_delete_message")
{
if let Err(e) = py_func.call1(args) {
warn!("failed to call function<{}>: {:?}", ICA_DELETE_MESSAGE_FUNC, e);
}
}
})
});
}
}
pub async fn matrix_new_message_py() {
verify_plugins();
let plugins = PyStatus::get_files();
for (path, plugin) in plugins.iter() {
tokio::spawn(async move {
Python::with_gil(|py| {
if let Some(py_func) =
get_func(plugin.py_module.as_ref(py), path, MATRIX_NEW_MESSAGE_FUNC)
{
if let Err(e) = py_func.call0() {
warn!("failed to call function<{}>: {:?}", MATRIX_NEW_MESSAGE_FUNC, e);
warn!("failed to call function<on_delete_message>: {:?}", e);
}
}
})

View File

@ -1,7 +1,237 @@
pub mod ica;
use pyo3::prelude::*;
use rust_socketio::asynchronous::Client;
use tokio::runtime::Runtime;
use toml::Value as TomlValue;
use tracing::{debug, info, warn};
use crate::data_struct::ica::messages::{
DeleteMessage, MessageTrait, NewMessage, ReplyMessage, SendMessage,
};
use crate::data_struct::ica::MessageId;
use crate::ica::client::{delete_message, send_message};
use crate::MainStatus;
#[pyclass]
#[pyo3(name = "IcaStatus")]
pub struct IcaStatusPy {}
#[pymethods]
impl IcaStatusPy {
#[new]
pub fn py_new() -> Self { Self {} }
#[getter]
pub fn get_qq_login(&self) -> bool { MainStatus::global_ica_status().qq_login }
#[getter]
pub fn get_online(&self) -> bool { MainStatus::global_ica_status().online_status.online }
#[getter]
pub fn get_self_id(&self) -> i64 { MainStatus::global_ica_status().online_status.qqid }
#[getter]
pub fn get_nick_name(&self) -> String {
MainStatus::global_ica_status().online_status.nick.clone()
}
#[getter]
pub fn get_loaded_messages_count(&self) -> u64 {
MainStatus::global_ica_status().current_loaded_messages_count
}
#[getter]
pub fn get_ica_version(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.ica_version.clone()
}
#[getter]
pub fn get_os_info(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.os_info.clone()
}
#[getter]
pub fn get_resident_set_size(&self) -> String {
MainStatus::global_ica_status()
.online_status
.icalingua_info
.resident_set_size
.clone()
}
#[getter]
pub fn get_heap_used(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.heap_used.clone()
}
#[getter]
pub fn get_load(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.load.clone()
}
}
impl Default for IcaStatusPy {
fn default() -> Self { Self::new() }
}
impl IcaStatusPy {
pub fn new() -> Self { Self {} }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "NewMessage")]
pub struct NewMessagePy {
pub msg: NewMessage,
}
#[pymethods]
impl NewMessagePy {
pub fn reply_with(&self, content: String) -> SendMessagePy {
SendMessagePy::new(self.msg.reply_with(&content))
}
pub fn as_deleted(&self) -> DeleteMessagePy { DeleteMessagePy::new(self.msg.as_deleted()) }
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
#[getter]
pub fn get_id(&self) -> MessageId { self.msg.msg_id().clone() }
#[getter]
pub fn get_content(&self) -> String { self.msg.content().clone() }
#[getter]
pub fn get_sender_id(&self) -> i64 { self.msg.sender_id() }
#[getter]
pub fn get_is_from_self(&self) -> bool { self.msg.is_from_self() }
#[getter]
pub fn get_is_reply(&self) -> bool { self.msg.is_reply() }
}
impl NewMessagePy {
pub fn new(msg: &NewMessage) -> Self { Self { msg: msg.clone() } }
}
#[pyclass]
#[pyo3(name = "ReplyMessage")]
pub struct ReplyMessagePy {
pub msg: ReplyMessage,
}
#[pymethods]
impl ReplyMessagePy {
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
}
impl ReplyMessagePy {
pub fn new(msg: ReplyMessage) -> Self { Self { msg } }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "SendMessage")]
pub struct SendMessagePy {
pub msg: SendMessage,
}
#[pymethods]
impl SendMessagePy {
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
/// 设置消息内容
/// 用于链式调用
pub fn with_content(&mut self, content: String) -> Self {
self.msg.content = content;
self.clone()
}
#[getter]
pub fn get_content(&self) -> String { self.msg.content.clone() }
#[setter]
pub fn set_content(&mut self, content: String) { self.msg.content = content; }
}
impl SendMessagePy {
pub fn new(msg: SendMessage) -> Self { Self { msg } }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "DeleteMessage")]
pub struct DeleteMessagePy {
pub msg: DeleteMessage,
}
#[pymethods]
impl DeleteMessagePy {
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
}
impl DeleteMessagePy {
pub fn new(msg: DeleteMessage) -> Self { Self { msg } }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "IcaClient")]
pub struct IcaClientPy {
pub client: Client,
}
#[pymethods]
impl IcaClientPy {
pub fn send_message(&self, message: SendMessagePy) -> bool {
tokio::task::block_in_place(|| {
let rt = Runtime::new().unwrap();
rt.block_on(send_message(&self.client, &message.msg))
})
}
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 {
tokio::task::block_in_place(|| {
let rt = Runtime::new().unwrap();
rt.block_on(delete_message(&self.client, &message.msg))
})
}
/// 仅作占位
/// (因为目前来说, rust调用 Python端没法启动一个异步运行时
/// 所以只能 tokio::task::block_in_place 转换成同步调用)
// #[staticmethod]
// pub fn send_message_a(
// py: Python,
// client: IcaClientPy,
// message: SendMessagePy,
// ) -> PyResult<&PyAny> {
// pyo3_asyncio::tokio::future_into_py(py, async move {
// Ok(send_message(&client.client, &message.msg).await)
// })
// }
#[getter]
pub fn get_status(&self) -> IcaStatusPy { IcaStatusPy::new() }
#[getter]
pub fn get_version(&self) -> String { crate::VERSION.to_string() }
#[getter]
pub fn get_ica_version(&self) -> String { crate::ICA_VERSION.to_string() }
#[getter]
pub fn get_matrix_version(&self) -> String { crate::MATRIX_VERSION.to_string() }
pub fn debug(&self, content: String) {
debug!("{}", content);
}
pub fn info(&self, content: String) {
info!("{}", content);
}
pub fn warn(&self, content: String) {
warn!("{}", content);
}
}
impl IcaClientPy {
pub fn new(client: &Client) -> Self {
Self {
client: client.clone(),
}
}
}
#[derive(Clone)]
#[pyclass]

View File

@ -1,239 +0,0 @@
use pyo3::prelude::*;
use rust_socketio::asynchronous::Client;
use tokio::runtime::Runtime;
use tracing::{debug, info, warn};
use crate::data_struct::ica::messages::{
DeleteMessage, MessageTrait, NewMessage, ReplyMessage, SendMessage,
};
use crate::data_struct::ica::{MessageId, RoomId, RoomIdTrait};
use crate::ica::client::{delete_message, send_message};
use crate::MainStatus;
#[pyclass]
#[pyo3(name = "IcaStatus")]
pub struct IcaStatusPy {}
#[pymethods]
impl IcaStatusPy {
#[new]
pub fn py_new() -> Self { Self {} }
#[getter]
pub fn get_qq_login(&self) -> bool { MainStatus::global_ica_status().qq_login }
#[getter]
pub fn get_online(&self) -> bool { MainStatus::global_ica_status().online_status.online }
#[getter]
pub fn get_self_id(&self) -> i64 { MainStatus::global_ica_status().online_status.qqid }
#[getter]
pub fn get_nick_name(&self) -> String {
MainStatus::global_ica_status().online_status.nick.clone()
}
#[getter]
pub fn get_loaded_messages_count(&self) -> u64 {
MainStatus::global_ica_status().current_loaded_messages_count
}
#[getter]
pub fn get_ica_version(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.ica_version.clone()
}
#[getter]
pub fn get_os_info(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.os_info.clone()
}
#[getter]
pub fn get_resident_set_size(&self) -> String {
MainStatus::global_ica_status()
.online_status
.icalingua_info
.resident_set_size
.clone()
}
#[getter]
pub fn get_heap_used(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.heap_used.clone()
}
#[getter]
pub fn get_load(&self) -> String {
MainStatus::global_ica_status().online_status.icalingua_info.load.clone()
}
}
impl Default for IcaStatusPy {
fn default() -> Self { Self::new() }
}
impl IcaStatusPy {
pub fn new() -> Self { Self {} }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "NewMessage")]
pub struct NewMessagePy {
pub msg: NewMessage,
}
#[pymethods]
impl NewMessagePy {
pub fn reply_with(&self, content: String) -> SendMessagePy {
SendMessagePy::new(self.msg.reply_with(&content))
}
pub fn as_deleted(&self) -> DeleteMessagePy { DeleteMessagePy::new(self.msg.as_deleted()) }
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
#[getter]
pub fn get_id(&self) -> MessageId { self.msg.msg_id().clone() }
#[getter]
pub fn get_content(&self) -> String { self.msg.content().clone() }
#[getter]
pub fn get_sender_id(&self) -> i64 { self.msg.sender_id() }
#[getter]
pub fn get_is_from_self(&self) -> bool { self.msg.is_from_self() }
#[getter]
pub fn get_is_reply(&self) -> bool { self.msg.is_reply() }
#[getter]
pub fn get_is_room_msg(&self) -> bool { self.msg.room_id.is_room() }
#[getter]
pub fn get_is_chat_msg(&self) -> bool { self.msg.room_id.is_chat() }
#[getter]
pub fn get_room_id(&self) -> RoomId { self.msg.room_id.clone() }
}
impl NewMessagePy {
pub fn new(msg: &NewMessage) -> Self { Self { msg: msg.clone() } }
}
#[pyclass]
#[pyo3(name = "ReplyMessage")]
pub struct ReplyMessagePy {
pub msg: ReplyMessage,
}
#[pymethods]
impl ReplyMessagePy {
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
}
impl ReplyMessagePy {
pub fn new(msg: ReplyMessage) -> Self { Self { msg } }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "SendMessage")]
pub struct SendMessagePy {
pub msg: SendMessage,
}
#[pymethods]
impl SendMessagePy {
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
/// 设置消息内容
/// 用于链式调用
pub fn with_content(&mut self, content: String) -> Self {
self.msg.content = content;
self.clone()
}
#[getter]
pub fn get_content(&self) -> String { self.msg.content.clone() }
#[setter]
pub fn set_content(&mut self, content: String) { self.msg.content = content; }
}
impl SendMessagePy {
pub fn new(msg: SendMessage) -> Self { Self { msg } }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "DeleteMessage")]
pub struct DeleteMessagePy {
pub msg: DeleteMessage,
}
#[pymethods]
impl DeleteMessagePy {
pub fn __str__(&self) -> String { format!("{:?}", self.msg) }
}
impl DeleteMessagePy {
pub fn new(msg: DeleteMessage) -> Self { Self { msg } }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "IcaClient")]
pub struct IcaClientPy {
pub client: Client,
}
#[pymethods]
impl IcaClientPy {
pub fn send_message(&self, message: SendMessagePy) -> bool {
tokio::task::block_in_place(|| {
let rt = Runtime::new().unwrap();
rt.block_on(send_message(&self.client, &message.msg))
})
}
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 {
tokio::task::block_in_place(|| {
let rt = Runtime::new().unwrap();
rt.block_on(delete_message(&self.client, &message.msg))
})
}
/// 仅作占位
/// (因为目前来说, rust调用 Python端没法启动一个异步运行时
/// 所以只能 tokio::task::block_in_place 转换成同步调用)
// #[staticmethod]
// pub fn send_message_a(
// py: Python,
// client: IcaClientPy,
// message: SendMessagePy,
// ) -> PyResult<&PyAny> {
// pyo3_asyncio::tokio::future_into_py(py, async move {
// Ok(send_message(&client.client, &message.msg).await)
// })
// }
#[getter]
pub fn get_status(&self) -> IcaStatusPy { IcaStatusPy::new() }
#[getter]
pub fn get_version(&self) -> String { crate::VERSION.to_string() }
#[getter]
pub fn get_ica_version(&self) -> String { crate::ICA_VERSION.to_string() }
#[getter]
pub fn get_matrix_version(&self) -> String { crate::MATRIX_VERSION.to_string() }
pub fn debug(&self, content: String) {
debug!("{}", content);
}
pub fn info(&self, content: String) {
info!("{}", content);
}
pub fn warn(&self, content: String) {
warn!("{}", content);
}
}
impl IcaClientPy {
pub fn new(client: &Client) -> Self {
Self {
client: client.clone(),
}
}
}

12
news.md
View File

@ -1,15 +1,5 @@
# 更新日志
## 0.5.3
修复了 Icalingua 断开时 如果 socketio 已经断开会导致程序 返回 Error 的问题
以及还有一些别的修复就是了
- Python 端修改
- `on_message` -> `on_ica_message`
- `on_delete_message` -> `on_ica_delete_message`
- 添加 `on_matrix_message`
## 0.5.1/2
重构了一整波, 还没改 `ica-typing.py` 的代码
@ -42,7 +32,7 @@
- `IcalinguaStatus.current_loaded_messages_count`
- 用于以后加载信息计数
- 修改
- `py::class::IcaStatusPy`
- `py::class::IcaStatusPy`
- 大部分方法从手动 `unsafe` + `Option`
- 改成直接调用 `IcalinguaStatus` 的方法
- `IcalinguaStatus`