Compare commits
24 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
a9fb91c771 | |
|
|
8602c883d3 | |
|
|
04cd52f7e4 | |
|
|
fb74ae27bd | |
|
|
f0ef9ac755 | |
|
|
4e60cef97a | |
|
|
d180f8d664 | |
|
|
096234f423 | |
|
|
16825f006f | |
|
|
da04f2d4e0 | |
|
|
cbc0b0cec9 | |
|
|
8c2251d41c | |
|
|
4ed442632c | |
|
|
742609b4af | |
|
|
db6eed1ac8 | |
|
|
3d712fdcb3 | |
|
|
ec8447bc58 | |
|
|
0b2e2bcb4a | |
|
|
be130790ea | |
|
|
0354cc6569 | |
|
|
7609e2007e | |
|
|
cdf823a94d | |
|
|
88c5a1b1a0 | |
|
|
57b79e7a5e |
23
Cargo.toml
23
Cargo.toml
|
|
@ -1,32 +1,45 @@
|
||||||
[package]
|
[package]
|
||||||
name = "manifold"
|
name = "manifold"
|
||||||
version = "3.0.0"
|
version = "8.0.0"
|
||||||
authors = ["Lucy Bladen <admin@lbladen.uk>"]
|
authors = ["Lucy Bladen <admin@lbladen.uk>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = { version = "0.6.0", features = ["git2", "chrono"] }
|
built = { version = "0.8.0", features = ["git2", "chrono"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
built = { version = "0.6.0", features = ["git2", "chrono"] }
|
built = { version = "0.8.0", features = ["git2", "chrono"] }
|
||||||
chrono = "0.4.26"
|
chrono = "0.4.26"
|
||||||
chrono-tz = "0.8.3"
|
chrono-tz = "0.8.3"
|
||||||
clap = "4.3.4"
|
clap = "4.3.4"
|
||||||
config = { version = "0.13.1", features = [ "yaml" ] }
|
config = { version = "0.13.1", features = [ "yaml" ] }
|
||||||
d20 = "0.1.0"
|
d20 = "0.1.0"
|
||||||
diesel = { version = "2.1.0", features = ["sqlite", "r2d2", "chrono"] }
|
diesel = { version = "2.1.0", features = ["postgres", "r2d2", "chrono"] }
|
||||||
diesel_migrations = "2.1.0"
|
diesel_migrations = "2.1.0"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
num = "0.4.1"
|
num = "0.4.1"
|
||||||
poise = "0.5.5"
|
poise = { version = "0.5.*", features = [ "cache" ] }
|
||||||
r2d2 = "0.8.9"
|
r2d2 = "0.8.9"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.5.4"
|
regex = "1.5.4"
|
||||||
reqwest = "0.11.9"
|
reqwest = "0.11.9"
|
||||||
|
rust-i18n = "3.1.2"
|
||||||
serde = "1.0.171"
|
serde = "1.0.171"
|
||||||
serde_json = "1.0.105"
|
serde_json = "1.0.105"
|
||||||
tokio = { version = "1.16.1", features = ["sync", "macros", "rt-multi-thread"] }
|
tokio = { version = "1.16.1", features = ["sync", "macros", "rt-multi-thread"] }
|
||||||
|
|
||||||
|
[package.metadata.i18n]
|
||||||
|
# The available locales for your application, default: ["en"].
|
||||||
|
available-locales = ["en", "de"]
|
||||||
|
|
||||||
|
# The default locale, default: "en".
|
||||||
|
default-locale = "en"
|
||||||
|
|
||||||
|
# Path for your translations YAML file, default: "locales".
|
||||||
|
# This config for let `cargo i18n` command line tool know where to find your translations.
|
||||||
|
# You must keep this path same as the one you pass to method `rust_i18n::i18n!`.
|
||||||
|
load-path = "config/locales"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
_version: 2
|
||||||
|
global:
|
||||||
|
not_available:
|
||||||
|
de: Nicht verfügbar
|
||||||
|
en: Not Available
|
||||||
|
not_available_with_id:
|
||||||
|
de: "Nicht verfügbar (%{id})"
|
||||||
|
en: "Not Available (%{id})"
|
||||||
|
|
||||||
|
logging:
|
||||||
|
bot_start:
|
||||||
|
de: Bot hat sich mit Discord verbunden und ist bereit für Befehlseingabe.
|
||||||
|
en: Manifold bot connected to discord and ready to begin broadcast operations.
|
||||||
|
logged_command:
|
||||||
|
de: "Befehl %{command} von %{name} erhalten"
|
||||||
|
en: "Received command %{command} from %{name}"
|
||||||
|
user_banned:
|
||||||
|
de: "%{user} wurde gesperrt"
|
||||||
|
en: "%{user} was banned"
|
||||||
|
user_unbanned:
|
||||||
|
de: "Die Sperre wurde für %{user} aufgehoben"
|
||||||
|
en: "Ban was lifted for %{user}"
|
||||||
|
user_joined:
|
||||||
|
de: "%{name} ist dem Server mit dem Spitznamen %{nickname} beigetreten (%{uid})"
|
||||||
|
en: "%{name} joined the server with nickname %{nickname} (%{uid})"
|
||||||
|
user_left:
|
||||||
|
de: "%{name} hat den Server verlassen"
|
||||||
|
en: "%{name} left the server"
|
||||||
|
role_created:
|
||||||
|
de: "Neue Rolle %{name} (%{role_id}) wurde erstellt"
|
||||||
|
en: "New role %{name} (%{role_id}) was created"
|
||||||
|
permissions:
|
||||||
|
de: Berechtigungen für Rollen
|
||||||
|
en: Role Permissions
|
||||||
|
hoist:
|
||||||
|
de: Hochziehen
|
||||||
|
en: Hoist
|
||||||
|
icon:
|
||||||
|
de: Ikone
|
||||||
|
en: Icon
|
||||||
|
mentionable:
|
||||||
|
de: Erwähnenswert
|
||||||
|
en: Mentionable
|
||||||
|
role_deleted:
|
||||||
|
de: "Rolle %{name} (%{role_id}) wurde gelöscht"
|
||||||
|
en: "Role %{name} (%{role_id}) was deleted"
|
||||||
|
permissions:
|
||||||
|
de: Berechtigungen für Rollen
|
||||||
|
en: Role Permissions
|
||||||
|
hoist:
|
||||||
|
de: Hochziehen
|
||||||
|
en: Hoist
|
||||||
|
icon:
|
||||||
|
de: Symbol
|
||||||
|
en: Icon
|
||||||
|
mentionable:
|
||||||
|
de: Erwähnenswert
|
||||||
|
en: Mentionable
|
||||||
|
not_cached:
|
||||||
|
de: "Rolle %{role_id} wurde gelöscht (Rolle nicht zwischengespeichert, keine Rolleninformationen verfügbar)"
|
||||||
|
en: "Role %{role_id} was deleted (Role not cached, no role info available)"
|
||||||
|
message_deleted:
|
||||||
|
de: "Nachricht in #%{channel} (%{channel_id}) entfernt"
|
||||||
|
en: "Message removed in #%{channel} (%{channel_id})"
|
||||||
|
content:
|
||||||
|
de: Inhalt der Nachricht
|
||||||
|
en: Message Content
|
||||||
|
attachments:
|
||||||
|
de: Nachrichten-Anhänge
|
||||||
|
en: Message Attachments
|
||||||
|
not_cached:
|
||||||
|
de: "Nachricht entfernt in #%{channel_name} (%{channel_id}) (Nicht zwischengespeichert)"
|
||||||
|
en: "Message removed in #%{channel_name} (%{channel_id}) (Not cached)"
|
||||||
|
channel_not_cached:
|
||||||
|
de: "Nicht verfügbar / Nicht zwischengespeichert (%{channel_id})"
|
||||||
|
en: "Not Available / Not Cached (%{channel_id})"
|
||||||
|
message_edited:
|
||||||
|
de: "Nachricht aktualisiert in #%{channel_name} (%{channel_id})"
|
||||||
|
en: "Message updated in #%{channel_name} (%{channel_id})"
|
||||||
|
original_content:
|
||||||
|
de: Ursprünglicher Inhalt
|
||||||
|
en: Original Content
|
||||||
|
new_content:
|
||||||
|
de: Neue Inhalte
|
||||||
|
en: New Content
|
||||||
|
created_at:
|
||||||
|
de: Nachricht erstellt am
|
||||||
|
en: Message created at
|
||||||
|
author:
|
||||||
|
de: Verfasser der Nachricht
|
||||||
|
en: Message author
|
||||||
|
|
||||||
|
commands:
|
||||||
|
admin:
|
||||||
|
config_dump:
|
||||||
|
de: "Konfigurations-Deponie; %{Dump}"
|
||||||
|
en: "Config dump; %{dump}"
|
||||||
|
start_watching:
|
||||||
|
de: "Okay, ich beobachte nun, %{target}."
|
||||||
|
en: "Okay, I'll start watching %{target}"
|
||||||
|
saving_the_world:
|
||||||
|
de: Speicher-Befehl erhalten, speichere Welt...
|
||||||
|
en: Save call received, saving the world...
|
||||||
|
save_complete:
|
||||||
|
de: Speichern abgeschlossen; Speicherte %{user_count} Benutzer.
|
||||||
|
en: "Save call complete; Saved %{user_count} users."
|
||||||
|
animal:
|
||||||
|
no_frog_tips:
|
||||||
|
de: Es konnten keine Hinweise gefunden werden.
|
||||||
|
en: No tips could be located. Try rubbing it.
|
||||||
|
no_animal_pic:
|
||||||
|
de: Die Suche schien kein Erfolg zu haben. Vielleicht hilft etwas Speck?
|
||||||
|
en: No frens here at the moment. Perhaps some bacon would help?
|
||||||
|
convert:
|
||||||
|
specify:
|
||||||
|
de: Geben Sie eine Konvertierung an. Weitere Informationen finden Sie in der Hilfe.
|
||||||
|
en: Specify a conversion. See help for more info.
|
||||||
|
converted:
|
||||||
|
de: "%{input_value}%{input_unit} ist %{output_value}%{output_unit}"
|
||||||
|
en: "%{input_value}%{input_unit} is %{output_value}%{output_unit}"
|
||||||
|
core:
|
||||||
|
set_timezone:
|
||||||
|
reject_invalid_timezone:
|
||||||
|
de: Ungültige Zeitzone
|
||||||
|
en: Are you having a laugh? What kind of timezone is that?
|
||||||
|
accept_timezone:
|
||||||
|
de: "Ich habe Ihre Zeitzone auf %{timezone} eingestellt, aber wir alle wissen, dass UTC die einzig wahre Zeitzone ist"
|
||||||
|
en: "I have set your timezone to %{timezone}, but we all know that UTC is the One True Timezone"
|
||||||
|
time:
|
||||||
|
unknown_timezone:
|
||||||
|
de: Ve vill tell ze Zeit!
|
||||||
|
en: This user hasn't given me their timezone yet. Shame on them!
|
||||||
|
unknown_user:
|
||||||
|
de: Wer ist das? Ich kenne sie nicht.
|
||||||
|
en: Who's that? I don't know them.
|
||||||
|
time_output:
|
||||||
|
de: "Die Zeit in %{name}'s Zeitzone von %{timezone} ist %{time}"
|
||||||
|
en: "Time in %{name}'s current timezone of %{timezone} is %{time}"
|
||||||
|
dad_joke:
|
||||||
|
no_jokes:
|
||||||
|
de: Ich kann keine Dad-Jokes finden. Vielleicht ist dein Vater nicht lustig.
|
||||||
|
en: Can't find any dad jokes. Maybe your dad isn't funny.
|
||||||
|
nasa_apod:
|
||||||
|
no_picture:
|
||||||
|
de: Das NASA Astronomy Photo of the Day fehlte. Versuchen Sie stattdessen, nach oben zu schauen.
|
||||||
|
en: The NASA Astronomy Photo of the Day was missing. Try looking up instead.
|
||||||
|
weather:
|
||||||
|
no_location:
|
||||||
|
de: Ich weiß nicht, wo du wohnst, und du hast es mir nicht gesagt, also kann ich nicht helfen. Schauen Sie aus dem Fenster.
|
||||||
|
en: I don't know where you live, and you didn't tell me, so I can't help. Look out of the window.
|
||||||
|
no_weather:
|
||||||
|
de: "Irgendetwas ist schief gelaufen. Vielleicht gibt es dort kein Wetter. %{error_output}"
|
||||||
|
en: "Something went wrong. Maybe there is no weather there. %{error_output}"
|
||||||
|
retrieve_wait:
|
||||||
|
de: Wetter abrufen, Geduld haben
|
||||||
|
en: Retrieving weather, be patient
|
||||||
|
current_weather:
|
||||||
|
de: "Aktuelles Wetter bei %{location_name}, %{location_region}, %{location_country}"
|
||||||
|
en: "Current weather at %{location_name}, %{location_region}, %{location_country}"
|
||||||
|
forecast_time:
|
||||||
|
de: "Prognose für %{forecast_time} erstellt um %{forecast_updated}"
|
||||||
|
en: "Forecast for %{forecast_time} made at %{forecast_updated}"
|
||||||
|
temperature_dewpoint:
|
||||||
|
de: Temperatur (Taupunkt)
|
||||||
|
en: Temperature (Dewpoint)
|
||||||
|
feels_like:
|
||||||
|
de: Fühlt sich wie
|
||||||
|
en: Feels like
|
||||||
|
condition:
|
||||||
|
de: Zustand
|
||||||
|
en: Condition
|
||||||
|
pressure:
|
||||||
|
de: Druck
|
||||||
|
en: Pressure
|
||||||
|
precipitation:
|
||||||
|
de: Niederschlag
|
||||||
|
en: Precipitation
|
||||||
|
humidity:
|
||||||
|
de: Feuchtigkeit
|
||||||
|
en: Humidity
|
||||||
|
cloud_coverage:
|
||||||
|
de: Wolkenbedeckung
|
||||||
|
en: Cloud coverage
|
||||||
|
coordinates:
|
||||||
|
de: Koordinaten
|
||||||
|
en: Coordinates
|
||||||
|
wind:
|
||||||
|
de: Wind
|
||||||
|
en: Wind
|
||||||
|
wind_value:
|
||||||
|
de: "%{wind_mph}mph/%{wind_kph}km/h von den %{wind_dir} (%{wind_degree} Grad), Böen auf %{gust_mph}mph/%{gust_kph}km/h"
|
||||||
|
en: "%{wind_mph}mph/%{wind_kph}kph from the %{wind_dir} (%{wind_degree} degrees), gusting to %{gust_mph}mph/%{gust_kph}kph"
|
||||||
|
weather_location:
|
||||||
|
location_save_confirm:
|
||||||
|
de: "Okay. Jetzt weiß ich, dass du in %{location} lebst. Ob das so weise war?"
|
||||||
|
en: "Okay. Now I know that you live in %{location}. Are you sure that was wise?"
|
||||||
|
|
||||||
|
misc:
|
||||||
|
version_string:
|
||||||
|
de: "%{caller} (Mannigfaltigkeits-Framework-Version %{mfold_ver}, erstellt um %{mfold_time} aus der Revision %{mfold_rev})"
|
||||||
|
en: "%{caller} (Manifold framework version %{mfold_ver} built at %{mfold_time} from revision %{mfold_rev})"
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {}}:
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
openssl
|
||||||
|
postgresql
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use poise::serenity_prelude::Activity;
|
use poise::serenity_prelude::Activity;
|
||||||
|
|
||||||
use crate::{ManifoldContext, ManifoldData, ManifoldResult};
|
use crate::{ManifoldContext, ManifoldData, ManifoldResult};
|
||||||
use crate::error::ManifoldError;
|
use crate::error::ManifoldError;
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, owners_only)]
|
#[poise::command(slash_command, prefix_command, owners_only)]
|
||||||
async fn dump_config(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
async fn dump_config(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
ctx.say(format!("Config dump; {:?}", &ctx.data().bot_config)).await?;
|
ctx.say(t!("commands.admin.config_dump", dump = format!("{:?}", &ctx.data().bot_config))).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -16,17 +17,17 @@ async fn register_commands(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, track_edits, aliases("sa"), required_permissions = "MODERATE_MEMBERS")]
|
#[poise::command(slash_command, prefix_command, track_edits, aliases("watch"), required_permissions = "MODERATE_MEMBERS")]
|
||||||
async fn set_activity(ctx: ManifoldContext<'_>, #[rest] #[description="Who to watch"] target: String) -> ManifoldResult<()> {
|
async fn set_activity(ctx: ManifoldContext<'_>, #[rest] #[description="Who to watch"] target: String) -> ManifoldResult<()> {
|
||||||
ctx.serenity_context().set_activity(Activity::watching(&target)).await;
|
ctx.serenity_context().set_activity(Activity::watching(&target)).await;
|
||||||
|
|
||||||
ctx.say(format!("Okay, I'll start watching {}", target)).await?;
|
ctx.say(t!("commands.admin.start_watching", target = target)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, owners_only)]
|
#[poise::command(slash_command, prefix_command, owners_only)]
|
||||||
async fn save_world(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
async fn save_world(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
let my_message = ctx.send(|f| f.content("Save call received, saving the world...").reply(true)).await?;
|
let my_message = ctx.send(|f| f.content(t!("commands.admin.saving_the_world")).reply(true)).await?;
|
||||||
|
|
||||||
let mut user_count = 0;
|
let mut user_count = 0;
|
||||||
|
|
||||||
|
|
@ -38,7 +39,7 @@ async fn save_world(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
Ok(_) => debug!("User {:?} inserted successfully", user.0),
|
Ok(_) => debug!("User {:?} inserted successfully", user.0),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("User {:?} was not inserted: {:?}, attempting to save instead", user.1, e);
|
debug!("User {:?} was not inserted: {:?}, attempting to save instead", user.1, e);
|
||||||
match user.1.save(db) {
|
match user.1.insert(db) {
|
||||||
Ok(_) => debug!("User {:?} saved successfully, user is saved in database.", user.0),
|
Ok(_) => debug!("User {:?} saved successfully, user is saved in database.", user.0),
|
||||||
Err(e) => debug!("User {:?} could not be saved: {:?}", user.1, e),
|
Err(e) => debug!("User {:?} could not be saved: {:?}", user.1, e),
|
||||||
};
|
};
|
||||||
|
|
@ -47,7 +48,7 @@ async fn save_world(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
user_count = userinfo.len();
|
user_count = userinfo.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
my_message.edit(ctx, |f| f.content(format!("Save call complete; Saved {} users.", user_count))).await?;
|
my_message.edit(ctx, |f| f.content(t!("commands.admin.save_complete", user_count = user_count))).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ async fn frog_tip(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?;
|
ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?;
|
||||||
} else {
|
} else {
|
||||||
error!("No such fuel tank frog_tips");
|
error!("No such fuel tank frog_tips");
|
||||||
ctx.send(|f| f.content("No tips could be located. Try rubbing it.".to_string()).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.animal.no_frog_tips").to_string()).reply(true)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -39,7 +39,7 @@ async fn animal_pic(ctx: ManifoldContext<'_>, tank_name: &str) -> ManifoldResult
|
||||||
ctx.send(|f| f.content(format!("{}", pic)).reply(true)).await?;
|
ctx.send(|f| f.content(format!("{}", pic)).reply(true)).await?;
|
||||||
} else {
|
} else {
|
||||||
error!("No such fuel tank {}", tank_name);
|
error!("No such fuel tank {}", tank_name);
|
||||||
ctx.send(|f| f.content("No frens here at the moment. Perhaps some bacon would help?".to_string()).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.animal.no_animal_pic").to_string()).reply(true)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -4,35 +4,35 @@ use crate::{ManifoldContext, ManifoldData};
|
||||||
#[poise::command(slash_command, prefix_command, aliases("c"), subcommands("ftoc", "ftok", "ftor", "ctof", "ctok", "ctor", "rtof", "rtoc", "rtok", "ktoc", "ktof", "ktor"))]
|
#[poise::command(slash_command, prefix_command, aliases("c"), subcommands("ftoc", "ftok", "ftor", "ctof", "ctok", "ctor", "rtof", "rtoc", "rtok", "ktoc", "ktof", "ktor"))]
|
||||||
pub async fn convert(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
pub async fn convert(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
|
||||||
ctx.send(|f| f.content("Specify a conversion. See help for more info").reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.specify")).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ftoc(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ftoc(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°F is {}°C", value, ((value - 32.0)/1.8))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_value = value, input_unit = "°F", output_value = ((value - 32.0)/1.8), output_unit = "°C")).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ftok(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ftok(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°F is {}°K", value, (((value - 32.0)/1.8) + 273.0))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "°F", input_value = value, output_unit = "K", output_value = (((value - 32.0)/1.8) + 273.0))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ftor(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ftor(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°F is {}°R", value, (value + 459.67))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "°F", input_value = value, output_unit = "°Ra", output_value = (value + 459.67))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ctof(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ctof(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°C is {}°F", value, ((value * 1.8)+32.0))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "°C", input_value = value, output_unit = "°F", output_value = ((value * 1.8)+32.0))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -46,49 +46,49 @@ pub async fn ctok(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ctor(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ctor(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°C is {}°R", value, ((value + 273.15) * (9.0/5.0)))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "°C", input_value = value, output_unit = "°Ra", output_value = ((value + 273.15) * (9.0/5.0)))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ktoc(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ktoc(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°K is {}°C", value, (value - 273.15))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "K", input_value = value, output_unit = "°C", output_value = (value - 273.15))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ktof(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ktof(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°K is {}°F", value, ((value * (9.0/5.0)) - 459.67))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "K", input_value = value, output_unit = "°F", output_value = ((value * (9.0/5.0)) - 459.67))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn ktor(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn ktor(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°K is {}°R", value, (value * (9.0/5.0)))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "K", input_value = value, output_unit = "°Ra", output_value = (value * (9.0/5.0)))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn rtok(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn rtok(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°R is {}°K", value, (value * (5.0/9.0)))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "°Ra", input_value = value, output_unit = "K", output_value = (value * (5.0/9.0)))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn rtoc(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn rtoc(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°R is {}°C", value, ((value - 491.67) * (5.0/9.0)))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "°Ra", input_value = value, output_unit = "°C", output_value = ((value - 491.67) * (5.0/9.0)))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn rtof(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
pub async fn rtof(ctx: ManifoldContext<'_>, value: f32) -> ManifoldResult<()> {
|
||||||
ctx.send(|f| f.content(format!("{}°R is {}°F", value, (value - 459.67))).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.convert.converted", input_unit = "°Ra", input_value = value, output_unit = "°F", output_value = (value - 459.67))).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,14 @@ async fn set_timezone(ctx: ManifoldContext<'_>, input_timezone: String) -> Manif
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Problem parsing timezone input: {:?}", e);
|
error!("Problem parsing timezone input: {:?}", e);
|
||||||
ctx.send(|f| f.content(format!("Are you having a laugh? What kind of timezone is that? {:?}", e)).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.core.set_timezone.reject_invalid_timezone")).reply(true)).await?;
|
||||||
Err(e)?
|
Err(e)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(user) = userinfo.get_mut(ctx.author().id.as_u64()) {
|
if let Some(user) = userinfo.get_mut(ctx.author().id.as_u64()) {
|
||||||
user.timezone = Some(validated_timezone.to_string());
|
user.timezone = Some(validated_timezone.to_string());
|
||||||
ctx.send(|f| f.content(format!("I have set your timezone to {}, but we all know that UTC is the One True Timezone.", validated_timezone.to_string())).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.core.set_timezone.accept_timezone", timezone = validated_timezone.to_string())).reply(true)).await?;
|
||||||
} else {
|
} else {
|
||||||
let new_user = UserInfo {
|
let new_user = UserInfo {
|
||||||
user_id: ctx.author().id.as_u64().to_owned() as i64,
|
user_id: ctx.author().id.as_u64().to_owned() as i64,
|
||||||
|
|
@ -73,7 +73,7 @@ async fn set_timezone(ctx: ManifoldContext<'_>, input_timezone: String) -> Manif
|
||||||
last_seen: Some(chrono::Utc::now().timestamp())
|
last_seen: Some(chrono::Utc::now().timestamp())
|
||||||
};
|
};
|
||||||
userinfo.insert(ctx.author().id.as_u64().clone(), new_user);
|
userinfo.insert(ctx.author().id.as_u64().clone(), new_user);
|
||||||
ctx.send(|f| f.content(format!("I have set your timezone to {}, but we all know that UTC is the One True Timezone.", validated_timezone.to_string())).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.core.set_timezone.accept_timezone", timezone = validated_timezone.to_string())).reply(true)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -86,16 +86,16 @@ pub async fn time(ctx: ManifoldContext<'_>, target: u64) -> ManifoldResult<()> {
|
||||||
let user = match userinfo.get(&target) {
|
let user = match userinfo.get(&target) {
|
||||||
Some(u) => match u.timezone.to_owned() {
|
Some(u) => match u.timezone.to_owned() {
|
||||||
Some(_) => u,
|
Some(_) => u,
|
||||||
None => {ctx.send(|f| f.content(format!("This user hasn't given me their timezone yet. Shame on them!")).reply(true)).await?; Err("NoTZ")?}
|
None => {ctx.send(|f| f.content(t!("commands.core.time.unknown_timezone")).reply(true)).await?; Err("NoTZ")?}
|
||||||
},
|
},
|
||||||
None => { ctx.send(|f| f.content(format!("Who's that? I don't know them.")).reply(true)).await?; Err("User not found")?}
|
None => { ctx.send(|f| f.content(t!("commands.core.time.unknown_user")).reply(true)).await?; Err("User not found")?}
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_time_utc = chrono::Utc::now();
|
let current_time_utc = chrono::Utc::now();
|
||||||
let user_timezone: Tz = user.timezone.as_ref().unwrap().parse::<Tz>().unwrap();
|
let user_timezone: Tz = user.timezone.as_ref().unwrap().parse::<Tz>().unwrap();
|
||||||
let adjusted_time = user_timezone.from_utc_datetime(¤t_time_utc.naive_utc());
|
let adjusted_time = user_timezone.from_utc_datetime(¤t_time_utc.naive_utc());
|
||||||
|
|
||||||
ctx.send(|f| f.content(format!("Time in {}'s current timezone of {} is {}", user.username, user_timezone.name().to_string(), adjusted_time.time().to_string())).reply(true)).await?;
|
ctx.send(|f| f.content(t!("commands.core.time.time_output", name = user.username, timezone = user_timezone.name().to_string(), time = adjusted_time.time().to_string())).reply(true)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
use crate::{ManifoldContext, ManifoldData};
|
||||||
|
use crate::models::dadjoke::{DadJoke, DadJokeWrapper};
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("dad"))]
|
||||||
|
async fn dad_joke(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
|
||||||
|
debug!("Processing tip");
|
||||||
|
|
||||||
|
if let Some(tank) = ctx.data().bot_config.services.get("dad_jokes") {
|
||||||
|
let tip: &DadJoke = &tank.next_or_fill::<DadJoke, DadJokeWrapper>().await?;
|
||||||
|
|
||||||
|
debug!("Tip: {:?}", tip);
|
||||||
|
|
||||||
|
ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?;
|
||||||
|
} else {
|
||||||
|
error!("No such fuel tank dad_jokes");
|
||||||
|
ctx.send(|f| f.content(t!("commands.dad_joke.no_jokes")).reply(true)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 1] {
|
||||||
|
[dad_joke()]
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ mod admin;
|
||||||
mod animal;
|
mod animal;
|
||||||
mod convert;
|
mod convert;
|
||||||
mod core;
|
mod core;
|
||||||
|
mod joke;
|
||||||
|
mod nasa;
|
||||||
mod weather;
|
mod weather;
|
||||||
|
|
||||||
use crate::ManifoldCommand;
|
use crate::ManifoldCommand;
|
||||||
|
|
@ -11,6 +13,8 @@ pub fn collect_commands(injected: Vec<ManifoldCommand>) -> Vec<ManifoldCommand>
|
||||||
.chain(admin::commands())
|
.chain(admin::commands())
|
||||||
.chain(animal::commands())
|
.chain(animal::commands())
|
||||||
.chain(convert::commands())
|
.chain(convert::commands())
|
||||||
|
.chain(joke::commands())
|
||||||
|
.chain(nasa::commands())
|
||||||
.chain(weather::commands())
|
.chain(weather::commands())
|
||||||
.chain(injected)
|
.chain(injected)
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
use crate::{ManifoldContext, ManifoldData};
|
||||||
|
use crate::models::nasa::*;
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("apod"))]
|
||||||
|
async fn nasa_apod(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
|
||||||
|
debug!("Processing tip");
|
||||||
|
|
||||||
|
if let Some(tank) = ctx.data().bot_config.services.get("nasa_apod") {
|
||||||
|
let photo: &NasaAstroPhoto = &tank.next_or_fill::<NasaAstroPhoto, NasaAstroPhoto>().await?;
|
||||||
|
|
||||||
|
debug!("Tip: {:?}", photo);
|
||||||
|
|
||||||
|
ctx.send(|f| {
|
||||||
|
f.content(format!("{}", photo))
|
||||||
|
.reply(true)
|
||||||
|
.embed(|e| {
|
||||||
|
e.title(&photo.title);
|
||||||
|
e.description(&photo.explanation);
|
||||||
|
e.image(&photo.hdurl);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
} else {
|
||||||
|
error!("No such fuel tank nasa_apod");
|
||||||
|
ctx.send(|f| f.content(t!("commands.nasa_apod.no_picture")).reply(true)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 1] {
|
||||||
|
[nasa_apod()]
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::helpers::paginate;
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, aliases("w"))]
|
#[poise::command(slash_command, prefix_command, aliases("w"))]
|
||||||
async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to look up weather for"] location: Option<String>) -> ManifoldResult<()> {
|
async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to look up weather for"] location: Option<String>) -> ManifoldResult<()> {
|
||||||
let my_message = ctx.say("Retrieving weather, be patient").await?;
|
let my_message = ctx.say(t!("commands.weather.retrieve_wait")).await?;
|
||||||
|
|
||||||
let weather_forecast = _get_weather(ctx, &my_message, location).await?;
|
let weather_forecast = _get_weather(ctx, &my_message, location).await?;
|
||||||
|
|
||||||
|
|
@ -26,20 +26,20 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo
|
||||||
d.hour.iter().for_each(|f| {
|
d.hour.iter().for_each(|f| {
|
||||||
pages.push(CreateEmbed::default()
|
pages.push(CreateEmbed::default()
|
||||||
.colour(card_colour)
|
.colour(card_colour)
|
||||||
.title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country))
|
.title(t!("commands.weather.current_weather", location_name = weather_forecast.location.name, location_region = weather_forecast.location.region, location_country = weather_forecast.location.country))
|
||||||
.description(format!("Forecast for {} made at {}.", f.time, weather_forecast.current.last_updated))
|
.description(t!("commands.weather.forecast_time", forecast_time = f.time, forecast_updated = weather_forecast.current.last_updated))
|
||||||
.image(format!("https:{}", f.condition.icon))
|
.image(format!("https:{}", f.condition.icon))
|
||||||
.fields(vec![
|
.fields(vec![
|
||||||
("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", f.temp_c, f.temp_f, f.dewpoint_c, f.dewpoint_f), true),
|
(t!("commands.weather.temperature_dewpoint"), format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", f.temp_c, f.temp_f, f.dewpoint_c, f.dewpoint_f), true),
|
||||||
("Feels like", format!("{}°C/{}°F", f.feelslike_c, f.feelslike_f), true),
|
(t!("commands.weather.feels_like"), format!("{}°C/{}°F", f.feelslike_c, f.feelslike_f), true),
|
||||||
("Condition", format!("{}", f.condition.text), true),
|
(t!("commands.weather.condition"), format!("{}", f.condition.text), true),
|
||||||
("Pressure", format!("{}mb/{}in", f.pressure_mb, f.pressure_in), true),
|
(t!("commands.weather.pressure"), format!("{}mb/{}in", f.pressure_mb, f.pressure_in), true),
|
||||||
("Precipitation", format!("{}mm/{}in", f.precip_mm, f.precip_in), true),
|
(t!("commands.weather.precipitation"), format!("{}mm/{}in", f.precip_mm, f.precip_in), true),
|
||||||
("Humidity", format!("{}%", f.humidity), true),
|
(t!("commands.weather.humidity"), format!("{}%", f.humidity), true),
|
||||||
("Cloud coverage", format!("{}%", f.cloud), true),
|
(t!("commands.weather.cloud_coverage"), format!("{}%", f.cloud), true),
|
||||||
("Coordinates", format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true),
|
(t!("commands.weather.coordinates"), format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true),
|
||||||
])
|
])
|
||||||
.field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", f.wind_mph, f.wind_kph, f.wind_dir, f.wind_degree, f.gust_mph, f.gust_kph), false)
|
.field(t!("commands.weather.wind"), t!("commands.weather.wind_value", wind_mph = f.wind_mph, wind_kph = f.wind_kph, wind_dir = f.wind_dir, wind_degree = f.wind_degree, gust_mph = f.gust_mph, gust_kph = f.gust_kph), false)
|
||||||
.footer(|f| {
|
.footer(|f| {
|
||||||
f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string())));
|
f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string())));
|
||||||
f
|
f
|
||||||
|
|
@ -62,7 +62,7 @@ pub async fn save_weather_location(ctx: ManifoldContext<'_>, #[rest] #[descripti
|
||||||
|
|
||||||
if let Some(existing_user) = userinfo.get_mut(&ctx.author().id.as_u64()) {
|
if let Some(existing_user) = userinfo.get_mut(&ctx.author().id.as_u64()) {
|
||||||
existing_user.weather_location = Some(location.clone());
|
existing_user.weather_location = Some(location.clone());
|
||||||
existing_user.save(db)?;
|
existing_user.insert(db)?;
|
||||||
} else {
|
} else {
|
||||||
let new_user = UserInfo {
|
let new_user = UserInfo {
|
||||||
user_id: ctx.author().id.as_u64().clone() as i64,
|
user_id: ctx.author().id.as_u64().clone() as i64,
|
||||||
|
|
@ -76,7 +76,7 @@ pub async fn save_weather_location(ctx: ManifoldContext<'_>, #[rest] #[descripti
|
||||||
userinfo.insert(ctx.author().id.as_u64().clone(), new_user);
|
userinfo.insert(ctx.author().id.as_u64().clone(), new_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.say(format!("Okay. Now I know that you live in {}. Are you sure that was wise?", &location)).await?;
|
ctx.say(t!("commands.weather_location.location_save_confirm", location = &location)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +85,6 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle
|
||||||
|
|
||||||
let mut weather_location: String = String::default();
|
let mut weather_location: String = String::default();
|
||||||
|
|
||||||
let responses = &ctx.data().responses;
|
|
||||||
let config = &ctx.data().bot_config;
|
let config = &ctx.data().bot_config;
|
||||||
|
|
||||||
let userinfo = &mut ctx.data().user_info.lock().await;
|
let userinfo = &mut ctx.data().user_info.lock().await;
|
||||||
|
|
@ -105,13 +104,13 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
message_handle.edit(ctx, |m| {
|
message_handle.edit(ctx, |m| {
|
||||||
m.content = responses.get_response(&"weather noloc".to_string()).cloned(); m
|
m.content = Some(t!("commands.weather.no_location").to_string()); m
|
||||||
}).await?;
|
}).await?;
|
||||||
Err("No location provided")?;
|
Err("No location provided")?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
ctx.say(responses.get_response(&"weather noloc".to_string()).unwrap_or(&"B I don't know where you live, and you didn't tell me, so I can't help. Look out of the window.".to_string())).await?;
|
ctx.say(t!("commands.weather.no_location")).await?;
|
||||||
Err("No location provided")?;
|
Err("No location provided")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +120,7 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle
|
||||||
let weather_forecast: WeatherForecastRequestResponse = match weather_client.get_weather_forecast(weather_location).await {
|
let weather_forecast: WeatherForecastRequestResponse = match weather_client.get_weather_forecast(weather_location).await {
|
||||||
Ok(w) => w,
|
Ok(w) => w,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ctx.say(format!("Something went wrong. Maybe there is no weather there. {:?}", e)).await?;
|
ctx.say(t!("commands.weather.no_weather", error_output = format!("{:?}", e))).await?;
|
||||||
Err("unknown")?
|
Err("unknown")?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use poise::serenity_prelude::ChannelId;
|
use poise::serenity_prelude::ChannelId;
|
||||||
use crate::ManifoldResult;
|
use crate::ManifoldResult;
|
||||||
|
|
@ -10,13 +9,23 @@ pub struct Channels {
|
||||||
pub log: ChannelId,
|
pub log: ChannelId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct DatabaseConfig {
|
||||||
|
pub host: String,
|
||||||
|
pub user: String,
|
||||||
|
pub pass: String,
|
||||||
|
pub database_name: String,
|
||||||
|
pub port: i32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ManifoldConfig {
|
pub struct ManifoldConfig {
|
||||||
pub prefix: String,
|
pub prefix: String,
|
||||||
pub channels: Channels,
|
pub channels: Channels,
|
||||||
pub nickname: String,
|
pub nickname: String,
|
||||||
pub services: HashMap<String, FuelTank>,
|
pub services: HashMap<String, FuelTank>,
|
||||||
pub responses_file_path: PathBuf,
|
pub database: DatabaseConfig,
|
||||||
|
pub responses_file_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManifoldConfig {
|
impl ManifoldConfig {
|
||||||
|
|
|
||||||
258
src/events.rs
258
src/events.rs
|
|
@ -1,42 +1,60 @@
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use poise::{Event, FrameworkContext};
|
use poise::{async_trait, Event, FrameworkContext};
|
||||||
use poise::serenity_prelude::model::{
|
use poise::serenity_prelude::model::{
|
||||||
gateway::Ready,
|
gateway::Ready,
|
||||||
};
|
};
|
||||||
use poise::serenity_prelude::{Context, Message};
|
use poise::serenity_prelude::{ChannelId, Colour, Context, GuildId, Member, Message, MessageId, Role, RoleId, Timestamp, User};
|
||||||
use crate::ManifoldData;
|
use crate::ManifoldData;
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
use crate::models::user::UserInfo;
|
use crate::models::user::UserInfo;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait EventHandler {
|
||||||
|
async fn listen(ctx: Context, framework_ctx: FrameworkContext<'_, ManifoldData, ManifoldError>, event: &Event<'_>) -> ManifoldResult<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Handler {
|
pub struct Handler {
|
||||||
pub timer_running: AtomicBool,
|
pub timer_running: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for Handler {
|
||||||
|
async fn listen(ctx: Context, framework_ctx: FrameworkContext<'_, ManifoldData, ManifoldError>, event: &Event<'_>) -> ManifoldResult<bool> {
|
||||||
|
match event {
|
||||||
|
Event::Ready { data_about_bot } => Handler::standard_startup(&ctx, &framework_ctx, data_about_bot).await,
|
||||||
|
Event::GuildBanAddition { guild_id, banned_user } => Handler::ban_add(&ctx, &framework_ctx, guild_id, banned_user).await,
|
||||||
|
Event::GuildBanRemoval { guild_id, unbanned_user } => Handler::ban_remove(&ctx, &framework_ctx, guild_id, unbanned_user).await,
|
||||||
|
Event::GuildMemberAddition { new_member } => Handler::new_member(&ctx, &framework_ctx, &new_member).await,
|
||||||
|
Event::GuildMemberRemoval { guild_id, user, member_data_if_available } => Handler::member_leave(&ctx, &framework_ctx, guild_id, user, member_data_if_available).await,
|
||||||
|
Event::GuildMemberUpdate { old_if_available, new } => Handler::member_update(&ctx, &framework_ctx, old_if_available, new).await,
|
||||||
|
Event::GuildRoleCreate { new } => Handler::new_role(&ctx, &framework_ctx, new).await,
|
||||||
|
Event::GuildRoleDelete { guild_id, removed_role_id, removed_role_data_if_available } => Handler::delete_role(&ctx, &framework_ctx, guild_id, removed_role_id, removed_role_data_if_available).await,
|
||||||
|
Event::GuildRoleUpdate { old_data_if_available, new } => Handler::update_role(&ctx, &framework_ctx, old_data_if_available, new).await,
|
||||||
|
Event::Message { new_message } => Handler::message(&ctx, &framework_ctx, &new_message).await,
|
||||||
|
Event::MessageDelete { channel_id, deleted_message_id, guild_id } => Handler::message_deleted(&ctx, &framework_ctx, channel_id, deleted_message_id, guild_id).await,
|
||||||
|
Event::MessageUpdate { old_if_available, new, event: _event } => Handler::message_edited(&ctx, &framework_ctx, old_if_available, new).await,
|
||||||
|
_ => Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Handler {
|
Handler {
|
||||||
timer_running: AtomicBool::from(false)
|
timer_running: AtomicBool::from(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn listen(ctx: &Context, framework_ctx: FrameworkContext<'_, ManifoldData, ManifoldError>, event: &Event<'_>) -> ManifoldResult<()> {
|
pub async fn standard_startup(ctx: &Context, framework_ctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, data_about_bot: &Ready) -> ManifoldResult<bool> {
|
||||||
match event {
|
|
||||||
Event::Ready { data_about_bot} => Handler::standard_startup(&ctx, &framework_ctx, data_about_bot).await,
|
|
||||||
Event::Message { new_message } => Handler::message(&ctx, &framework_ctx, &new_message).await,
|
|
||||||
Event::MessageUpdate { old_if_available, new, event: _event } => Handler::message_edited(&ctx, &framework_ctx, old_if_available, new).await,
|
|
||||||
_ => Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn standard_startup(ctx: &Context, framework_ctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, data_about_bot: &Ready) -> ManifoldResult<()> {
|
|
||||||
let config = &framework_ctx.user_data().await.bot_config;
|
let config = &framework_ctx.user_data().await.bot_config;
|
||||||
let responses = &framework_ctx.user_data().await.responses;
|
let responses = &framework_ctx.user_data().await.responses;
|
||||||
|
|
||||||
|
|
||||||
let greeting = match responses.get_response(&"bot startup".to_string()) {
|
let greeting = match responses.get_response(&"bot startup".to_string()) {
|
||||||
Some(g) => g.to_owned(),
|
Some(g) => g.to_owned(),
|
||||||
None => "Manifold bot connected to discord and ready to begin broadcast operations.".to_string(),
|
None => t!("logging.bot_start").to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for guild in &data_about_bot.guilds {
|
for guild in &data_about_bot.guilds {
|
||||||
|
|
@ -50,14 +68,151 @@ impl Handler {
|
||||||
|
|
||||||
config.channels.log.say(&ctx, greeting).await.expect("Couldn't message log channel!");
|
config.channels.log.say(&ctx, greeting).await.expect("Couldn't message log channel!");
|
||||||
|
|
||||||
Ok(())
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn message(_ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, msg: &Message) -> ManifoldResult<()> {
|
async fn ban_add(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, banned_user: &User) -> ManifoldResult<bool> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(t!("logging.user_banned", user = banned_user.name))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ban_remove(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, unbanned_user: &User) -> ManifoldResult<bool> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(t!("logging.user_unbanned", user = unbanned_user.name))
|
||||||
|
.colour(Colour::from_rgb(0, 255, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_member(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, new_member: &Member) -> ManifoldResult<bool> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(t!("logging.user_joined", name = new_member.user.name, nickname = new_member.nick.as_ref().unwrap_or(&new_member.user.name), uid = new_member.user.id))
|
||||||
|
.colour(Colour::from_rgb(0, 255, 0))
|
||||||
|
.timestamp(new_member.joined_at.unwrap_or(Timestamp::now()))
|
||||||
|
.field("Account creation date", new_member.user.created_at(), false)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn member_leave(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, user: &User, _member_data_if_available: &Option<Member>) -> ManifoldResult<bool> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(t!("logging.user_left", name = user.name))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn member_update(_ctx: &Context, _fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _old_if_available: &Option<Member>, _new: &Member) -> ManifoldResult<bool> {
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_role(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, new: &Role) -> ManifoldResult<bool> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(t!("logging.role_created", name = &new.name, role_id = &new.id))
|
||||||
|
.colour(Colour::from_rgb(0, 255, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
.field(t!("logging.role_created.permissions"), &new.permissions, false)
|
||||||
|
.field(t!("logging.role_created.hoist"), &new.hoist, true)
|
||||||
|
.field(t!("logging.role_created.icon"), &new.icon.clone().unwrap_or("None set".to_string()), true)
|
||||||
|
.field(t!("logging.role_created.mentionable"), &new.mentionable, true)
|
||||||
|
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn delete_role(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, removed_role_id: &RoleId, removed_role_data_if_available: &Option<Role>) -> ManifoldResult<bool> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
|
||||||
|
match removed_role_data_if_available {
|
||||||
|
Some(role) => {
|
||||||
|
return e
|
||||||
|
.title(t!("logging.role_deleted", name = &role.name, role_id = &role.id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
.field(t!("logging.role_deleted.permissions"), &role.permissions, false)
|
||||||
|
.field(t!("logging.role_deleted.hoist"), &role.hoist, true)
|
||||||
|
.field(t!("logging.role_deleted.icon"), &role.icon.clone().unwrap_or("None set".to_string()), true)
|
||||||
|
.field(t!("logging.role_deleted.mentionable"), &role.mentionable, true)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return e
|
||||||
|
.title(t!("logging.role_deleted.not_cached", role_id = removed_role_id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn update_role(_ctx: &Context, _fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _old_data_if_available: &Option<Role>, _new: &Role) -> ManifoldResult<bool> {
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn message(_ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, msg: &Message) -> ManifoldResult<bool> {
|
||||||
let userinfo = &mut fctx.user_data().await.user_info.lock().await;
|
let userinfo = &mut fctx.user_data().await.user_info.lock().await;
|
||||||
|
let db = &fctx.user_data().await.database;
|
||||||
|
|
||||||
if let Some(u) = userinfo.get_mut(&msg.author.id.as_u64()) {
|
if let Some(u) = userinfo.get_mut(&msg.author.id.as_u64()) {
|
||||||
u.last_seen = Some(chrono::Utc::now().timestamp());
|
u.last_seen = Some(chrono::Utc::now().timestamp());
|
||||||
|
u.insert(db)?;
|
||||||
} else {
|
} else {
|
||||||
let new_user = UserInfo {
|
let new_user = UserInfo {
|
||||||
user_id: msg.author.id.as_u64().clone() as i64,
|
user_id: msg.author.id.as_u64().clone() as i64,
|
||||||
|
|
@ -68,33 +223,90 @@ impl Handler {
|
||||||
last_seen: Some(chrono::Utc::now().timestamp()),
|
last_seen: Some(chrono::Utc::now().timestamp()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
new_user.insert(db)?;
|
||||||
userinfo.insert(msg.author.id.as_u64().clone(), new_user);
|
userinfo.insert(msg.author.id.as_u64().clone(), new_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn message_edited(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, original: &Option<Message>, new_message: &Option<Message>) -> ManifoldResult<()> {
|
async fn message_deleted(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, channel_id: &ChannelId, deleted_message_id: &MessageId, _guild_id: &Option<GuildId>) -> ManifoldResult<bool> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
let channel = match ctx.cache.guild_channel(channel_id) {
|
||||||
|
Some(c) => c.name,
|
||||||
|
None => t!("global.not_available_with_id", id = channel_id).to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
match ctx.cache.message(channel_id, deleted_message_id) {
|
||||||
|
Some(msg) => {
|
||||||
|
|
||||||
|
let mut attachment_urls: Vec<String> = Vec::new();
|
||||||
|
for attachment in &msg.attachments {
|
||||||
|
attachment_urls.push(attachment.url.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(t!("logging.message_deleted", channel = channel, channel_id = channel_id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.author(|a| a.name(&msg.author.name))
|
||||||
|
.field(t!("logging.message_deleted.content"), msg.content_safe(&ctx.cache), false)
|
||||||
|
.field(t!("logging.message_deleted.attachments"), attachment_urls.join(", "), false)
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
.footer(|f| f.text(&msg.author.id));
|
||||||
|
for attachment in &msg.attachments {
|
||||||
|
e.image(attachment.url.clone());
|
||||||
|
}
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(t!("logging.message_deleted.not_cached", channel_name = channel, channel_id = channel_id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn message_edited(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, original: &Option<Message>, new_message: &Option<Message>) -> ManifoldResult<bool> {
|
||||||
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
if let Some(new) = new_message.as_ref() {
|
if let Some(new) = new_message.as_ref() {
|
||||||
|
let message_channel = new.channel_id.name(ctx).await.unwrap_or("Unknown Channel".to_string());
|
||||||
log_channel.send_message(ctx, |f| {
|
log_channel.send_message(ctx, |f| {
|
||||||
f
|
f
|
||||||
.content("")
|
.content("")
|
||||||
.embed(|e| {
|
.embed(|e| {
|
||||||
e
|
e
|
||||||
.title("Message updated")
|
.title(t!("logging.message_edited", channel_name = message_channel, channel_id = new.channel_id))
|
||||||
|
.colour(Colour::from_rgb(255, 153, 0))
|
||||||
.author(|a| a.name(new.author.name.clone()))
|
.author(|a| a.name(new.author.name.clone()))
|
||||||
.timestamp(new.timestamp)
|
.timestamp(Timestamp::now())
|
||||||
.field("Original Content", match original.as_ref() {
|
.field(t!("logging.message_edited.original_content"), match original.as_ref() {
|
||||||
Some(m) => m.content.clone(),
|
Some(m) => m.content.clone(),
|
||||||
None => "Not available".to_string(),
|
None => t!("global.not_available").to_string(),
|
||||||
}, false)
|
}, false)
|
||||||
.field("New Content", new.content.clone(), false)
|
.field(t!("logging.message_edited.new_content"), new.content.clone(), false)
|
||||||
|
.field(t!("logging.message_edited.created_at"), new.timestamp, true)
|
||||||
|
.field(t!("logging.message_edited.author"), &new.author.id, true)
|
||||||
})
|
})
|
||||||
}).await?;
|
}).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ pub async fn paginate(ctx: ManifoldContext<'_>, message: ReplyHandle<'_>, pages:
|
||||||
// We defined our button IDs to start with `ctx_id`. If they don't, some other command's
|
// We defined our button IDs to start with `ctx_id`. If they don't, some other command's
|
||||||
// button was pressed
|
// button was pressed
|
||||||
.filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string()))
|
.filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string()))
|
||||||
// Timeout when no navigation button has been pressed for 24 hours
|
// Timeout when no navigation button has been pressed for five minutes
|
||||||
.timeout(std::time::Duration::from_secs(300))
|
.timeout(std::time::Duration::from_secs(300))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|
|
||||||
49
src/lib.rs
49
src/lib.rs
|
|
@ -1,4 +1,5 @@
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
|
#[macro_use] extern crate rust_i18n;
|
||||||
#[macro_use] extern crate serde;
|
#[macro_use] extern crate serde;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
@ -7,16 +8,16 @@ use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use diesel::pg::Pg;
|
||||||
use diesel::r2d2::ConnectionManager;
|
use diesel::r2d2::ConnectionManager;
|
||||||
use diesel::sqlite::Sqlite;
|
use diesel::PgConnection;
|
||||||
use diesel::SqliteConnection;
|
|
||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||||
use poise::framework::FrameworkBuilder;
|
use poise::framework::FrameworkBuilder;
|
||||||
use poise::serenity_prelude::*;
|
use poise::serenity_prelude::*;
|
||||||
|
|
||||||
use crate::config::ManifoldConfig;
|
use crate::config::ManifoldConfig;
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
use crate::events::Handler;
|
use crate::events::{EventHandler, Handler};
|
||||||
use crate::models::user::{ManifoldUserInfo, UserInfo};
|
use crate::models::user::{ManifoldUserInfo, UserInfo};
|
||||||
use crate::responses::Responses;
|
use crate::responses::Responses;
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ pub mod built_info {
|
||||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ManifoldDatabasePool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
|
pub type ManifoldDatabasePool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
||||||
|
|
||||||
pub struct Db {
|
pub struct Db {
|
||||||
pub pool: ManifoldDatabasePool,
|
pub pool: ManifoldDatabasePool,
|
||||||
|
|
@ -61,32 +62,37 @@ impl Deref for ManifoldData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ManifoldDataInner {
|
pub struct ManifoldDataInner {
|
||||||
bot_config: ManifoldConfig,
|
pub bot_config: ManifoldConfig,
|
||||||
database: Db,
|
pub database: Db,
|
||||||
responses: Responses,
|
pub responses: Responses,
|
||||||
user_info: Mutex<ManifoldUserInfo>,
|
pub user_info: Mutex<ManifoldUserInfo>,
|
||||||
version_string: String,
|
pub version_string: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ManifoldContext<'a> = poise::Context<'a, ManifoldData, ManifoldError>;
|
pub type ManifoldContext<'a> = poise::Context<'a, ManifoldData, ManifoldError>;
|
||||||
pub type ManifoldCommand = poise::Command<ManifoldData, ManifoldError>;
|
pub type ManifoldCommand = poise::Command<ManifoldData, ManifoldError>;
|
||||||
|
|
||||||
pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, injected_commands: Vec<ManifoldCommand>, caller_version_string: String) -> ManifoldResult<FrameworkBuilder<ManifoldData, ManifoldError>> {
|
i18n!("config/locales");
|
||||||
|
|
||||||
|
pub async fn prepare_client<T: EventHandler>(arguments: ArgMatches, intents: GatewayIntents, injected_commands: Vec<ManifoldCommand>, caller_version_string: String, caller_database_migrations: EmbeddedMigrations) -> ManifoldResult<FrameworkBuilder<ManifoldData, ManifoldError>> {
|
||||||
let bot_environment = arguments.get_one("environment").unwrap();
|
let bot_environment = arguments.get_one("environment").unwrap();
|
||||||
let config_file = arguments.get_one::<String>("config-file").unwrap();
|
let config_file = arguments.get_one::<String>("config-file").unwrap();
|
||||||
|
|
||||||
info!("Reading configuration...");
|
info!("Reading configuration...");
|
||||||
debug!("Configuration file path: {}", &config_file);
|
debug!("Configuration file path: {}", &config_file);
|
||||||
let bot_config = ManifoldConfig::new(&config_file, bot_environment).expect(&*format!("Could not read configuration file {}", &config_file));
|
let bot_config = ManifoldConfig::new(&config_file, bot_environment).expect(&*format!("Could not read configuration file {}", &config_file));
|
||||||
|
let bot_locale = arguments.get_one::<String>("locale").unwrap();
|
||||||
|
|
||||||
let manager = ConnectionManager::<SqliteConnection>::new("manifold.db");
|
rust_i18n::set_locale(bot_locale);
|
||||||
|
|
||||||
|
let manager = ConnectionManager::<PgConnection>::new(format!("postgresql://{user}:{pass}@{host}:{port}/{database}", user=&bot_config.database.user, pass=&bot_config.database.pass, host=&bot_config.database.host, port=&bot_config.database.port, database=&bot_config.database.database_name));
|
||||||
let pool = r2d2::Pool::builder()
|
let pool = r2d2::Pool::builder()
|
||||||
.max_size(1)
|
.max_size(1)
|
||||||
.build(manager)
|
.build(manager)
|
||||||
.expect("Database setup error!");
|
.expect("Database setup error!");
|
||||||
|
|
||||||
let mut responses = Responses::new();
|
let mut responses = Responses::new();
|
||||||
responses.reload(&PathBuf::from(&bot_config.responses_file_path)).expect("Could not load responses file!");
|
responses.reload(&PathBuf::from(&format!("{file}.{locale}.txt", file=bot_config.responses_file_path, locale=bot_locale))).expect("Could not load responses file!");
|
||||||
|
|
||||||
let token = env::var("DISCORD_TOKEN").expect(
|
let token = env::var("DISCORD_TOKEN").expect(
|
||||||
"Could not find an environment variable called DISCORD_TOKEN",
|
"Could not find an environment variable called DISCORD_TOKEN",
|
||||||
|
|
@ -95,11 +101,15 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
let framework = poise::Framework::builder()
|
let framework = poise::Framework::builder()
|
||||||
.options(poise::FrameworkOptions {
|
.options(poise::FrameworkOptions {
|
||||||
event_handler: |ctx, e, fctx, _| Box::pin(async move {
|
event_handler: |ctx, e, fctx, _| Box::pin(async move {
|
||||||
Handler::listen(ctx, fctx, e).await
|
match T::listen(ctx.clone(), fctx, e).await {
|
||||||
|
Ok(b) => if b { return Ok(()) },
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
_ = Handler::listen(ctx.clone(), fctx, e).await;
|
||||||
|
Ok(())
|
||||||
}),
|
}),
|
||||||
pre_command: |ctx: ManifoldContext<'_>| Box::pin(async move {
|
pre_command: |ctx: ManifoldContext<'_>| Box::pin(async move {
|
||||||
info!("Received command {} from {}", ctx.command().name, ctx.author().name);
|
debug!("Received command {} from {}", ctx.command().name, ctx.author().name);
|
||||||
let _ = ctx.data().bot_config.channels.log.say(ctx, format!("Received command {} from {}", ctx.command().name, ctx.author().name)).await;
|
|
||||||
}),
|
}),
|
||||||
commands: commands::collect_commands(injected_commands),
|
commands: commands::collect_commands(injected_commands),
|
||||||
prefix_options: poise::PrefixFrameworkOptions {
|
prefix_options: poise::PrefixFrameworkOptions {
|
||||||
|
|
@ -114,8 +124,9 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||||
ctx.set_activity(Activity::watching("you")).await;
|
ctx.set_activity(Activity::watching("you")).await;
|
||||||
|
ctx.cache.set_max_messages(350);
|
||||||
let db = Db { pool };
|
let db = Db { pool };
|
||||||
apply_migrations(&mut db.get()?);
|
apply_migrations(&mut db.get()?, caller_database_migrations);
|
||||||
let user_info = UserInfo::load(&db).expect("Could not load user info, rejecting");
|
let user_info = UserInfo::load(&db).expect("Could not load user info, rejecting");
|
||||||
let git_info: String = built_info::GIT_VERSION.unwrap_or("unknown").to_string();
|
let git_info: String = built_info::GIT_VERSION.unwrap_or("unknown").to_string();
|
||||||
|
|
||||||
|
|
@ -124,7 +135,7 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
database: db,
|
database: db,
|
||||||
responses,
|
responses,
|
||||||
user_info: Mutex::new(user_info),
|
user_info: Mutex::new(user_info),
|
||||||
version_string: format!("{caller} (Manifold framework version {mfold_ver} built at {mfold_time} from revision {mfold_rev})", caller=caller_version_string, mfold_ver=built_info::PKG_VERSION, mfold_time=built_info::BUILT_TIME_UTC, mfold_rev=git_info),
|
version_string: t!("misc.version_string", caller=caller_version_string, mfold_ver=built_info::PKG_VERSION, mfold_time=built_info::BUILT_TIME_UTC, mfold_rev=git_info).to_string(),
|
||||||
})))
|
})))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -132,8 +143,10 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
Ok(framework)
|
Ok(framework)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_migrations(conn: &mut impl MigrationHarness<Sqlite>) {
|
fn apply_migrations(conn: &mut impl MigrationHarness<Pg>, caller_migrations: EmbeddedMigrations) {
|
||||||
|
|
||||||
conn.run_pending_migrations(MIGRATIONS)
|
conn.run_pending_migrations(MIGRATIONS)
|
||||||
.expect("An error occurred applying migrations.");
|
.expect("An error occurred applying migrations.");
|
||||||
|
conn.run_pending_migrations(caller_migrations)
|
||||||
|
.expect("An error occurred applying caller migrations");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::models::fueltank::Pump;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DadJoke {
|
||||||
|
pub id: String,
|
||||||
|
pub joke: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DadJokeWrapper {
|
||||||
|
pub results: Vec<DadJoke>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DadJoke {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.joke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pump<DadJoke> for DadJokeWrapper {
|
||||||
|
fn pump(&mut self) -> Option<DadJoke> {
|
||||||
|
self.results.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.results.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
|
use std::env::temp_dir;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use dirs::home_dir;
|
|
||||||
use std::io::{SeekFrom, Seek};
|
use std::io::{SeekFrom, Seek};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::{ACCEPT, CONTENT_TYPE};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
|
@ -19,77 +19,90 @@ pub trait Pump<V> {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct FuelTank {
|
pub struct FuelTank {
|
||||||
pub(crate) source_uri: String,
|
pub source_uri: String,
|
||||||
cache_name: Option<String>,
|
cache_name: Option<String>,
|
||||||
pub(crate) api_key: Option<String>,
|
pub api_key: Option<String>,
|
||||||
cache_mode: TankMode,
|
cache_mode: TankMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum TankMode {
|
pub enum TankMode {
|
||||||
Cache,
|
Cache,
|
||||||
NoCache,
|
NoCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuelTank {
|
impl FuelTank {
|
||||||
pub fn new(source_uri: String, cache_name: Option<String>, api_key: Option<String>, cache_mode: TankMode) -> Self {
|
|
||||||
Self {
|
|
||||||
source_uri,
|
|
||||||
cache_name,
|
|
||||||
api_key,
|
|
||||||
cache_mode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn next_or_fill<T: Debug, U: Debug>(&self) -> ManifoldResult<T>
|
pub async fn next_or_fill<T: Debug, U: Debug>(&self) -> ManifoldResult<T>
|
||||||
where T: DeserializeOwned,
|
where T: DeserializeOwned,
|
||||||
U: Serialize,
|
U: Serialize,
|
||||||
U: DeserializeOwned, U: Pump<T>
|
U: DeserializeOwned, U: Pump<T>
|
||||||
{
|
{
|
||||||
debug!("Filling tank!");
|
let path = PathBuf::new()
|
||||||
let mut cache = {
|
.join(temp_dir())
|
||||||
let mut path = PathBuf::new();
|
.join(format!("manifold-{}-cache-{}.json", self.cache_name.clone().unwrap(), CACHE_VERSION));
|
||||||
path.push(home_dir().ok_or(ManifoldError::from("No home dir"))?);
|
|
||||||
path.push(".local");
|
|
||||||
path.push(format!("manifold-{}-cache-{}.json", self.cache_name.clone().unwrap(), CACHE_VERSION));
|
|
||||||
debug!("Cache path: {:?}", &path);
|
|
||||||
fs::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(path)
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Error creating cache file: {}", e);
|
|
||||||
ManifoldError::from("CACHE ERROR")
|
|
||||||
})?
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("Cache: {:?}", &cache);
|
let mut fuel = match self.cache_mode {
|
||||||
|
TankMode::Cache => {
|
||||||
|
// Read cache only if we have one
|
||||||
|
debug!("Filling tank!");
|
||||||
|
let mut cache = {
|
||||||
|
debug!("Cache path: {:?}", &path);
|
||||||
|
fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Error creating cache file: {}", e);
|
||||||
|
ManifoldError::from("CACHE ERROR")
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
let json: Result<U, serde_json::Error> = serde_json::from_reader(&mut cache);
|
debug!("Cache: {:?}", &cache);
|
||||||
debug!("Json: {:?}", &json);
|
|
||||||
let mut fuel = match json {
|
let json: Result<U, serde_json::Error> = serde_json::from_reader(&mut cache);
|
||||||
Ok(contents) => {
|
debug!("Json: {:?}", &json);
|
||||||
match contents.len() {
|
match json {
|
||||||
0 => self.emit().await?,
|
Ok(contents) => {
|
||||||
_ => contents,
|
match contents.len() {
|
||||||
|
0 => self.emit().await?,
|
||||||
|
_ => contents,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self.emit().await?,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
_ => self.emit().await?,
|
TankMode::NoCache => {
|
||||||
|
self.emit().await?
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Got tips: {:?}", &fuel);
|
debug!("Got tips: {:?}", &fuel);
|
||||||
let single = fuel.pump().ok_or(ManifoldError::from("CACHE ERROR"))?;
|
let single = fuel.pump().ok_or(ManifoldError::from("CACHE ERROR"))?;
|
||||||
|
|
||||||
debug!("Got single tip: {:?}", &single);
|
debug!("Got single tip: {:?}", &single);
|
||||||
cache
|
if self.cache_mode == TankMode::Cache {
|
||||||
.set_len(0)
|
// Write cache only if we have one
|
||||||
.and_then(|_| cache.seek(SeekFrom::Start(0)))?;
|
let mut cache = {
|
||||||
|
debug!("Cache path: {:?}", &path);
|
||||||
|
fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Error creating cache file: {}", e);
|
||||||
|
ManifoldError::from("CACHE ERROR")
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
cache
|
||||||
|
.set_len(0)
|
||||||
|
.and_then(|_| cache.seek(SeekFrom::Start(0)))?;
|
||||||
|
|
||||||
debug!("Writing cache");
|
debug!("Writing cache");
|
||||||
serde_json::to_writer(cache, &fuel)?;
|
serde_json::to_writer(cache, &fuel)?;
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(single)
|
Ok(single)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn emit<U: Debug>(&self) -> ManifoldResult<U>
|
pub async fn emit<U: Debug>(&self) -> ManifoldResult<U>
|
||||||
|
|
@ -99,10 +112,13 @@ impl FuelTank {
|
||||||
let result: U;
|
let result: U;
|
||||||
|
|
||||||
let mut client = Client::new().get(&self.source_uri)
|
let mut client = Client::new().get(&self.source_uri)
|
||||||
.header(CONTENT_TYPE, "application/json");
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.header(ACCEPT, "application/json");
|
||||||
|
|
||||||
if let Some(api_key) = &self.api_key {
|
if let Some(api_key) = &self.api_key {
|
||||||
client = client.header("X-API-KEY", api_key);
|
client = client.header("X-API-KEY", api_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = client.send().await?.json().await?;
|
result = client.send().await?.json().await?;
|
||||||
|
|
||||||
debug!("Result: {:?}", &result);
|
debug!("Result: {:?}", &result);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
pub mod animalpics;
|
pub mod animalpics;
|
||||||
pub mod fueltank;
|
pub mod dadjoke;
|
||||||
pub mod frogtip;
|
pub mod frogtip;
|
||||||
|
pub mod fueltank;
|
||||||
|
pub mod nasa;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod weather;
|
pub mod weather;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::models::fueltank::Pump;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct NasaAstroPhoto {
|
||||||
|
pub explanation: String,
|
||||||
|
pub hdurl: String,
|
||||||
|
pub title: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for NasaAstroPhoto {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.hdurl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pump<NasaAstroPhoto> for NasaAstroPhoto {
|
||||||
|
fn pump(&mut self) -> Option<NasaAstroPhoto> {
|
||||||
|
Some(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use diesel::{insert_into, update};
|
use diesel::insert_into;
|
||||||
|
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use crate::schema::userinfo::dsl as userinfo_dsl;
|
use crate::schema::userinfo::dsl as userinfo_dsl;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
|
||||||
#[derive(Identifiable, Insertable, AsChangeset, Debug, Queryable, Serialize, Clone)]
|
#[derive(Identifiable, Insertable, AsChangeset, Selectable, Debug, Queryable, Serialize, Clone)]
|
||||||
#[diesel(primary_key(user_id))]
|
#[diesel(primary_key(user_id))]
|
||||||
#[diesel(table_name = userinfo)]
|
#[diesel(table_name = userinfo)]
|
||||||
pub struct UserInfo {
|
pub struct UserInfo {
|
||||||
|
|
@ -48,15 +48,11 @@ impl UserInfo {
|
||||||
pub fn insert(&self, conn: &Db) -> ManifoldResult<usize> {
|
pub fn insert(&self, conn: &Db) -> ManifoldResult<usize> {
|
||||||
insert_into(userinfo_dsl::userinfo)
|
insert_into(userinfo_dsl::userinfo)
|
||||||
.values(self)
|
.values(self)
|
||||||
.execute(&mut conn.get()?)
|
.on_conflict(userinfo_dsl::user_id)
|
||||||
.map_err(|e| ManifoldError::from(e))
|
.do_update()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(&self, conn: &Db) -> ManifoldResult<usize> {
|
|
||||||
update(userinfo_dsl::userinfo)
|
|
||||||
.filter(userinfo::user_id.eq(self.user_id))
|
|
||||||
.set(self)
|
.set(self)
|
||||||
.execute(&mut conn.get()?)
|
.execute(&mut conn.get()?)
|
||||||
.map_err(|e| ManifoldError::from(e))
|
.map_err(|e| ManifoldError::from(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ pub struct DayAstro {
|
||||||
pub moonrise: String,
|
pub moonrise: String,
|
||||||
pub moonset: String,
|
pub moonset: String,
|
||||||
pub moon_phase: String,
|
pub moon_phase: String,
|
||||||
pub moon_illumination: String
|
pub moon_illumination: i64
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue