Added support for misskey publisher, and switching between publishers via config

This commit is contained in:
emilis 2021-09-01 19:30:20 +02:00
parent 677226a77f
commit bd42f495dc
7 changed files with 864 additions and 94 deletions

792
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,3 +17,5 @@ uuid = { version = "0.8.2", features = ["v4"] }
anyhow = "1.0.41" anyhow = "1.0.41"
mammut = "0.13.0" mammut = "0.13.0"
thiserror = "1.0.26" thiserror = "1.0.26"
misskey = "0.2.0"
url = "2.2.2"

View File

@ -1,6 +1,6 @@
use std::{error::Error, path::Path}; use core::fmt::Debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{error::Error, path::Path};
use telegram_bot::ChatId; use telegram_bot::ChatId;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -10,12 +10,12 @@ pub struct Config {
pub temperature: String, pub temperature: String,
pub top_k: String, pub top_k: String,
pub gpt_code_path: String, pub gpt_code_path: String,
pub fediverse_base_url: String,
pub interval_seconds: MinMax, pub interval_seconds: MinMax,
pub bot_token: String, pub bot_token: String,
pub chat_ref: ChatId, pub chat_ref: ChatId,
pub fediverse_token: Option<mammut::Data>, pub mastodon_token: Option<mammut::Data>,
pub post_buffer: u32, pub post_buffer: u32,
pub publisher: Publisher,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -24,6 +24,18 @@ pub struct MinMax {
pub max: u64, pub max: u64,
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Publisher {
Misskey(FediverseConfig<String>),
Mastodon(FediverseConfig<Option<mammut::Data>>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FediverseConfig<T> {
pub base_url: String,
pub token: T,
}
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {
@ -32,15 +44,18 @@ impl Default for Config {
temperature: String::from("1"), temperature: String::from("1"),
top_k: String::from("40"), top_k: String::from("40"),
gpt_code_path: String::from("./gpt/"), gpt_code_path: String::from("./gpt/"),
fediverse_base_url: String::from("https://lain.com"),
interval_seconds: MinMax { interval_seconds: MinMax {
min: 60 * 30, min: 60 * 30,
max: 60 * 90, max: 60 * 90,
}, },
post_buffer: 5, post_buffer: 5,
fediverse_token: None, mastodon_token: None,
bot_token: "".to_owned(), bot_token: "".to_owned(),
chat_ref: ChatId::new(0), chat_ref: ChatId::new(0),
publisher: Publisher::Misskey(FediverseConfig {
base_url: "".to_string(),
token: "".to_string(),
}),
} }
} }
} }

View File

@ -10,11 +10,13 @@ use rand::Rng;
use telegram_bot::{Api, ChatId, ChatRef, ToChatRef}; use telegram_bot::{Api, ChatId, ChatRef, ToChatRef};
use crate::{ use crate::{
config::{FediverseConfig, Publisher},
publish::MastodonPublisher, publish::MastodonPublisher,
publish::MisskeyPublisher,
selection::{telegram::get_chat_ref, SelectorExt, TelegramSelector}, selection::{telegram::get_chat_ref, SelectorExt, TelegramSelector},
}; };
use futures::{channel::mpsc::channel, sink::unfold, SinkExt, StreamExt, TryStreamExt}; use futures::{SinkExt, StreamExt, TryStreamExt, channel::mpsc::channel, future::Either, sink::unfold};
use model::SampleModelExt; use model::SampleModelExt;
mod config; mod config;
@ -38,34 +40,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
}?; }?;
let app = AppBuilder { let publisher = resolve_publisher(&mut cfg).await?;
client_name: "izzilis",
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
scopes: Scopes::Write,
website: None,
};
let mut registration = Registration::new(cfg.fediverse_base_url.clone());
registration.register(app)?;
let mastodon = if let Some(data) = &cfg.fediverse_token {
Mastodon::from_data(data.clone())
} else {
let url = registration.authorise()?;
println!("{}", url);
let mut buffer = String::new();
stdin().read_line(&mut buffer).await?;
let fedi = registration.create_access_token(buffer)?;
cfg.fediverse_token = Some(fedi.data.clone());
cfg.save(CONFIG_PATH)?;
fedi
};
let publisher = MastodonPublisher::new(mastodon);
let api = Arc::new(Api::new(cfg.bot_token.clone())); let api = Arc::new(Api::new(cfg.bot_token.clone()));
@ -145,7 +120,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
}); });
publisher publisher
.sink_map_err(|e| Box::new(e) as Box<dyn Error>) .sink_map_err(|e| e)
.send_all( .send_all(
&mut receiver &mut receiver
.then(|item| { .then(|item| {
@ -165,3 +140,47 @@ async fn main() -> Result<(), Box<dyn Error>> {
return Ok(()); return Ok(());
} }
async fn resolve_publisher(
config: &mut config::Config,
) -> Result<Either<MisskeyPublisher, MastodonPublisher>, Box<dyn Error>> {
let publisher = match &config.publisher {
config::Publisher::Misskey(cfg) => {
Either::Left(MisskeyPublisher::new(&cfg.base_url, cfg.token.clone())?)
}
config::Publisher::Mastodon(cfg) => {
let app = AppBuilder {
client_name: "izzilis",
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
scopes: Scopes::Write,
website: None,
};
let mut registration = Registration::new(cfg.base_url.clone());
registration.register(app)?;
let mastodon = if let Some(data) = cfg.token.clone() {
Mastodon::from_data(data.clone())
} else {
let url = registration.authorise()?;
println!("{}", url);
let mut buffer = String::new();
stdin().read_line(&mut buffer).await?;
let fedi = registration.create_access_token(buffer)?;
config.publisher = Publisher::Mastodon(FediverseConfig {
base_url: cfg.base_url.clone(),
token: Some(fedi.data.clone()),
});
config.save(CONFIG_PATH)?;
fedi
};
Either::Right(MastodonPublisher::new(mastodon))
}
};
Ok(publisher)
}

View File

@ -1,7 +1,4 @@
use std::{ use std::{error::Error, pin::Pin, task::{Context, Poll}};
pin::Pin,
task::{Context, Poll},
};
use futures::Sink; use futures::Sink;
use mammut::{status_builder::Visibility, Mastodon, StatusBuilder}; use mammut::{status_builder::Visibility, Mastodon, StatusBuilder};
@ -17,7 +14,7 @@ impl MastodonPublisher {
} }
impl Sink<String> for MastodonPublisher { impl Sink<String> for MastodonPublisher {
type Error = mammut::Error; type Error = Box<dyn Error>;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))

49
src/publish/misskey.rs Normal file
View File

@ -0,0 +1,49 @@
use futures::Sink;
use misskey::{ClientExt, HttpClient};
use std::{error::Error, task::Poll};
use tokio::runtime::Runtime;
use url::Url;
pub struct MisskeyPublisher {
client: HttpClient,
}
impl MisskeyPublisher {
pub fn new(url: &String, token: String) -> Result<Self, Box<dyn Error>> {
Ok(Self {
client: HttpClient::with_token(Url::parse(url)?, token)?,
})
}
}
impl Sink<String> for MisskeyPublisher {
type Error = Box<dyn Error>;
fn poll_ready(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn start_send(self: std::pin::Pin<&mut Self>, item: String) -> Result<(), Self::Error> {
let mut runtime = Runtime::new()?;
let fut = self.client.create_note(item);
runtime.block_on(fut)?;
Ok(())
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
}

View File

@ -1,2 +1,4 @@
mod mastodon; mod mastodon;
mod misskey;
pub use mastodon::MastodonPublisher; pub use mastodon::MastodonPublisher;
pub use crate::publish::misskey::MisskeyPublisher;