first commit
Dieser Commit ist enthalten in:
Commit
a0e983bc92
|
@ -0,0 +1 @@
|
||||||
|
/target
|
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
|
@ -0,0 +1,73 @@
|
||||||
|
[package]
|
||||||
|
name = "anime_calendar"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
icalendar = "~0.16"
|
||||||
|
systemd-journal-logger = "~2.2"
|
||||||
|
toml ="~0.8"
|
||||||
|
lazy_static = "~1.5"
|
||||||
|
stderrlog = "0.*"
|
||||||
|
url = "2.*"
|
||||||
|
strfmt = "0.2.4"
|
||||||
|
|
||||||
|
[dependencies.log]
|
||||||
|
version = "0.4.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = ["std", "max_level_trace", "release_max_level_trace"]
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1.0.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = ["serde_derive"]
|
||||||
|
|
||||||
|
[dependencies.clap]
|
||||||
|
version = "4.5.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = ["derive","cargo"]
|
||||||
|
|
||||||
|
[dependencies.clap_builder]
|
||||||
|
version = "4.5.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = []
|
||||||
|
|
||||||
|
[dependencies.chrono]
|
||||||
|
version = "0.4.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = ["serde"]
|
||||||
|
|
||||||
|
[dependencies.myanimelist]
|
||||||
|
version = "0.1.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = []
|
||||||
|
optional =true
|
||||||
|
|
||||||
|
[dependencies.tide]
|
||||||
|
version = "0.16.0"
|
||||||
|
no-default-features = true
|
||||||
|
features = ["h1-server"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
version = "0.12.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = []
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.async-std]
|
||||||
|
version = "1.*"
|
||||||
|
no-default-features = true
|
||||||
|
features = []
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.signals]
|
||||||
|
version = "0.0.5"
|
||||||
|
no-default-features=true
|
||||||
|
features = []
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
mal = ["dep:myanimelist", "dep:tide", "dep:signals", "dep:reqwest", "dep:async-std"]
|
|
@ -0,0 +1,3 @@
|
||||||
|
outputdir = "tmp"
|
||||||
|
complete_ics = "all.ics"
|
||||||
|
anime = []
|
|
@ -0,0 +1,90 @@
|
||||||
|
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports, unused_variables))]
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
use myanimelist::{auth::Auth, AccessToken, RefreshToken};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{fmt::Debug, option::Iter};
|
||||||
|
|
||||||
|
fn default_filename() -> String {
|
||||||
|
return "all.ics".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_length() -> i64 {
|
||||||
|
return 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_shift() -> i64 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub(crate) struct Anime {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) episodes: u64,
|
||||||
|
pub(crate) start: chrono::NaiveDateTime,
|
||||||
|
#[serde(default = "default_length")]
|
||||||
|
pub(crate) length: i64,
|
||||||
|
#[serde(default = "default_shift")]
|
||||||
|
pub(crate) shift: i64,
|
||||||
|
pub(crate) url: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) pause: Vec<chrono::NaiveDate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
|
pub(crate) struct MALAccesskey {
|
||||||
|
pub(crate) access_token: AccessToken,
|
||||||
|
pub(crate) refresh_token: RefreshToken,
|
||||||
|
pub(crate) access_expiry_time: u64,
|
||||||
|
pub(crate) refresh_expiry_time: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
impl MALAccesskey {
|
||||||
|
pub(crate) fn load(&self, auth: &Auth) {
|
||||||
|
auth.set_access_token_unchecked(self.access_token.clone());
|
||||||
|
auth.set_expires_at_unchecked(self.access_expiry_time);
|
||||||
|
auth.set_refresh_token_unchecked(self.refresh_token.clone());
|
||||||
|
auth.set_refresh_expires_at_unchecked(self.refresh_expiry_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
access_token: AccessToken::new("".into()),
|
||||||
|
refresh_token: RefreshToken::new("".into()),
|
||||||
|
access_expiry_time: 0,
|
||||||
|
refresh_expiry_time: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
pub(crate) outputdir: String,
|
||||||
|
#[serde(default = "default_filename")]
|
||||||
|
pub(crate) complete_ics: String,
|
||||||
|
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
pub(crate) mal_accesskey: Option<MALAccesskey>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) anime: Vec<Anime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
#[command(author, version, about, disable_help_subcommand = true)]
|
||||||
|
pub(crate) struct Commandline {
|
||||||
|
#[arg(short, long, default_value = "/etc/animecalendar.toml")]
|
||||||
|
pub(crate) config: String,
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub(crate) subcommand: Option<Subcommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Subcommand, Debug, Clone, Copy)]
|
||||||
|
pub(crate) enum Subcommand {
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
#[command(name="sync", visible_aliases=["sync-mal","mal"] ,about="Adds the missing shows from MyAnimelist")]
|
||||||
|
SyncMAL,
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports, unused_variables))]
|
||||||
|
use async_std::task;
|
||||||
|
use chrono::{Duration, Utc};
|
||||||
|
use clap::{crate_name, Parser};
|
||||||
|
use icalendar::{Alarm, Calendar, Component, Event, EventLike, EventStatus, Property};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use log::{self, SetLoggerError};
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
use malclient::update_config;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
io::{self, ErrorKind, Read, Seek, Write},
|
||||||
|
os::unix::fs::OpenOptionsExt,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
use stderrlog::{new as errlog, ColorChoice, Timestamp};
|
||||||
|
use systemd_journal_logger::JournalLog;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
mod malclient;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CONFIG_READOPTIONS: fs::OpenOptions = fs::OpenOptions::new()
|
||||||
|
.write(false)
|
||||||
|
.read(true)
|
||||||
|
.create(false)
|
||||||
|
.to_owned();
|
||||||
|
static ref CALENDAR_WRITEOPTIONS: fs::OpenOptions = fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.read(false)
|
||||||
|
.mode(0o644)
|
||||||
|
.to_owned();
|
||||||
|
static ref CONFIG_WRITEOPTIONS: fs::OpenOptions = fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(false)
|
||||||
|
.read(true)
|
||||||
|
.mode(0o644)
|
||||||
|
.to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slugify(input: String) -> String {
|
||||||
|
input.to_lowercase().replace(" ", "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_cal(name: &str, description: &str) -> Calendar {
|
||||||
|
Calendar::new()
|
||||||
|
.name(name)
|
||||||
|
.description(description)
|
||||||
|
.ttl(&Duration::days(1))
|
||||||
|
.append_property(Property::new("CUTYPE", "OTHER"))
|
||||||
|
.append_property(Property::new("FBTYPE", "BUSY-UNAVAILABLE"))
|
||||||
|
.done()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_calendars(config: config::Config) {
|
||||||
|
match fs::DirBuilder::new()
|
||||||
|
.recursive(true)
|
||||||
|
.create(config.outputdir.clone())
|
||||||
|
{
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => match err.kind() {
|
||||||
|
ErrorKind::AlreadyExists => {}
|
||||||
|
error => {
|
||||||
|
log::error!("Failed to create directory: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let binding = config.outputdir.clone();
|
||||||
|
let p = Path::new(&binding);
|
||||||
|
let mut allcal = gen_cal("Anime Calendar", "Calendar for all configured animes");
|
||||||
|
let mut itemcal: Calendar;
|
||||||
|
let mut formatargs = HashMap::<String, String>::with_capacity(2);
|
||||||
|
formatargs.insert("name".to_string(), "".to_string());
|
||||||
|
formatargs.insert("episode".to_string(), "".to_string());
|
||||||
|
for anime in config.anime.iter() {
|
||||||
|
itemcal = gen_cal(
|
||||||
|
format!("Calendar for {}", anime.name).as_str(),
|
||||||
|
format!("Calendar for {}", anime.name).as_str(),
|
||||||
|
);
|
||||||
|
formatargs.insert("name".to_string(), anime.name.clone());
|
||||||
|
let mut lastdate = anime
|
||||||
|
.start
|
||||||
|
.and_utc()
|
||||||
|
.checked_add_signed(Duration::minutes(anime.shift))
|
||||||
|
.unwrap();
|
||||||
|
let mut pause = anime.pause.clone();
|
||||||
|
pause.sort();
|
||||||
|
for episode in 1..(anime.episodes + 1) {
|
||||||
|
formatargs.insert("episode".to_string(), episode.to_string());
|
||||||
|
let mut start = if episode == 1 {
|
||||||
|
lastdate
|
||||||
|
} else {
|
||||||
|
lastdate.checked_add_days(chrono::Days::new(7)).unwrap()
|
||||||
|
};
|
||||||
|
for breakdate in pause.iter() {
|
||||||
|
if start.date_naive() == *breakdate {
|
||||||
|
start = start.checked_add_days(chrono::Days::new(7)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastdate = start;
|
||||||
|
let mut item = Event::new()
|
||||||
|
.starts(start)
|
||||||
|
.ends(
|
||||||
|
start
|
||||||
|
.checked_add_signed(Duration::minutes(anime.length))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.summary(&anime.name)
|
||||||
|
.alarm(icalendar::Alarm::display(&anime.name, Duration::seconds(0)))
|
||||||
|
.uid(slugify(format!("{}-{}", anime.name, episode)).as_str())
|
||||||
|
.done();
|
||||||
|
match anime.url.clone() {
|
||||||
|
None => {}
|
||||||
|
Some(url) => {
|
||||||
|
let url = strfmt::strfmt(url.as_str(), &formatargs.clone()).unwrap();
|
||||||
|
item.url(url.as_str()).location(url.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allcal.push(item.clone());
|
||||||
|
itemcal.push(item.done());
|
||||||
|
}
|
||||||
|
let mut itemics = CALENDAR_WRITEOPTIONS
|
||||||
|
.open(p.join(format!("{}.ics", anime.name.clone())))
|
||||||
|
.unwrap();
|
||||||
|
write!(itemics, "{}", itemcal).unwrap();
|
||||||
|
}
|
||||||
|
let mut allics = CALENDAR_WRITEOPTIONS
|
||||||
|
.open(p.join(format!("{}", config.complete_ics)))
|
||||||
|
.unwrap();
|
||||||
|
write!(allics, "{}", allcal).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
match JournalLog::new() {
|
||||||
|
Ok(log) => {
|
||||||
|
log.add_extra_field("RUST_PACKAGE", crate_name!())
|
||||||
|
.install()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
io::stderr().write(b"Failed to setup logging").unwrap();
|
||||||
|
errlog()
|
||||||
|
.color(ColorChoice::Auto)
|
||||||
|
.quiet(false)
|
||||||
|
.show_level(true)
|
||||||
|
.show_module_names(true)
|
||||||
|
.timestamp(Timestamp::Second)
|
||||||
|
.init()
|
||||||
|
.unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::set_max_level(log::LevelFilter::Trace);
|
||||||
|
let args = config::Commandline::parse();
|
||||||
|
let configfileresult = match args.subcommand {
|
||||||
|
None => CONFIG_READOPTIONS.open(args.config),
|
||||||
|
Some(_) => CONFIG_WRITEOPTIONS.open(args.config),
|
||||||
|
};
|
||||||
|
let mut configfile = match configfileresult {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error opening the configuration: {}", e.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut cfgstring = String::new();
|
||||||
|
match configfile.read_to_string(&mut cfgstring) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("Failed to read config file: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut config: config::Config = toml::from_str(&cfgstring).expect("Expected valid toml");
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
match args.subcommand {
|
||||||
|
None => {
|
||||||
|
log::info!("ICal creator started up");
|
||||||
|
log::debug!("{:?}", config);
|
||||||
|
write_calendars(config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "mal")]
|
||||||
|
Some(config::Subcommand::SyncMAL) => {
|
||||||
|
log::info!("Updating the list");
|
||||||
|
task::block_on(update_config(&mut config));
|
||||||
|
}
|
||||||
|
Some(_) => {}
|
||||||
|
}
|
||||||
|
match toml::to_string_pretty(&config) {
|
||||||
|
Ok(configstring) => {
|
||||||
|
let _ = configfile.seek(io::SeekFrom::Start(0));
|
||||||
|
let _ = configfile.set_len(0);
|
||||||
|
let _ = configfile.write(configstring.as_bytes());
|
||||||
|
let _ = configfile.sync_all();
|
||||||
|
}
|
||||||
|
Err(_) => todo!(),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports, unused_variables))]
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
convert::Infallible,
|
||||||
|
error::Error,
|
||||||
|
future::{self, Future},
|
||||||
|
mem,
|
||||||
|
net::{IpAddr, Ipv6Addr, SocketAddr},
|
||||||
|
process::Output,
|
||||||
|
str::FromStr,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::config::{self, MALAccesskey};
|
||||||
|
use async_std::{io::Bytes, task};
|
||||||
|
use async_std::{net::TcpListener, task::sleep};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use log::{error, info};
|
||||||
|
use myanimelist::{self, auth::TokenError, MalClientError};
|
||||||
|
use myanimelist::{auth::Auth, AuthorizationCode, CsrfToken};
|
||||||
|
use myanimelist::{ClientId, ClientSecret, RedirectUrl, Scope};
|
||||||
|
use signals::Signal;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use tide;
|
||||||
|
use tide::prelude::*;
|
||||||
|
use url::Url;
|
||||||
|
const SCHEME: &str = "http";
|
||||||
|
const HOST: &str = "localhost";
|
||||||
|
static mut PORT: u16 = 0;
|
||||||
|
const LOCALHOST: SocketAddr = SocketAddr::new(
|
||||||
|
std::net::IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
lazy_static! {
|
||||||
|
static ref CALLBACKRESULT: Mutex<AuthorizationCode> =
|
||||||
|
Mutex::new(AuthorizationCode::new(String::new()));
|
||||||
|
static ref CSRFTOKEN: Mutex<CsrfToken> = Mutex::new(CsrfToken::new(String::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn hook_recv(request: tide::Request<()>) -> tide::Result {
|
||||||
|
let mut body = tide::Body::from_bytes(b"Sorry this must be redirected from MyAnimelist".into());
|
||||||
|
let mut response = tide::Response::new(400);
|
||||||
|
response.set_content_type("text/plain");
|
||||||
|
let token = match CSRFTOKEN.lock() {
|
||||||
|
Ok(token) => token,
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("Failed to get CSRF Token: {error}");
|
||||||
|
response.set_status(500);
|
||||||
|
response.set_body(body);
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut token_code = String::new();
|
||||||
|
let mut csrf = false;
|
||||||
|
for (key, value) in request.url().query_pairs().into_iter() {
|
||||||
|
if key.eq("code") {
|
||||||
|
token_code = value.into_owned();
|
||||||
|
} else if key.eq("state") && token.secret().eq(&value) {
|
||||||
|
csrf = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if csrf {
|
||||||
|
let mut callback = CALLBACKRESULT.lock().unwrap();
|
||||||
|
*callback = AuthorizationCode::new(token_code);
|
||||||
|
body = tide::Body::from_bytes(b"Successful. You can now close this Tab".into());
|
||||||
|
response.set_status(200);
|
||||||
|
}
|
||||||
|
response.set_body(body);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn callback(
|
||||||
|
_url: reqwest::Url,
|
||||||
|
_: CsrfToken,
|
||||||
|
) -> Result<(AuthorizationCode, CsrfToken), Box<dyn Error>> {
|
||||||
|
println!("Please visit {} to authenticate", _url.as_str());
|
||||||
|
loop {
|
||||||
|
sleep(Duration::from_nanos(100)).await;
|
||||||
|
let lock = match CALLBACKRESULT.lock() {
|
||||||
|
Ok(cg) => cg,
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("Failed to get call guard: {error}");
|
||||||
|
return Err(Box::new(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if lock.secret().len() != 0 {
|
||||||
|
return Ok((
|
||||||
|
AuthorizationCode::new(lock.secret().clone()),
|
||||||
|
CsrfToken::new("".to_owned()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tideserver() -> Result<(), ()> {
|
||||||
|
let sock = match TcpListener::bind(LOCALHOST).await {
|
||||||
|
Ok(sock) => sock,
|
||||||
|
Err(error) => {
|
||||||
|
error!("Failed to open Socket for the Server");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let addr = sock.local_addr().unwrap();
|
||||||
|
let mut server = tide::new();
|
||||||
|
server.at("/").post(hook_recv);
|
||||||
|
info!("Starting the Server under {}", addr);
|
||||||
|
unsafe { PORT = addr.port() };
|
||||||
|
let _ = server.listen(sock).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn update_config(config: &mut config::Config) {
|
||||||
|
let code = Cow::from("code");
|
||||||
|
|
||||||
|
let server = task::spawn_local(tideserver());
|
||||||
|
|
||||||
|
let auth: Auth = Auth::new(
|
||||||
|
ClientId::new("".into()),
|
||||||
|
ClientSecret::new("".into()),
|
||||||
|
RedirectUrl::new(unsafe { format!("{SCHEME}://{HOST}:{PORT}/") }).unwrap(),
|
||||||
|
);
|
||||||
|
auth.add_scope(Scope::new("write:users".to_string()));
|
||||||
|
auth.set_callback(callback).await;
|
||||||
|
if config.mal_accesskey.is_some() {
|
||||||
|
config.mal_accesskey.as_ref().unwrap().load(&auth);
|
||||||
|
}
|
||||||
|
if !auth.is_access_valid() {
|
||||||
|
if auth.is_refresh_valid() {
|
||||||
|
match auth.refresh().await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("Failed to refresh token: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.mal_accesskey = Some(MALAccesskey {
|
||||||
|
access_token: auth.access_token(),
|
||||||
|
refresh_token: auth.refresh_token(),
|
||||||
|
access_expiry_time: auth.expires_at(),
|
||||||
|
refresh_expiry_time: auth.refresh_expires_at(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
match auth.regenerate().await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(_) => todo!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ =server.cancel();
|
||||||
|
let _client = match myanimelist::MalClientBuilder::new().auth(auth).build() {
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(MalClientError::Reqwest(error)) => {
|
||||||
|
log::error!("Failed to connect to the API: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(MalClientError::Builder(error)) => {
|
||||||
|
log::error!("Failed to build object: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(MalClientError::Token(error)) => {
|
||||||
|
log::error!("Broken token: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren