mirror of
https://github.com/shiroyashik/doggy-watch.git
synced 2025-12-06 12:31:13 +03:00
Compare commits
No commits in common. "master" and "v0.2.1" have entirely different histories.
12 changed files with 282 additions and 468 deletions
493
Cargo.lock
generated
493
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "doggy-watch"
|
||||
authors = ["Shiroyashik <shiroyashik@shsr.ru>"]
|
||||
version = "0.2.2"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
|
@ -26,7 +26,6 @@ tracing-panic = "0.1"
|
|||
lazy_static = "1.5"
|
||||
indexmap = "2.7"
|
||||
dashmap = "6.1"
|
||||
url = "2.5"
|
||||
|
||||
# https://github.com/teloxide/teloxide/issues/1154
|
||||
# [profile.dev]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
## Chef
|
||||
# FROM clux/muslrust:stable AS chef
|
||||
FROM rust:alpine AS chef
|
||||
FROM rust:1.84.0-alpine3.20 AS chef
|
||||
USER root
|
||||
RUN apk add --no-cache musl-dev libressl-dev
|
||||
RUN cargo install cargo-chef
|
||||
|
|
|
|||
|
|
@ -35,10 +35,6 @@ ID канала для проверки подписки.
|
|||
`trace, debug, info, warn, error`
|
||||
Также можно указать отдельный уровень логирования для отдельных целей.
|
||||
|
||||
`TELEGRAM_API_URL=<url>`
|
||||
|
||||
Сторонний Telegram Bot API сервер (необязательно).
|
||||
|
||||
### Только для Docker
|
||||
|
||||
`TZ=<TZ_identifier>`
|
||||
|
|
|
|||
|
|
@ -108,22 +108,22 @@ impl MigrationTrait for Migration {
|
|||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// Actions
|
||||
// Videos
|
||||
manager
|
||||
.drop_table(Table::drop().table(Actions::Table).to_owned())
|
||||
.drop_table(Table::drop().table(Videos::Table).to_owned())
|
||||
.await?;
|
||||
// Requests
|
||||
manager
|
||||
.drop_table(Table::drop().table(Requests::Table).to_owned())
|
||||
.await?;
|
||||
// Actions
|
||||
manager
|
||||
.drop_table(Table::drop().table(Actions::Table).to_owned())
|
||||
.await?;
|
||||
// Archived
|
||||
manager
|
||||
.drop_table(Table::drop().table(Archived::Table).to_owned())
|
||||
.await?;
|
||||
// Videos
|
||||
manager
|
||||
.drop_table(Table::drop().table(Videos::Table).to_owned())
|
||||
.await?;
|
||||
// Moderators
|
||||
manager
|
||||
.drop_table(Table::drop().table(Moderators::Table).to_owned())
|
||||
|
|
|
|||
|
|
@ -10,17 +10,10 @@ use crate::{check_subscription, markup, notify, AppState, DialogueState, MyDialo
|
|||
pub async fn message(bot: Bot, msg: Message, dialogue: MyDialogue) -> anyhow::Result<()> {
|
||||
use youtube::*;
|
||||
if let Some(text) = msg.clone().text() {
|
||||
if let Some(user) = check_subscription(&bot, &msg.clone().from.ok_or(anyhow::anyhow!("Message not from user!"))?.id).await {
|
||||
if let Some(user) = check_subscription(&bot, &msg.from.ok_or(anyhow::anyhow!("Message not from user!"))?.id).await {
|
||||
// Get ready!
|
||||
if let Some(ytid) = extract_youtube_video_id(text) {
|
||||
let meta = match get_video_metadata(&ytid).await {
|
||||
Ok(meta) => meta,
|
||||
Err(err) => {
|
||||
tracing::error!("Caused an exception in get_video_metadata due: {err:?}");
|
||||
bot.send_message(msg.chat.id, "Ошибка при получении метаданных видео!").await?;
|
||||
return Ok(());
|
||||
},
|
||||
};
|
||||
let meta = get_video_metadata(&ytid).await?;
|
||||
// Post
|
||||
bot.send_message(msg.chat.id, format!(
|
||||
"Вы уверены что хотите добавить <b>{}</b>",
|
||||
|
|
@ -28,7 +21,6 @@ pub async fn message(bot: Bot, msg: Message, dialogue: MyDialogue) -> anyhow::Re
|
|||
)).parse_mode(ParseMode::Html).reply_markup(markup::inline_yes_or_no()).await?;
|
||||
dialogue.update(DialogueState::AcceptVideo { ytid, uid: user.id.0, title: meta.title }).await?;
|
||||
} else {
|
||||
tracing::debug!("Not a YouTube video: {:?}", msg);
|
||||
bot.send_message(msg.chat.id, "Это не похоже на YouTube видео... Долбоёб").await?;
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,115 +1,34 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use teloxide::{prelude::*, types::{InlineKeyboardButton, InlineKeyboardMarkup, LinkPreviewOptions, ParseMode}};
|
||||
use teloxide::{prelude::*, types::{LinkPreviewOptions, ParseMode}};
|
||||
use sea_orm::{prelude::*, Order, QueryOrder};
|
||||
|
||||
use database::*;
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
struct Video {
|
||||
pub async fn command(bot: Bot, msg: Message, state: Arc<AppState>) -> anyhow::Result<()> {
|
||||
struct Video {
|
||||
id: i32,
|
||||
title: String,
|
||||
url: String,
|
||||
contributors: u64,
|
||||
status: String,
|
||||
}
|
||||
|
||||
pub async fn command(bot: Bot, msg: Message, state: Arc<AppState>) -> anyhow::Result<()> {
|
||||
}
|
||||
let videos: Vec<(requests::Model, Option<videos::Model>)> = requests::Entity::find()
|
||||
.find_also_related(videos::Entity).filter(videos::Column::Banned.eq(false)).all(&state.db).await?;
|
||||
|
||||
let result = generate_list(videos, &state).await;
|
||||
match result {
|
||||
Ok(list) => {
|
||||
let result = if let Some(list) = list {
|
||||
list
|
||||
} else {
|
||||
"Нет видео для просмотра :(".to_string()
|
||||
};
|
||||
|
||||
let keyboard: Vec<Vec<InlineKeyboardButton>> = vec![
|
||||
vec![InlineKeyboardButton::callback("Непросмотренные", "list_unviewed")],
|
||||
];
|
||||
|
||||
bot.send_message(msg.chat.id, result).parse_mode(ParseMode::Html)
|
||||
.link_preview_options(LinkPreviewOptions {
|
||||
is_disabled: true,
|
||||
url: None,
|
||||
prefer_small_media: false,
|
||||
prefer_large_media: false,
|
||||
show_above_text: false
|
||||
}).reply_markup(InlineKeyboardMarkup::new(keyboard)).await?;
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("{:?}", e);
|
||||
bot.send_message(msg.chat.id, "Произошла ошибка!").await?;
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn inline(state: Arc<AppState>, bot: Bot, q: CallbackQuery) -> anyhow::Result<()> {
|
||||
bot.answer_callback_query(&q.id).await?;
|
||||
let videos: Vec<(requests::Model, Option<videos::Model>)> = requests::Entity::find()
|
||||
.find_also_related(videos::Entity).filter(videos::Column::Banned.eq(false)).filter(requests::Column::ViewedAt.is_null()).all(&state.db).await?;
|
||||
let result = generate_list(videos, &state).await;
|
||||
match result {
|
||||
Ok(list) => {
|
||||
let result = if let Some(list) = list {
|
||||
list
|
||||
} else {
|
||||
"Нет видео для просмотра :(".to_string()
|
||||
};
|
||||
|
||||
let keyboard: Vec<Vec<InlineKeyboardButton>> = vec![
|
||||
vec![InlineKeyboardButton::callback("Обновить", "list_unviewed")],
|
||||
];
|
||||
|
||||
if let Some(message) = q.regular_message() {
|
||||
bot.edit_message_text(message.chat.id, message.id, result).parse_mode(ParseMode::Html)
|
||||
.link_preview_options(LinkPreviewOptions {
|
||||
is_disabled: true,
|
||||
url: None,
|
||||
prefer_small_media: false,
|
||||
prefer_large_media: false,
|
||||
show_above_text: false
|
||||
}).reply_markup(InlineKeyboardMarkup::new(keyboard)).await?;
|
||||
} else if let Some(message_id) = q.inline_message_id {
|
||||
bot.edit_message_text_inline(&message_id, result)
|
||||
.parse_mode(ParseMode::Html).disable_web_page_preview(true).reply_markup(InlineKeyboardMarkup::new(keyboard)).await?;
|
||||
} else {
|
||||
bot.send_message(q.from.id, result).parse_mode(ParseMode::Html)
|
||||
.reply_markup(InlineKeyboardMarkup::new(keyboard))
|
||||
.link_preview_options(LinkPreviewOptions {
|
||||
is_disabled: true,
|
||||
url: None,
|
||||
prefer_small_media: false,
|
||||
prefer_large_media: false,
|
||||
show_above_text: false
|
||||
}).await?;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("{:?}", e);
|
||||
bot.send_message(q.from.id, "Произошла ошибка!").await?;
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn generate_list(videos: Vec<(requests::Model, Option<videos::Model>)>, state: &AppState) -> anyhow::Result<Option<String>> {
|
||||
if videos.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
// let videos_len = videos.len();
|
||||
if !videos.is_empty() {
|
||||
let mut by_date: IndexMap<Date, Vec<Video>> = IndexMap::new();
|
||||
for (request, video) in videos {
|
||||
let video = video.unwrap();
|
||||
let creator = if let Some(c) = request.find_related(actions::Entity).order_by(actions::Column::Id, Order::Asc).one(&state.db).await? {
|
||||
c
|
||||
} else {
|
||||
anyhow::bail!("Can't find creator for {request:?}");
|
||||
let data = format!("Can't find creator for {request:?}");
|
||||
bot.send_message(msg.chat.id, data.clone()).await?;
|
||||
anyhow::bail!(data);
|
||||
};
|
||||
|
||||
let contributors = request.find_related(actions::Entity).count(&state.db).await?;
|
||||
|
|
@ -157,5 +76,9 @@ async fn generate_list(videos: Vec<(requests::Model, Option<videos::Model>)>, st
|
|||
}
|
||||
}
|
||||
// result.push_str(&format!("\nВсего: {}", videos_len));
|
||||
Ok(Some(result))
|
||||
bot.send_message(msg.chat.id, result).parse_mode(ParseMode::Html).link_preview_options(LinkPreviewOptions { is_disabled: true, url: None, prefer_small_media: false, prefer_large_media: false, show_above_text: false }).await?;
|
||||
} else {
|
||||
bot.send_message(msg.chat.id, "Нет видео для просмотра :(").await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ pub fn schema() -> UpdateHandler<anyhow::Error> {
|
|||
use dptree::case;
|
||||
let moderator_commands = dptree::entry()
|
||||
.branch(case![Command::Start].endpoint(start::command_mod))
|
||||
.branch(case![Command::Help].endpoint(start::command_mod))
|
||||
.branch(case![Command::List].endpoint(list::command))
|
||||
.branch(case![Command::Archive].endpoint(archive::command))
|
||||
.branch(case![Command::Mods].endpoint(moderator::list::command))
|
||||
|
|
@ -74,7 +73,6 @@ pub fn schema() -> UpdateHandler<anyhow::Error> {
|
|||
InlineCommand::parse(&q.data?)
|
||||
}))
|
||||
.branch(case![InlineCommand::Cancel].endpoint(cancel))
|
||||
.branch(case![InlineCommand::ListUnviewed].endpoint(list::inline))
|
||||
.branch(filter(|com: InlineCommand| {
|
||||
matches!(com, InlineCommand::ArchiveAll | InlineCommand::ArchiveViewed)
|
||||
}).endpoint(archive::inline))
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub async fn inline(bot: Bot, q: CallbackQuery, state: Arc<AppState>, uid: Strin
|
|||
if let Some(data) = q.clone().data {
|
||||
let text= if &data == "yes" {
|
||||
if let Ok(uid) = uid.parse::<u64>() {
|
||||
if Entity::delete_by_id(uid as i64).exec(&state.db).await?.rows_affected != 0 {
|
||||
if Entity::delete_by_id(uid as i32).exec(&state.db).await?.rows_affected != 0 {
|
||||
"Модератор удалён!"
|
||||
} else {
|
||||
"Произошла ошибка!\nПо всей видимости такого модератора не существует."
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::AppState;
|
|||
|
||||
/// Invert notify status for moderator
|
||||
pub async fn command(bot: Bot, msg: Message, uid: UserId, state: Arc<AppState>) -> anyhow::Result<()> {
|
||||
let text = if let Some(moder) = moderators::Entity::find_by_id(uid.0 as i64).one(&state.db).await? {
|
||||
let text = if let Some(moder) = moderators::Entity::find_by_id(uid.0 as i32).one(&state.db).await? {
|
||||
let moder = match moder.notify {
|
||||
true => {
|
||||
let mut moder = moder.into_active_model();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ pub enum InlineCommand {
|
|||
Unview(i32),
|
||||
ArchiveViewed,
|
||||
ArchiveAll,
|
||||
ListUnviewed,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +19,6 @@ impl InlineCommand {
|
|||
"unview" => Self::Unview(parts.next()?.parse().ok()?),
|
||||
"archive_viewed" => Self::ArchiveViewed,
|
||||
"archive_all" => Self::ArchiveAll,
|
||||
"list_unviewed" => Self::ListUnviewed,
|
||||
"cancel" => Self::Cancel,
|
||||
_ => return None,
|
||||
})
|
||||
|
|
|
|||
17
src/main.rs
17
src/main.rs
|
|
@ -17,7 +17,6 @@ mod markup;
|
|||
|
||||
mod inline;
|
||||
pub use inline::InlineCommand;
|
||||
use url::Url;
|
||||
|
||||
pub const COOLDOWN_DURATION: Duration = Duration::from_secs(10);
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
|
@ -29,12 +28,6 @@ lazy_static! {
|
|||
pub static ref TOKEN: String = {
|
||||
var("TOKEN").expect("TOKEN env not set.")
|
||||
};
|
||||
pub static ref TELEGRAM_API_URL: Url = {
|
||||
match var("TELEGRAM_API_URL") {
|
||||
Ok(url) => url.parse().expect("Can't parse TELEGRAM_API_URL"),
|
||||
Err(_) => teloxide::net::TELEGRAM_API_URL.parse().expect("Failed to parse default Telegram bot API url")
|
||||
}
|
||||
};
|
||||
pub static ref DATABASE_URL: String = {
|
||||
var("DATABASE_URL").expect("DATABASE_URL env not set.")
|
||||
};
|
||||
|
|
@ -67,8 +60,8 @@ async fn main() -> anyhow::Result<()> {
|
|||
}));
|
||||
|
||||
tracing::info!("Doggy-Watch v{VERSION}");
|
||||
tracing::info!("admins: {:?} tg api: {}", *ADMINISTRATORS, TELEGRAM_API_URL.as_str());
|
||||
let bot = Bot::new(&*TOKEN).set_api_url(TELEGRAM_API_URL.clone());
|
||||
tracing::info!("{:?}", *ADMINISTRATORS);
|
||||
let bot = Bot::new(&*TOKEN);
|
||||
|
||||
let mut opt = ConnectOptions::new(&*DATABASE_URL);
|
||||
opt.sqlx_logging_level(tracing::log::LevelFilter::Trace);
|
||||
|
|
@ -100,7 +93,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
// Pass the shared state to the handler as a dependency.
|
||||
.dependencies(dptree::deps![state, InMemStorage::<DialogueState>::new()])
|
||||
.default_handler(|upd| async move {
|
||||
tracing::debug!("Unhandled update: {:?}", upd);
|
||||
tracing::warn!("Unhandled update: {:?}", upd);
|
||||
})
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
|
|
@ -127,8 +120,6 @@ pub enum DialogueState {
|
|||
enum Command {
|
||||
#[command(description = "запустить бота и/или вывести этот текст.")]
|
||||
Start,
|
||||
#[command(description = "вывести этот текст.")]
|
||||
Help,
|
||||
#[command(description = "вывести список.")]
|
||||
List,
|
||||
#[command(description = "действия с архивом.")]
|
||||
|
|
@ -193,7 +184,7 @@ impl AppState {
|
|||
async fn check_rights(&self, uid: &UserId) -> anyhow::Result<Rights> {
|
||||
use database::moderators::Entity as Moderators;
|
||||
|
||||
Ok(if let Some(moder) = Moderators::find_by_id(uid.0 as i64).one(&self.db).await? {
|
||||
Ok(if let Some(moder) = Moderators::find_by_id(uid.0 as i32).one(&self.db).await? {
|
||||
Rights::Moderator { can_add_mods: moder.can_add_mods }
|
||||
} else {
|
||||
Rights::None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue