Compare commits

...

4 Commits

Author SHA1 Message Date
boring_nick 822f32ae5d Merge branch 'master' into supibot 2023-07-07 10:33:00 +03:00
boring_nick e8ed3654b7 redirect to latest available log instead of current date 2023-07-04 17:38:25 +03:00
boring_nick 70d8af69f6 fix admin check on optout 2023-06-29 17:04:54 +03:00
boring_nick 4098036d31 add offset support 2023-06-28 08:54:12 +03:00
4 changed files with 102 additions and 31 deletions

View File

@ -13,7 +13,7 @@ use tokio::{
sync::mpsc::{Receiver, Sender},
time::sleep,
};
use tracing::{debug, error, info, trace};
use tracing::{debug, error, info, log::warn, trace};
use twitch_irc::{
login::LoginCredentials,
message::{AsRawIRC, IRCMessage, ServerMessage},
@ -155,13 +155,11 @@ impl Bot {
if let ServerMessage::Privmsg(privmsg) = &msg {
trace!("Processing message {}", privmsg.message_text);
if let Some(cmd) = privmsg.message_text.strip_prefix(COMMAND_PREFIX) {
if self.app.config.admins.contains(&privmsg.sender.login) {
self.handle_command(cmd, client, &privmsg.sender.id).await?;
} else {
info!(
"User {} is not an admin to use commands",
privmsg.sender.login
);
if let Err(err) = self
.handle_command(cmd, client, &privmsg.sender.id, &privmsg.sender.login)
.await
{
warn!("Could not handle command {cmd}: {err:#}");
}
}
}
@ -171,6 +169,20 @@ impl Bot {
Ok(())
}
fn check_admin(&self, user_login: &str) -> anyhow::Result<()> {
if self
.app
.config
.admins
.iter()
.any(|login| login == user_login)
{
Ok(())
} else {
Err(anyhow!("User {user_login} is not an admin"))
}
}
async fn write_message(&self, msg: ServerMessage) -> anyhow::Result<()> {
// Ignore
if matches!(msg, ServerMessage::RoomState(_)) {
@ -211,6 +223,7 @@ impl Bot {
cmd: &str,
client: &TwitchClient<C>,
sender_id: &str,
sender_login: &str,
) -> anyhow::Result<()> {
debug!("Processing command {cmd}");
let mut split = cmd.split_whitespace();
@ -219,10 +232,12 @@ impl Bot {
match action {
"join" => {
self.check_admin(sender_login)?;
self.update_channels(client, &args, ChannelAction::Join)
.await?
}
"leave" | "part" => {
self.check_admin(sender_login)?;
self.update_channels(client, &args, ChannelAction::Part)
.await?
}

View File

@ -23,9 +23,12 @@ pub async fn read_channel(
channel_id: &str,
log_date: ChannelLogDate,
reverse: bool,
limit: Option<u64>,
offset: Option<u64>,
) -> Result<LogsStream> {
let suffix = if reverse { "DESC" } else { "ASC" };
let query = format!("SELECT raw FROM message WHERE channel_id = ? AND toStartOfDay(timestamp) = ? ORDER BY timestamp {suffix}");
let mut query = format!("SELECT raw FROM message WHERE channel_id = ? AND toStartOfDay(timestamp) = ? ORDER BY timestamp {suffix}");
apply_limit_offset(&mut query, limit, offset);
let cursor = db
.query(&query)
@ -41,9 +44,12 @@ pub async fn read_user(
user_id: &str,
log_date: UserLogDate,
reverse: bool,
limit: Option<u64>,
offset: Option<u64>,
) -> Result<LogsStream> {
let suffix = if reverse { "DESC" } else { "ASC" };
let query = format!("SELECT raw FROM message WHERE channel_id = ? AND user_id = ? AND toStartOfMonth(timestamp) = ? ORDER BY timestamp {suffix}");
let mut query = format!("SELECT raw FROM message WHERE channel_id = ? AND user_id = ? AND toStartOfMonth(timestamp) = ? ORDER BY timestamp {suffix}");
apply_limit_offset(&mut query, limit, offset);
let cursor = db
.query(&query)
@ -188,3 +194,12 @@ pub async fn delete_user_logs(db: &Client, user_id: &str) -> Result<()> {
.await?;
Ok(())
}
fn apply_limit_offset(query: &mut String, limit: Option<u64>, offset: Option<u64>) {
if let Some(limit) = limit {
*query = format!("{query} LIMIT {limit}");
}
if let Some(offset) = offset {
*query = format!("{query} OFFSET {offset}");
}
}

View File

@ -25,7 +25,6 @@ use axum::{
response::Redirect,
Json, TypedHeader,
};
use chrono::{Datelike, Utc};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::time::Duration;
use tracing::debug;
@ -72,7 +71,15 @@ pub async fn get_channel_logs(
let log_date = ChannelLogDate::try_from(channel_log_params.date)?;
debug!("Querying logs for date {log_date:?}");
let stream = read_channel(&app.db, &channel_id, log_date, logs_params.reverse).await?;
let stream = read_channel(
&app.db,
&channel_id,
log_date,
logs_params.reverse,
logs_params.limit,
logs_params.offset,
)
.await?;
let logs = LogsResponse {
response_type: logs_params.response_type(),
@ -143,6 +150,8 @@ async fn get_user_logs(
&user_id,
log_date,
logs_params.reverse,
logs_params.limit,
logs_params.offset,
)
.await?;
@ -194,54 +203,72 @@ pub async fn redirect_to_latest_channel_logs(
channel,
}): Path<LogsPathChannel>,
RawQuery(query): RawQuery,
) -> Redirect {
let today = Utc::now();
let year = today.year();
let month = today.month();
let day = today.day();
app: State<App>,
) -> Result<Redirect> {
let channel_id = match channel_id_type {
ChannelIdType::Name => app.get_user_id_by_name(&channel).await?,
ChannelIdType::Id => channel.clone(),
};
let mut new_uri = format!("/{channel_id_type}/{channel}/{year}/{month}/{day}");
let available_logs = read_available_channel_logs(&app.db, &channel_id).await?;
let latest_log = available_logs.first().ok_or(Error::NotFound)?;
let mut new_uri = format!("/{channel_id_type}/{channel}/{latest_log}");
if let Some(query) = query {
new_uri.push('?');
new_uri.push_str(&query);
}
Redirect::to(&new_uri)
Ok(Redirect::to(&new_uri))
}
pub async fn redirect_to_latest_user_name_logs(
path: Path<UserLogPathParams>,
query: RawQuery,
) -> Redirect {
redirect_to_latest_user_logs(path, query, "user")
app: State<App>,
) -> Result<Redirect> {
redirect_to_latest_user_logs(path, query, false, app).await
}
pub async fn redirect_to_latest_user_id_logs(
path: Path<UserLogPathParams>,
query: RawQuery,
) -> Redirect {
redirect_to_latest_user_logs(path, query, "userid")
app: State<App>,
) -> Result<Redirect> {
redirect_to_latest_user_logs(path, query, true, app).await
}
fn redirect_to_latest_user_logs(
async fn redirect_to_latest_user_logs(
Path(UserLogPathParams {
channel_id_type,
channel,
user,
}): Path<UserLogPathParams>,
RawQuery(query): RawQuery,
user_id_type: &str,
) -> Redirect {
let today = Utc::now();
let year = today.year();
let month = today.month();
user_is_id: bool,
app: State<App>,
) -> Result<Redirect> {
let channel_id = match channel_id_type {
ChannelIdType::Name => app.get_user_id_by_name(&channel).await?,
ChannelIdType::Id => channel.clone(),
};
let user_id = if user_is_id {
user.clone()
} else {
app.get_user_id_by_name(&user).await?
};
let mut new_uri = format!("/{channel_id_type}/{channel}/{user_id_type}/{user}/{year}/{month}");
let available_logs = read_available_user_logs(&app.db, &channel_id, &user_id).await?;
let latest_log = available_logs.first().ok_or(Error::NotFound)?;
let user_id_type = if user_is_id { "userid" } else { "user" };
let mut new_uri = format!("/{channel_id_type}/{channel}/{user_id_type}/{user}/{latest_log}");
if let Some(query) = query {
new_uri.push('?');
new_uri.push_str(&query);
}
Redirect::to(&new_uri)
Ok(Redirect::to(&new_uri))
}
pub async fn random_channel_line(

View File

@ -89,6 +89,8 @@ pub struct LogsParams {
pub reverse: bool,
#[serde(default, deserialize_with = "deserialize_bool_param")]
pub ndjson: bool,
pub limit: Option<u64>,
pub offset: Option<u64>,
}
impl LogsParams {
@ -139,6 +141,18 @@ pub struct AvailableLogDate {
pub day: Option<String>,
}
impl Display for AvailableLogDate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.year, self.month)?;
if let Some(day) = &self.day {
write!(f, "/{day}")?;
}
Ok(())
}
}
#[derive(Deserialize, JsonSchema)]
pub struct AvailableLogsParams {
#[serde(flatten)]