Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support qq guild bot. #29

Merged
merged 3 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "blooming"
version = "0.4.2"
version = "0.5.0"
authors = ["RinChanNOW <[email protected]>"]
description = "BT/PT 站订阅更新通知器。"
homepage = "https://github.com/RinChanNOWWW/blooming"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ BT/PT 站 RSS 更新通知器。

## 通知方式

- QQ 官方频道机器人。
- QQ 机器人: [go-cqhttp](https://github.com/Mrs4s/go-cqhttp).

## Install
Expand Down
6 changes: 6 additions & 0 deletions examples/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ dms = [114514, 1919810]
groups = [114514, 1919810]
delay = 200
with_torrent = true

[qq_guild]
app_id = "app id"
app_secret = "app secret"
channel_id = "channel id"
sandbox = true
17 changes: 16 additions & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,26 @@ pub struct QQBotConfig {
pub with_torrent: bool,
}

#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct QQGuildBotConfig {
/// The app id of the bot.
pub app_id: String,
/// The app secret of the bot.
pub app_secret: String,
/// The channel id of the channel to notify.
pub channel_id: String,
/// If use sandbox API.
pub sandbox: bool,
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
/// config of qq bot.
pub qq: QQBotConfig,
pub qq: Option<QQBotConfig>,
/// config of qq guild bot.
pub qq_guild: Option<QQGuildBotConfig>,
/// mikan
pub mikan: Option<MikanConfig>,
/// byrbt
Expand Down
52 changes: 40 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use std::env::current_dir;
use std::fs::File;
use std::path::PathBuf;
use std::sync::Arc;

use backon::ConstantBuilder;
use backon::Retryable;
Expand All @@ -27,28 +26,44 @@ use blooming::source::SourceFactory;
use blooming::source::SourcePtr;
use blooming::ClapConfig;
use blooming::Config;
use blooming::Notifier;
use blooming::QQGuildNotifier;
use blooming::QQNotifier;
use blooming::Result;
use chrono::Local;
use clap::Parser;
use daemonize::Daemonize;
use log::error;
use log::info;
use reqwest::Client;
use tokio::task::JoinHandle;

const VERSION: &str = env!("CARGO_PKG_VERSION");

async fn main_impl(config: Config) -> Result<()> {
let notifier = notifier::QQNotifier::new(config.qq.clone());

let mut factory = SourceFactory::default();
register(&mut factory, &config)?;

activate_sources(factory, Arc::new(notifier)).await
let client = Client::new();
let mut handles = Vec::new();

if let Some(qq) = config.qq.clone() {
let notifier = notifier::QQNotifier::new(client.clone(), qq);
handles.extend(activate_qq_notifier(&factory, notifier));
}
if let Some(qq_guild) = config.qq_guild.clone() {
let notifier = notifier::QQGuildNotifier::new(client, qq_guild);
handles.extend(activate_qq_guild_notifier(&factory, notifier));
}

futures::future::join_all(handles).await;

Ok(())
}

async fn activate_sources(factory: SourceFactory, notifier: Arc<QQNotifier>) -> Result<()> {
fn activate_qq_notifier(factory: &SourceFactory, notifier: QQNotifier) -> Vec<JoinHandle<()>> {
let sources = factory.sources();
let handles = sources
sources
.iter()
.map(|source| {
let source = source.clone();
Expand All @@ -57,14 +72,27 @@ async fn activate_sources(factory: SourceFactory, notifier: Arc<QQNotifier>) ->
run(source, n).await;
})
})
.collect::<Vec<_>>();

futures::future::join_all(handles).await;
.collect::<Vec<_>>()
}

Ok(())
fn activate_qq_guild_notifier(
factory: &SourceFactory,
notifier: QQGuildNotifier,
) -> Vec<JoinHandle<()>> {
let sources = factory.sources();
sources
.iter()
.map(|source| {
let source = source.clone();
let n = notifier.clone();
tokio::spawn(async move {
run(source, n).await;
})
})
.collect::<Vec<_>>()
}

async fn run(source: SourcePtr, notifier: Arc<QQNotifier>) {
async fn run<T: Notifier>(source: SourcePtr, mut notifier: T) {
if source.check_connection().await.is_err() {
error!("Check connection of '{}' failed", source.name());
} else {
Expand Down Expand Up @@ -93,7 +121,7 @@ async fn run(source: SourcePtr, notifier: Arc<QQNotifier>) {
if pub_time > acc { pub_time } else { acc }
});

// notify by qq bot
// notify
notifier.notify(&source.name(), new_items.clone()).await?;
}
};
Expand Down
12 changes: 11 additions & 1 deletion src/notifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,15 @@
// limitations under the License.

mod qq;
mod qq_guild;

pub use qq::*;
pub use qq::QQNotifier;
pub use qq_guild::QQGuildNotifier;

use crate::source::Item;
use crate::Result;

#[async_trait::async_trait]
pub trait Notifier: Sync + Send + Clone {
async fn notify(&mut self, source: &str, items: Vec<Item>) -> Result<()>;
}
116 changes: 57 additions & 59 deletions src/notifier/qq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::sync::Arc;
use std::time::Duration;

use log::error;
use log::info;
use reqwest::Client;
use serde::Deserialize;
use serde::Serialize;

use crate::source::Item;
use crate::Notifier;
use crate::QQBotConfig;
use crate::Result;

#[derive(Clone)]
pub struct QQNotifier {
inner: Arc<Notifier>,
}

struct Notifier {
client: Client,
conf: QQBotConfig,
}
Expand Down Expand Up @@ -63,48 +58,39 @@ struct Data {
content: String,
}

impl Notifier {
pub fn new(conf: QQBotConfig) -> Self {
Self {
client: Client::new(),
conf,
}
}
}

impl QQNotifier {
pub fn new(conf: QQBotConfig) -> Self {
Self {
inner: Arc::new(Notifier::new(conf)),
}
}

pub async fn notify(&self, source: &str, items: Vec<Item>) -> Result<()> {
#[async_trait::async_trait]
impl Notifier for QQNotifier {
async fn notify(&mut self, source: &str, items: Vec<Item>) -> Result<()> {
let delay = self.conf.delay;
let pm_handle = {
let notifier = self.inner.clone();
let client = self.client.clone();
let pm_items = items.clone();
let source = source.to_string();
let mut msgs = Vec::with_capacity(pm_items.len() * 2);
for item in pm_items.iter() {
msgs.extend(self.messages(&source, item));
}
let msgs = self.private_messages(msgs);
let url = format!("{}/send_private_forward_msg", self.conf.api);
async move {
let mut msgs = Vec::with_capacity(pm_items.len() * 2);
for item in pm_items.iter() {
msgs.extend(Self::wrap_item(&notifier, &source, item));
}
if let Err(e) = Self::send_private_msg(&notifier, msgs).await {
if let Err(e) = Self::send_messages(client, &url, msgs, delay).await {
error!("Send private msg failed: {}", e);
}
}
};

let dm_handle = {
let notifier = self.inner.clone();
let client = self.client.clone();
let gp_items = items;
let source = source.to_string();
let mut msgs = Vec::with_capacity(gp_items.len() * 2);
for item in gp_items.iter() {
msgs.extend(self.messages(&source, item));
}
let msgs = self.group_messages(msgs);
let url = format!("{}/send_group_forward_msg", self.conf.api);
async move {
let mut msgs = Vec::with_capacity(gp_items.len() * 2);
for item in gp_items.iter() {
msgs.extend(Self::wrap_item(&notifier, &source, item));
}
if let Err(e) = Self::send_group_msg(&notifier, msgs).await {
if let Err(e) = Self::send_messages(client, &url, msgs, delay).await {
error!("Send group msg failed: {}", e);
}
}
Expand All @@ -114,54 +100,66 @@ impl QQNotifier {

Ok(())
}
}

impl QQNotifier {
pub fn new(client: Client, conf: QQBotConfig) -> Self {
Self { client, conf }
}

fn wrap_item(notifier: &Notifier, source: &str, item: &Item) -> Vec<Message> {
fn messages(&self, source: &str, item: &Item) -> Vec<Message> {
let mut messages = vec![Message {
msg_type: "node".to_string(),
data: Data {
sender_name: notifier.conf.name.clone(),
sender_uin: notifier.conf.uin.clone(),
sender_name: self.conf.name.clone(),
sender_uin: self.conf.uin.clone(),
content: format!("{}:\n{} ({})", source, item.title, item.pub_date),
},
}];
if notifier.conf.with_torrent {
if self.conf.with_torrent {
messages.push(Message {
msg_type: "node".to_string(),
data: Data {
sender_name: notifier.conf.name.clone(),
sender_uin: notifier.conf.uin.clone(),
sender_name: self.conf.name.clone(),
sender_uin: self.conf.uin.clone(),
content: item.url.clone(),
},
});
}
messages
}

async fn send_private_msg(notifier: &Notifier, msg: Vec<Message>) -> Result<()> {
let url = format!("{}/send_private_forward_msg", notifier.conf.api);

for user_id in notifier.conf.dms.iter() {
let body = PrivateMsg {
fn private_messages(&self, msg: Vec<Message>) -> Vec<PrivateMsg> {
let mut msgs = Vec::with_capacity(self.conf.dms.len());
for user_id in self.conf.dms.iter() {
msgs.push(PrivateMsg {
user_id: *user_id,
messages: msg.clone(),
};
notifier.client.post(url.clone()).json(&body).send().await?;
info!("Notified user {}", user_id);
tokio::time::sleep(Duration::from_micros(notifier.conf.delay)).await;
});
}
Ok(())
msgs
}

async fn send_group_msg(notifier: &Notifier, msg: Vec<Message>) -> Result<()> {
let url = format!("{}/send_group_forward_msg", notifier.conf.api);
for group_id in notifier.conf.groups.iter() {
let body = GroupMsg {
fn group_messages(&self, msg: Vec<Message>) -> Vec<GroupMsg> {
let mut msgs = Vec::with_capacity(self.conf.groups.len());
for group_id in self.conf.groups.iter() {
msgs.push(GroupMsg {
group_id: *group_id,
messages: msg.clone(),
};
notifier.client.post(url.clone()).json(&body).send().await?;
info!("Notified group {}", group_id);
tokio::time::sleep(Duration::from_micros(notifier.conf.delay)).await;
});
}
msgs
}

async fn send_messages<T: Serialize>(
client: Client,
url: &str,
msgs: Vec<T>,
delay: u64,
) -> Result<()> {
for msg in msgs.iter() {
client.post(url).json(msg).send().await?;
tokio::time::sleep(Duration::from_micros(delay)).await;
}
Ok(())
}
Expand Down
Loading
Loading