Compare commits
10 commits
1c4e976562
...
acec764eac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acec764eac | ||
|
|
71da65576b | ||
|
|
82e67a3c79 | ||
|
|
7a12fc4490 | ||
|
|
ed40f2e299 | ||
|
|
4da8e0b5c1 | ||
|
|
2d18d3743b | ||
|
|
69c8d850fb | ||
|
|
68a45b26d0 | ||
|
|
197b9dce6d |
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
.env
|
||||||
|
|
|
||||||
1815
Cargo.lock
generated
1815
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -1,7 +1,15 @@
|
||||||
[package]
|
[package]
|
||||||
name = "DWHRust"
|
name = "daily_wiki_human"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
axum = { version = "0.7.9", features = ["macros"] }
|
||||||
|
chrono = "0.4.39"
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
json = "0.12.4"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
reqwest = { version = "0.12.11", features = ['blocking', 'multipart'] }
|
||||||
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
tokio-cron-scheduler = "0.13.0"
|
||||||
|
|
|
||||||
72
backup.txt
Normal file
72
backup.txt
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
https://en.wikipedia.org/wiki/Nova_Scotia_Trunk_9
|
||||||
|
https://en.wikipedia.org/wiki/World_line
|
||||||
|
https://en.wikipedia.org/wiki/Vorobyovy_Gory_(Moscow_Metro)
|
||||||
|
https://en.wikipedia.org/wiki/Female_impersonators
|
||||||
|
https://en.wikipedia.org/wiki/Pauline_Donalda
|
||||||
|
https://en.wikipedia.org/wiki/Skull_and_Bones
|
||||||
|
https://en.wikipedia.org/wiki/Woodin_cardinal
|
||||||
|
https://en.wikipedia.org/wiki/Hoy_No_Circula?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Bogdanov_affair
|
||||||
|
https://en.wikipedia.org/wiki/Subscriber_trunk_dialling
|
||||||
|
https://en.wikipedia.org/wiki/Area_code_318
|
||||||
|
https://en.wikipedia.org/wiki/Irving_Fisher
|
||||||
|
https://en.wikipedia.org/wiki/Can%27t_Stop_(board_game)
|
||||||
|
https://en.wikipedia.org/wiki/Three_Arrows_Capital?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Karlheinz_Stockhausen?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Kansas_experiment
|
||||||
|
https://en.wikipedia.org/wiki/Refusenik
|
||||||
|
https://en.wikipedia.org/wiki/Havre_de_Grace,_Maryland
|
||||||
|
https://en.wikipedia.org/wiki/Bootblacking_%28BDSM%29?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Flag_of_Martinique
|
||||||
|
https://en.wikipedia.org/wiki/Wii_system_software?wprov=sfti1
|
||||||
|
https://en.wikipedia.org/wiki/Vajont_Dam?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Massachusetts_Avenue_(metropolitan_Boston)
|
||||||
|
https://en.wikipedia.org/wiki/User_error?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Pangram
|
||||||
|
https://en.wikipedia.org/wiki/Analemma
|
||||||
|
https://en.wikipedia.org/wiki/Market_basket
|
||||||
|
https://en.wikipedia.org/wiki/Indian-made_foreign_liquor
|
||||||
|
https://en.wikipedia.org/wiki/Meeting_of_Waters
|
||||||
|
https://en.wikipedia.org/wiki/Snub_(geometry)
|
||||||
|
https://en.wikipedia.org/wiki/List_of_Interstate_Highways_in_Alaska
|
||||||
|
https://en.wikipedia.org/wiki/Jeff_Award
|
||||||
|
https://en.wikipedia.org/wiki/Crazy_Frog
|
||||||
|
https://en.wikipedia.org/wiki/Hurricane_Iota
|
||||||
|
https://en.wikipedia.org/wiki/Wade%E2%80%93Giles?wprov=sfti1
|
||||||
|
https://en.wikipedia.org/wiki/Japanese_addressing_system?wprov=sfti1
|
||||||
|
https://en.wikipedia.org/wiki/Cuban_peso
|
||||||
|
https://en.wikipedia.org/wiki/Telephone_numbers_in_the_State_of_Palestine
|
||||||
|
https://en.wikipedia.org/wiki/Han_unification
|
||||||
|
https://en.wikipedia.org/wiki/Changhua%E2%80%93Kaohsiung_Viaduct
|
||||||
|
https://en.wikipedia.org/wiki/Subway_%28George_Bush_Intercontinental_Airport%29?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Hawaii_Route_200?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Santa_Muerte?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/National_conventions_for_writing_telephone_numbers?wprov=sfti1
|
||||||
|
https://en.wikipedia.org/wiki/FOAF?wprov=sfti1
|
||||||
|
https://en.wikipedia.org/wiki/Quebec_Route_389
|
||||||
|
https://en.wikipedia.org/wiki/List_of_terms_referring_to_an_average_person?wprov=sfti1
|
||||||
|
https://en.wikipedia.org/wiki/Artemis_program?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/The_Chalice_of_the_Gods?wprov=sfla1
|
||||||
|
https://en.wikipedia.org/wiki/Chevrolet_Citation
|
||||||
|
https://en.wikipedia.org/wiki/History_of_the_Comoros_(1978%E2%80%931989)
|
||||||
|
https://en.wikipedia.org/wiki/Wag_(company)
|
||||||
|
https://en.wikipedia.org/wiki/New_York_City_steam_system
|
||||||
|
https://en.wikipedia.org/wiki/Airbus_Mobile
|
||||||
|
https://en.wikipedia.org/wiki/Derry/Londonderry_name_dispute
|
||||||
|
https://en.wikipedia.org/wiki/RS-Computer
|
||||||
|
https://en.wikipedia.org/wiki/Trainbow?wprov=sfti1
|
||||||
|
https://en.wikipedia.org/wiki/Siege_of_Suiyang
|
||||||
|
https://en.wikipedia.org/wiki/Amtrak's_25_Hz_traction_power_system
|
||||||
|
https://en.wikipedia.org/wiki/Amtrak's_60_Hz_traction_power_system
|
||||||
|
https://en.wikipedia.org/wiki/PowerCon
|
||||||
|
https://en.wikipedia.org/wiki/Chateaugay,_New_York
|
||||||
|
https://en.wikipedia.org/wiki/996_working_hour_system
|
||||||
|
https://en.wikipedia.org/wiki/Cin%C3%A9mas_Guzzo
|
||||||
|
https://en.wikipedia.org/wiki/List_of_people_from_Montclair,_New_Jersey
|
||||||
|
https://en.wikipedia.org/wiki/Latent_heat
|
||||||
|
https://en.wikipedia.org/wiki/Enthalpy_of_vaporization
|
||||||
|
https://en.wikipedia.org/wiki/Asahi-class_destroyer
|
||||||
|
https://en.wikipedia.org/wiki/Algiers_Accords
|
||||||
|
https://en.wikipedia.org/wiki/U.S._Route_23_in_Tennessee
|
||||||
|
https://en.wikipedia.org/wiki/Gradian
|
||||||
|
https://en.wikipedia.org/wiki/Electrostatics
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
devShells.${system}.default = pkgs.mkShell {
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
cargo rustc rust-analyzer clippy
|
cargo rustc rust-analyzer clippy
|
||||||
|
pkg-config
|
||||||
|
openssl
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
3
queue.txt
Normal file
3
queue.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
https://en.wikipedia.org/wiki/GNU/Linux_naming_controversy
|
||||||
|
https://en.wikipedia.org/wiki/Buck-a-beer?wprov=sfla1
|
||||||
|
|
||||||
80
src/file.rs
Normal file
80
src/file.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
use std::fs::{read_to_string, write};
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FileError {
|
||||||
|
IOError(io::Error),
|
||||||
|
EmptyLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for FileError {
|
||||||
|
fn from(value: io::Error) -> Self {
|
||||||
|
Self::IOError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct LinksFile<'a>(&'a Path);
|
||||||
|
|
||||||
|
impl<'a> LinksFile<'a> {
|
||||||
|
pub fn new(p: &'a Path) -> LinksFile<'a> {
|
||||||
|
LinksFile(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_all_lines(&self) -> Result<Vec<String>, FileError> {
|
||||||
|
let file_contents = read_to_string(self.0)?;
|
||||||
|
let file_lines = file_contents.split("\n");
|
||||||
|
Ok(
|
||||||
|
file_lines
|
||||||
|
.map(|string| string.to_owned())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_first_line(&self) -> Result<String, FileError> {
|
||||||
|
let file_contents = read_to_string(self.0)?;
|
||||||
|
let mut file_lines = file_contents.split("\n");
|
||||||
|
let mut line = file_lines.next();
|
||||||
|
// run through all empty lines
|
||||||
|
while let Some("") = line {
|
||||||
|
line = file_lines.next();
|
||||||
|
};
|
||||||
|
let line_clean = match line {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Err(FileError::EmptyLine)
|
||||||
|
};
|
||||||
|
let rest_of_file = file_lines
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join("\n");
|
||||||
|
write(self.0, rest_of_file)?;
|
||||||
|
Ok(line_clean.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn remove_first_line(&self) -> Result<String, FileError> {
|
||||||
|
// let file_contents = read_to_string(self.0)?;
|
||||||
|
// let mut file_lines = file_contents.split("\n");
|
||||||
|
// let first_line = file_lines.next();
|
||||||
|
// let first_line_clean = match first_line {
|
||||||
|
// Some("") => return Err(FileError::EmptyLine),
|
||||||
|
// Some(a) => a,
|
||||||
|
// None => panic!("Iterator returns None, something wrong happened")
|
||||||
|
// };
|
||||||
|
// let rest_of_file = file_lines
|
||||||
|
// .collect::<Vec<&str>>()
|
||||||
|
// .join("\n");
|
||||||
|
// write(self.0, rest_of_file)?;
|
||||||
|
// Ok(first_line_clean.to_owned())
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn add_line_to_end(&self, line: String) -> Result<(), FileError> {
|
||||||
|
let old_contents = read_to_string(self.0)?;
|
||||||
|
let new_contents = old_contents
|
||||||
|
.trim()
|
||||||
|
.to_owned()
|
||||||
|
+ (if old_contents.is_empty() {""} else {"\n"})
|
||||||
|
+ &line;
|
||||||
|
write(self.0, new_contents)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/lib.rs
17
src/lib.rs
|
|
@ -1,17 +0,0 @@
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
fn slug_from_link(link: String) -> Result<String, String> {
|
|
||||||
let re = Regex::new(r"\.wikipedia\.org\/wiki\/|\?").unwrap();
|
|
||||||
let v: Vec<&str> = re.split(&link).collect();
|
|
||||||
println!("{v:?}");
|
|
||||||
if v.len() >= 2 { Ok(v[1].to_string()) } else { Err("Illegal link {link} provided".to_string()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_slug_from_link_1() {
|
|
||||||
assert_eq!(slug_from_link("https://en.wikipedia.org/wiki/Buck-a-beer?wprov=sfla1".to_string()).unwrap().as_str(), "Buck-a-beer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
65
src/main.rs
65
src/main.rs
|
|
@ -1,3 +1,64 @@
|
||||||
fn main() {
|
use std::path::Path;
|
||||||
println!("Hello, world!");
|
use tokio::{spawn, task::spawn_blocking};
|
||||||
|
use tokio_cron_scheduler::{Job, JobScheduler};
|
||||||
|
|
||||||
|
mod file;
|
||||||
|
mod wikipedia;
|
||||||
|
mod mastodon;
|
||||||
|
mod server;
|
||||||
|
|
||||||
|
use file::{FileError, LinksFile};
|
||||||
|
use mastodon::{Mastodon, Visibility};
|
||||||
|
use wikipedia::post_body;
|
||||||
|
|
||||||
|
async fn post_link_from_file(queue: LinksFile<'_>, backup: LinksFile<'_>, mastodon: Mastodon) {
|
||||||
|
// get link to post
|
||||||
|
let link_to_post = match queue.remove_first_line() {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(FileError::EmptyLine) => match backup.remove_first_line() {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(c) => panic!("{c:?}")
|
||||||
|
},
|
||||||
|
Err(d) => panic!("{d:?}")
|
||||||
|
};
|
||||||
|
// convert said link into a post status
|
||||||
|
let post_status = post_body(link_to_post).await.unwrap();
|
||||||
|
// post it on mastodon
|
||||||
|
spawn_blocking(
|
||||||
|
move || {
|
||||||
|
mastodon.post_text_status(post_status, Visibility::Public).unwrap();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
dotenv::dotenv().ok();
|
||||||
|
// let app_code = std::env::var("APP_CODE").expect("APP_CODE required.");
|
||||||
|
let dwh_app_token = std::env::var("DWH_APP_TOKEN").expect("DWH_APP_TOKEN required.");
|
||||||
|
let dwh_app_instance = std::env::var("DWH_APP_INSTANCE").expect("DWH_APP_INSTANCE required.");
|
||||||
|
let dwh_cron_schedule = std::env::var("DWH_CRON_SCHEDULE").expect("DWH_CRON_SCHEDULE required.");
|
||||||
|
|
||||||
|
let sched = JobScheduler::new().await.unwrap();
|
||||||
|
|
||||||
|
sched.add(
|
||||||
|
Job::new_async(dwh_cron_schedule, move |_uuid, _l| {
|
||||||
|
Box::pin({
|
||||||
|
let app_instance_cloned = dwh_app_instance.clone();
|
||||||
|
let app_token_cloned = dwh_app_token.clone();
|
||||||
|
async move {
|
||||||
|
let queue = LinksFile::new(Path::new("queue.txt"));
|
||||||
|
let backup = LinksFile::new(Path::new("backup.txt"));
|
||||||
|
let mastodon = Mastodon::new(app_instance_cloned, app_token_cloned);
|
||||||
|
post_link_from_file(queue, backup, mastodon.clone()).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).unwrap()
|
||||||
|
).await.unwrap();
|
||||||
|
|
||||||
|
let scheduler = sched.start();
|
||||||
|
let server = server::serve();
|
||||||
|
|
||||||
|
let _ = scheduler.await;
|
||||||
|
let _ = server.await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
67
src/mastodon.rs
Normal file
67
src/mastodon.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
use reqwest::{blocking::{Client, multipart}, StatusCode};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
ReqwestError(reqwest::Error),
|
||||||
|
FailureStatus(reqwest::StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
fn from(value: reqwest::Error) -> Self {
|
||||||
|
Self::ReqwestError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum Visibility {
|
||||||
|
Public,
|
||||||
|
Unlisted,
|
||||||
|
Private,
|
||||||
|
Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visibility {
|
||||||
|
pub fn enum_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Public => "public",
|
||||||
|
Self::Unlisted => "unlisted",
|
||||||
|
Self::Private => "private",
|
||||||
|
Self::Direct => "direct",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mastodon {
|
||||||
|
instance: String,
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mastodon {
|
||||||
|
pub fn new(instance: String, token: String) -> Self {
|
||||||
|
Mastodon {
|
||||||
|
instance,
|
||||||
|
token,
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_text_status(&self, status: String, visibility: Visibility) -> Result<(), Error> {
|
||||||
|
let form = multipart::Form::new()
|
||||||
|
.text("status", status)
|
||||||
|
.text("visibility", visibility.enum_name());
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.danger_accept_invalid_certs(true)
|
||||||
|
.build()
|
||||||
|
.expect("");
|
||||||
|
let response = client
|
||||||
|
.post(format!("{}/api/v1/statuses", self.instance))
|
||||||
|
.header("Authorization", format!("Bearer {}", self.token))
|
||||||
|
.multipart(form)
|
||||||
|
.send()?;
|
||||||
|
match response.status() {
|
||||||
|
StatusCode::OK => Ok(()),
|
||||||
|
s => Err(Error::FailureStatus(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/server.rs
Normal file
53
src/server.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
routing::post,
|
||||||
|
http::StatusCode,
|
||||||
|
Json, Router
|
||||||
|
};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::file::LinksFile;
|
||||||
|
|
||||||
|
pub async fn serve<'a>() {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/addqueue", post(add_queue))
|
||||||
|
.route("/addbackup", post(add_backup));
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3041").await.unwrap();
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_queue(Json(payload): Json<AddPost>,) -> (StatusCode, Json<AddPostResponse>) {
|
||||||
|
if payload.authorization != "t2Ye@6qgKkm$SQg5" {
|
||||||
|
return (StatusCode::FORBIDDEN, Json(AddPostResponse { url: "".to_owned(), position: 999 } ))
|
||||||
|
};
|
||||||
|
let queue_file = LinksFile::new(Path::new("queue.txt"));
|
||||||
|
match queue_file.add_line_to_end(payload.url.clone()) {
|
||||||
|
Ok(()) => (StatusCode::CREATED, Json( AddPostResponse { url: payload.url, position: 0 })),
|
||||||
|
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, Json( AddPostResponse { url: "".to_owned(), position: 999 } )),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_backup(Json(payload): Json<AddPost>,) -> (StatusCode, Json<AddPostResponse>) {
|
||||||
|
if payload.authorization != "t2Ye@6qgKkm$SQg5" {
|
||||||
|
return (StatusCode::FORBIDDEN, Json(AddPostResponse { url: "".to_owned(), position: 999 } ))
|
||||||
|
};
|
||||||
|
let backup_file = LinksFile::new(Path::new("backup.txt"));
|
||||||
|
match backup_file.add_line_to_end(payload.url.clone()) {
|
||||||
|
Ok(()) => (StatusCode::CREATED, Json( AddPostResponse { url: payload.url, position: 0 })),
|
||||||
|
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, Json( AddPostResponse { url: "".to_owned(), position: 999 } )),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AddPost {
|
||||||
|
url: String,
|
||||||
|
authorization: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct AddPostResponse {
|
||||||
|
url: String,
|
||||||
|
position: u32,
|
||||||
|
}
|
||||||
98
src/wikipedia.rs
Normal file
98
src/wikipedia.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
use regex::Regex;
|
||||||
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
|
static APP_USER_AGENT: &str = r"DailyWikiHuman/0.0 (https://en.wikipedia.org/wiki/User:WittyWidi) generic-library/0.0";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error{
|
||||||
|
IllegalLink(String),
|
||||||
|
ReqwestError(reqwest::Error),
|
||||||
|
JsonError(json::Error),
|
||||||
|
ArticleNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
fn from(value: reqwest::Error) -> Self {
|
||||||
|
Self::ReqwestError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<json::Error> for Error {
|
||||||
|
fn from(value: json::Error) -> Self {
|
||||||
|
Self::JsonError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slug_from_link(link: String) -> Result<String, Error> {
|
||||||
|
let regex_pattern = Regex::new(r"\.wikipedia\.org\/wiki\/|\?").unwrap();
|
||||||
|
let link_parts: Vec<&str> = regex_pattern.split(&link).collect();
|
||||||
|
if link_parts.len() >= 2 {
|
||||||
|
Ok(link_parts[1].to_owned())
|
||||||
|
} else {
|
||||||
|
Err(Error::IllegalLink(link))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title_from_slug(slug: String) -> Result<String, Error> {
|
||||||
|
let escaped_slug = slug.replace("/", "%2F");
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.user_agent(APP_USER_AGENT)
|
||||||
|
.build()
|
||||||
|
.expect("");
|
||||||
|
let request_url =
|
||||||
|
format!(
|
||||||
|
"https://api.wikimedia.org/core/v1/wikipedia/en/page/{escaped_slug}/bare"
|
||||||
|
);
|
||||||
|
let response = client.get(request_url).send()?;
|
||||||
|
let json_body = response.text()?;
|
||||||
|
println!("{:?}", json_body);
|
||||||
|
let body = json::parse(&json_body)?;
|
||||||
|
match body["title"].as_str() {
|
||||||
|
Some(x) => Ok(x.to_owned()),
|
||||||
|
None => Err(Error::ArticleNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn post_body(link: String) -> Result<String, Error> {
|
||||||
|
let link_copy = link.clone();
|
||||||
|
let title = spawn_blocking(
|
||||||
|
move || {title_from_slug(slug_from_link(link_copy)?)}
|
||||||
|
).await.expect("failed with JoinError")?;
|
||||||
|
Ok(format!("Today's wikipedia article is {title}\n\n\
|
||||||
|
{link}\n\n\
|
||||||
|
#wikipedia").to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slug_from_link_1() {
|
||||||
|
assert_eq!(slug_from_link("https://en.wikipedia.org/wiki/Buck-a-beer?wprov=sfla1".to_owned()).unwrap().as_str(), "Buck-a-beer")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slug_from_link_2() {
|
||||||
|
assert_eq!(slug_from_link("https://en.wikipedia.org/wiki/GNU/Linux_naming_controversy".to_owned()).unwrap().as_str(), "GNU/Linux_naming_controversy")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_title_from_slug_1() {
|
||||||
|
assert_eq!(title_from_slug("Buck-a-beer".to_owned()).await.unwrap().as_str(), "Buck-a-beer")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_title_from_slug_2() {
|
||||||
|
assert_eq!(title_from_slug("GNU/Linux_naming_controversy".to_owned()).await.unwrap().as_str(), "GNU/Linux naming controversy")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_post_body_1() {
|
||||||
|
let body = post_body("https://en.wikipedia.org/wiki/GNU%2FLinux_naming_controversy".to_owned()).await.unwrap();
|
||||||
|
let expected = "Today's wikipedia article is GNU/Linux naming controversy\n\n\
|
||||||
|
https://en.wikipedia.org/wiki/GNU%2FLinux_naming_controversy\n\n\
|
||||||
|
#wikipedia";
|
||||||
|
assert_eq!(body, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue