add bovine

This commit is contained in:
Eric S. Londres 2021-08-21 22:05:29 -04:00
parent 788dca687f
commit d348b548c1
Signed by: slondr
GPG Key ID: A2D25B4D5CB970E4
1 changed files with 158 additions and 141 deletions

View File

@ -1,27 +1,25 @@
// BeerHolderBot // BeerHolderBot
// Copyright (C) 2021 Eric S. Londres // Copyright (C) 2021 Eric S. Londres
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published // it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or // by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#![allow(non_snake_case)] #![allow(non_snake_case)]
use teloxide::{prelude::*, utils::command::BotCommand, requests::ResponseResult}; use teloxide::{prelude::*, utils::command::BotCommand, requests::ResponseResult};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::error::Error; use std::{error::Error, fs::File, io::prelude::*};
use std::fs::File;
use std::io::prelude::*;
use rand::prelude::*; use rand::prelude::*;
type AsyncResult<T> = Result<T, Box<dyn Error + Send + Sync>>; type AsyncResult<T> = Result<T, Box<dyn Error + Send + Sync>>;
@ -43,15 +41,15 @@ async fn die() -> AsyncResult<String> {
// open the file (read-only) // open the file (read-only)
let mut file = match File::open(&path) { let mut file = match File::open(&path) {
Ok(f) => f, Ok(f) => f,
Err(e) => return Err(Box::new(e)) Err(e) => return Err(Box::new(e))
}; };
// read the file's contents into a string // read the file's contents into a string
let mut contents = String::new(); let mut contents = String::new();
match file.read_to_string(&mut contents) { match file.read_to_string(&mut contents) {
Ok(_) => (), Ok(_) => (),
Err(reason) => return Err(Box::new(reason)) Err(reason) => return Err(Box::new(reason))
} }
// vectorize contents // vectorize contents
@ -75,7 +73,7 @@ async fn initialize_database() -> AsyncResult<()> {
async fn create_beer(chat_id: i64, content: String) -> AsyncResult<()> { async fn create_beer(chat_id: i64, content: String) -> AsyncResult<()> {
CONNECTION.lock().await CONNECTION.lock().await
.execute(format!("INSERT INTO tap (chat_id, text) VALUES ('{}', '{}')", chat_id, content))?; .execute(format!("INSERT INTO tap (chat_id, text) VALUES ('{}', '{}')", chat_id, content))?;
Ok(()) Ok(())
} }
@ -84,10 +82,10 @@ async fn get_all_beers(chat_id: i64) -> AsyncResult<Vec<Beer>> {
let c = CONNECTION.lock().await; let c = CONNECTION.lock().await;
let mut statement = c.prepare(format!("SELECT id, text FROM tap WHERE chat_id={}", chat_id))?; let mut statement = c.prepare(format!("SELECT id, text FROM tap WHERE chat_id={}", chat_id))?;
while let sqlite::State::Row = statement.next().unwrap() { while let sqlite::State::Row = statement.next().unwrap() {
beers.push(Beer { beers.push(Beer {
id: statement.read::<i64>(0)?, id: statement.read::<i64>(0)?,
text: statement.read::<String>(1)? text: statement.read::<String>(1)?
}); });
} }
Ok(beers) Ok(beers)
} }
@ -96,9 +94,9 @@ async fn get_beer_count(chat_id: i64) -> AsyncResult<i64> {
let c = CONNECTION.lock().await; let c = CONNECTION.lock().await;
let mut statement = c.prepare(format!("SELECT COUNT(id) FROM tap WHERE chat_id={}", chat_id))?; let mut statement = c.prepare(format!("SELECT COUNT(id) FROM tap WHERE chat_id={}", chat_id))?;
if let sqlite::State::Row = statement.next().unwrap() { if let sqlite::State::Row = statement.next().unwrap() {
Ok(statement.read::<i64>(0)?) Ok(statement.read::<i64>(0)?)
} else { } else {
Err("could not retrieve beer count".into()) Err("could not retrieve beer count".into())
} }
} }
@ -106,32 +104,40 @@ async fn quaff(id: i64) -> AsyncResult<String> {
let c = CONNECTION.lock().await; let c = CONNECTION.lock().await;
let mut statement = c.prepare(format!("SELECT text FROM tap WHERE id={}", id))?; let mut statement = c.prepare(format!("SELECT text FROM tap WHERE id={}", id))?;
if let sqlite::State::Row = statement.next()? { if let sqlite::State::Row = statement.next()? {
let text = statement.read::<String>(0)?; let text = statement.read::<String>(0)?;
// remove the beer from the database // remove the beer from the database
c.execute(format!("DELETE FROM tap WHERE id={}", id))?; c.execute(format!("DELETE FROM tap WHERE id={}", id))?;
Ok(text) Ok(text)
} else { } else {
Err("could not retrieve beer text".into()) Err("could not retrieve beer text".into())
}
}
async fn grab_photo(query: &'static str) -> AsyncResult<String> {
if let Some(access) = std::env::var_os("UNSPLASH_ACCESS") {
// call API to get a random picture of corn
let auth_uri = format!("https://api.unsplash.com/photos/random/?client_id={}&query={}", access.into_string().unwrap(), query);
let response = reqwest::get(&auth_uri)
.await.unwrap().text().await.unwrap();
// response format is some pretty nested json
let parsed_response = json::parse(&response);
if let Ok(url) = parsed_response {
let img_url = &url["urls"]["regular"];
Ok(img_url.to_string())
} else {
Err("No corn to harvest.".into())
}
} else {
Err("You don't have a farm.".into())
} }
} }
async fn harvest_corn() -> AsyncResult<String> { async fn harvest_corn() -> AsyncResult<String> {
if let Some(access) = std::env::var_os("UNSPLASH_ACCESS") { grab_photo("corn").await
// call API to get a random picture of corn }
let auth_uri = format!("https://api.unsplash.com/photos/random/?client_id={}&query={}", access.into_string().unwrap(), "corn");
let response = reqwest::get(&auth_uri) async fn bovine() -> AsyncResult<String> {
.await.unwrap().text().await.unwrap(); grab_photo("cow").await
// response format is some pretty nested json
let parsed_response = json::parse(&response);
if let Ok(url) = parsed_response {
let img_url = &url["urls"]["regular"];
Ok(img_url.to_string())
} else {
Err("No corn to harvest.".into())
}
} else {
Err("You don't have a farm.".into())
}
} }
//// TO IMPLEMENT A NEW COMMAND //// TO IMPLEMENT A NEW COMMAND
@ -156,104 +162,115 @@ enum Command {
#[command(description = "Get the number of beers on tap")] #[command(description = "Get the number of beers on tap")]
Count, Count,
#[command(description = "Die stupidly")] #[command(description = "Die stupidly")]
Yasd Yasd,
#[command(description = "Take a tripe to the pasture")]
Bovine
} }
async fn answer(cx: UpdateWithCx<AutoSend<Bot>, Message>, command: Command) -> ResponseResult<()> { async fn answer(cx: UpdateWithCx<AutoSend<Bot>, Message>, command: Command) -> ResponseResult<()> {
match command { match command {
Command::Help => cx.answer(Command::descriptions()).await?, Command::Help => cx.answer(Command::descriptions()).await?,
Command::Beer(b) => { Command::Beer(b) => {
if !b.is_empty() { if !b.is_empty() {
log::info!("Adding {} to list of beers", b); log::info!("Adding {} to list of beers", b);
// add the beer to the database // add the beer to the database
match create_beer(cx.chat_id(), b).await { match create_beer(cx.chat_id(), b).await {
Err(e) => cx.reply_to(format!("Er, something went wrong.\n{}", e)).await?, Err(e) => cx.reply_to(format!("Er, something went wrong.\n{}", e)).await?,
Ok(_) => { Ok(_) => {
// increment the global beer counter // increment the global beer counter
let cur = get_beer_count(cx.chat_id()).await.unwrap(); let cur = get_beer_count(cx.chat_id()).await.unwrap();
// respond with how many beers are held (globally) // respond with how many beers are held (globally)
cx.reply_to(format!("Currently holding {} beer{}", cur, if cur == 1 { "" } else { "s" })) cx.reply_to(format!("Currently holding {} beer{}", cur, if cur == 1 { "" } else { "s" }))
.await? .await?
} }
} }
} else { } else {
// the given beer was an empty string, so don't actually store it // the given beer was an empty string, so don't actually store it
cx.reply_to("Sorry, I can't hold that beer.").await? cx.reply_to("Sorry, I can't hold that beer.").await?
} }
}, },
Command::OnTap => { Command::OnTap => {
log::info!("Printing list of beers"); log::info!("Printing list of beers");
// if the tap is empty, print a special message so the Telegram API doesn't freak out // if the tap is empty, print a special message so the Telegram API doesn't freak out
match get_all_beers(cx.chat_id()).await { match get_all_beers(cx.chat_id()).await {
Err(e) => cx.reply_to(format!("Uh, something went wrong.\n{}", e)).await?, Err(e) => cx.reply_to(format!("Uh, something went wrong.\n{}", e)).await?,
Ok(beers) => { Ok(beers) => {
let mut m: String = String::new(); let mut m: String = String::new();
if beers.is_empty() { if beers.is_empty() {
cx.reply_to("Sorry, I'm all empty.").await? cx.reply_to("Sorry, I'm all empty.").await?
} else { } else {
for beer in beers { for beer in beers {
m += format!("[{}] {}\n", beer.id, beer.text).as_str(); m += format!("[{}] {}\n", beer.id, beer.text).as_str();
} }
cx.reply_to(m.as_str()).await? cx.reply_to(m.as_str()).await?
} }
} }
} }
}, },
Command::Quaff(beer) => { Command::Quaff(beer) => {
log::info!("Quaffing beer #{}", beer); log::info!("Quaffing beer #{}", beer);
// try to parse the user input as an integer // try to parse the user input as an integer
if let Ok(index) = beer.parse::<i64>() { if let Ok(index) = beer.parse::<i64>() {
let quaff_attempt = quaff(index); let quaff_attempt = quaff(index);
match quaff_attempt.await { match quaff_attempt.await {
Err(e) => cx.reply_to(format!("Sorry, we can't do that.\n{}", e)).await?, Err(e) => cx.reply_to(format!("Sorry, we can't do that.\n{}", e)).await?,
Ok(m) => { Ok(m) => {
// send a message informing which beer was quaffed // send a message informing which beer was quaffed
cx.reply_to(format!("You have quaffed \"{}\"", m)).await? cx.reply_to(format!("You have quaffed \"{}\"", m)).await?
} }
} }
} else { } else {
cx.reply_to("Sorry, we don't have that beer on tap.").await? cx.reply_to("Sorry, we don't have that beer on tap.").await?
} }
}, },
Command::Corn => { Command::Corn => {
// harvest corn // harvest corn
log::info!("Harvesting corn"); log::info!("Harvesting corn");
if let Ok(corn) = harvest_corn().await { if let Ok(corn) = harvest_corn().await {
cx.answer_photo(teloxide::types::InputFile::url(corn)).await? cx.answer_photo(teloxide::types::InputFile::url(corn)).await?
// cx.reply_to(format!("<img src=\"{}\"/>", corn)).parse_mode(teloxide::types::ParseMode::Html).await? // cx.reply_to(format!("<img src=\"{}\"/>", corn)).parse_mode(teloxide::types::ParseMode::Html).await?
} else { } else {
log::error!("An error occurred within harvest_corn()"); log::error!("An error occurred within harvest_corn()");
cx.reply_to("You don't have a farm.").await? cx.reply_to("You don't have a farm.").await?
} }
}, },
Command::Post => { Command::Bovine => {
log::info!("Generating new message"); log::info!("cow time");
let new_msg = telegram_markov_chain::chain(); if let Ok(bovine) = bovine().await {
log::info!("Posting new message"); cx.answer_photo(teloxide::types::InputFile::url(bovine)).await?
cx.reply_to(new_msg).await? } else {
}, log::error!("An error occurred within bovine()");
Command::Count => { cx.reply_to("No bovine").await?
log::info!("Counting bottles of beer on the wall"); }
if let Ok(count) = get_beer_count(cx.chat_id()).await { },
cx.reply_to(format!("{} bottles of beer on the wall.", count)).await? Command::Post => {
} else { log::info!("Generating new message");
cx.reply_to("I can't seem to find any beers.").await? let new_msg = telegram_markov_chain::chain();
} log::info!("Posting new message");
}, cx.reply_to(new_msg).await?
Command::Yasd => { },
log::info!("Dying stupidly..."); Command::Count => {
if let Ok(death) = die().await { log::info!("Counting bottles of beer on the wall");
let caller = cx.update.from().unwrap().username.as_ref().unwrap(); if let Ok(count) = get_beer_count(cx.chat_id()).await {
cx.reply_to(format!("<code> cx.reply_to(format!("{} bottles of beer on the wall.", count)).await?
} else {
cx.reply_to("I can't seem to find any beers.").await?
}
},
Command::Yasd => {
log::info!("Dying stupidly...");
if let Ok(death) = die().await {
let caller = cx.update.from().unwrap().username.as_ref().unwrap();
cx.reply_to(format!("<code>
---------- ----------
/ \\ / \\
/ REST \\ / REST \\
/ IN \\ / IN \\
/ PEACE \\ / PEACE \\
/ \\ / \\
| @{} | @{}
| {} | {}
| | | |
| | | |
@ -261,11 +278,11 @@ async fn answer(cx: UpdateWithCx<AutoSend<Bot>, Message>, command: Command) -> R
| 2021 | | 2021 |
*| * * * | * *| * * * | *
_________)/\\\\_//(\\/(/\\)/\\//\\/|_)_______</code>", caller, death)).parse_mode(teloxide::types::ParseMode::Html).await? _________)/\\\\_//(\\/(/\\)/\\//\\/|_)_______</code>", caller, death)).parse_mode(teloxide::types::ParseMode::Html).await?
} else { } else {
cx.reply_to("I just can't seem to die.").await? cx.reply_to("I just can't seem to die.").await?
} }
} }
}; };
Ok(()) Ok(())