Compare commits

...

4 Commits

Author SHA1 Message Date
prcrst
04fdd8908f Refactor code to use API struct deserialization
The deserialization errors have been fixed in 0.18.1
2023-07-07 17:40:32 +02:00
prcrst
164bd37987 Add missing docker dependencies 2023-07-07 17:40:32 +02:00
prcrst
2237079feb Update dependencies 2023-07-07 17:40:32 +02:00
prcrst
a6bc39ca98 Initial commit 2023-07-07 17:40:32 +02:00
7 changed files with 2565 additions and 0 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
target

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
.vscode

2370
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "wordlebot"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4.26"
reqwest = { version ="0.11.18", features = ["json"]}
tokio = { version = "1" }
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.48"
lemmy_api_common = "0.18.1"

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM rust:1-bookworm as builder
WORKDIR /usr/src/wordlebot
COPY . .
RUN cargo install --path .
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y libssl3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/cargo/bin/wordlebot /usr/local/bin/wordlebot
CMD ["wordlebot"]

83
src/helper.rs Normal file
View File

@ -0,0 +1,83 @@
use lemmy_api_common::{
community::{GetCommunity, GetCommunityResponse},
lemmy_db_schema::newtypes::CommunityId,
person::{Login, LoginResponse},
sensitive::Sensitive,
};
use reqwest::Client;
use std::fmt;
static API_VERSION: i32 = 3;
pub fn lemmy_password() -> Result<Sensitive<String>, std::env::VarError> {
Ok(Sensitive::new(std::env::var("LEMMY_PASSWORD")?))
}
pub fn lemmy_user() -> Sensitive<String> {
Sensitive::new(std::env::var("LEMMY_USER").unwrap_or_else(|_| "wordlebot".to_string()))
}
pub fn lemmy_server() -> String {
std::env::var("LEMMY_SERVER").unwrap_or_else(|_| "https://enterprise.lemmy.ml".to_string())
}
pub fn lemmy_community() -> String {
std::env::var("LEMMY_COMMUNITY").unwrap_or_else(|_| "wordle".to_string())
}
pub fn api_url(suffix: &str) -> String {
format!("{}/api/v{}/{}", lemmy_server(), API_VERSION, suffix)
}
#[derive(Debug)]
struct LoginFailedError;
impl std::error::Error for LoginFailedError {}
impl fmt::Display for LoginFailedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Login failed.")
}
}
pub async fn get_community_id(
client: &Client,
community_name: String,
) -> Result<CommunityId, Box<dyn std::error::Error>> {
let params = GetCommunity {
name: Some(community_name),
..Default::default()
};
let response = client
.get(api_url("community"))
.query(&params)
.send()
.await?;
let data = response.json::<GetCommunityResponse>().await?;
Ok(data.community_view.community.id)
}
pub async fn lemmy_login(client: &Client) -> Result<Sensitive<String>, Box<dyn std::error::Error>> {
println!("Logging in");
let params = Login {
username_or_email: lemmy_user(),
password: lemmy_password()?,
..Default::default()
};
let response = client
.post(api_url("user/login"))
.json(&params)
.send()
.await?;
response.error_for_status_ref()?;
let data = response.json::<LoginResponse>().await?;
if let Some(jwt) = data.jwt {
return Ok(jwt);
}
Err(Box::new(LoginFailedError))
}

83
src/main.rs Normal file
View File

@ -0,0 +1,83 @@
use chrono::{Datelike, Month, Utc};
use lemmy_api_common::{post::{CreatePost, PostResponse}, sensitive::Sensitive, lemmy_db_schema::newtypes::PostId};
use reqwest::{Client, Url};
use serde::Deserialize;
mod helper;
use crate::helper::{get_community_id, lemmy_community, lemmy_login};
#[derive(Debug, Deserialize, Default)]
struct WordleData {
days_since_launch: i64,
}
async fn get_current_nyt_wordle_data() -> Result<WordleData, Box<dyn std::error::Error>> {
println!("Fetching NYT wordle data");
let now = Utc::now();
let date_string = format!("{}-{:02}-{:02}", now.year(), now.month(), now.day());
let response =
reqwest::get(format! {"https://www.nytimes.com/svc/wordle/v2/{}.json", date_string})
.await?;
// Did it work?
response.error_for_status_ref()?;
let data = response.json::<WordleData>().await?;
println!("{:?}", data);
Ok(data)
}
async fn post_to_lemmy(data: &WordleData, client: &Client, auth: Sensitive<String>) -> Result<PostId, Box<dyn std::error::Error>> {
println!("Posting to lemmy: {:?}", data);
// Get the community id
let community_id = get_community_id(client, lemmy_community()).await?;
let now = Utc::now();
let month = Month::try_from(now.month() as u8)?;
let title = format!(
"Wordle #{} - {} {} {} {}",
data.days_since_launch,
now.weekday(),
now.day(),
month.name(),
now.year()
);
let url = Url::parse(
format!(
"https://www.nytimes.com/games/wordle/index.html#{}",
data.days_since_launch
)
.as_ref(),
)?;
println!("{}", title);
let params = CreatePost {
community_id,
name: title,
url: Some(url),
body: Some("Post your results and discuss your guesses. Have fun! \nNote: there might be spoilers in the thread.".to_owned()),
auth,
..Default::default()
};
let response = client
.post(helper::api_url("post"))
.json(&params)
.send()
.await?;
let data = response.json::<PostResponse>().await?;
Ok(data.post_view.post.id)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("A wordle a day keeps the doctor away!");
let client = Client::new();
let auth = lemmy_login(&client).await?;
let data = get_current_nyt_wordle_data().await?;
let id = post_to_lemmy(&data, &client, auth).await?;
println!("New post ID: {}", id);
Ok(())
}