Skip to content

Commit

Permalink
feat: support qq guild bot. (#29)
Browse files Browse the repository at this point in the history
* Refactor notifier.

* Add qq guild notifier.

* Add copyright year.
  • Loading branch information
RinChanNOWWW authored Jan 19, 2024
1 parent 3b9c301 commit bb99483
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 75 deletions.
1 change: 1 addition & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ header:
license:
spdx-id: Apache-2.0
copyright-owner: RinChanNOWWW
copyright-year: 2023
paths-ignore:
- "target"
- exmaples
Expand Down
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 <rin_chan_now@outlook.com>"]
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

0 comments on commit bb99483

Please sign in to comment.