diff --git a/crates/app/src/public/html/chats/channels.lisp b/crates/app/src/public/html/chats/channels.lisp index fb61be0..ced2f93 100644 --- a/crates/app/src/public/html/chats/channels.lisp +++ b/crates/app/src/public/html/chats/channels.lisp @@ -24,32 +24,18 @@ (text "{{ icon \"ellipsis\" }}")) (div ("class" "inner") - (text "{% if user.id == channel.owner -%} {% if selected_community == 0 %}") (button - ("class" "lowered small") + ("onclick" "trigger('atto::copy_text', ['{{ channel.id }}'])") + (icon (text "copy")) + (str (text "general:action.copy_id"))) + (text "{% if user.id == channel.owner or can_manage_channels -%}") + ; owner/manager controls + (button ("onclick" "add_member('{{ channel.id }}')") (text "{{ icon \"user-plus\" }}") (span (text "{{ text \"chats:action.add_someone\" }}"))) - ; mute/unmute (button - ("class" "lowered small {% if channel.id in user.channel_mutes -%} hidden {%- endif %}") - ("ui_ident" "channel.mute:{{ channel.id }}") - ("onclick" "mute_channel('{{ channel.id }}')") - (icon (text "bell-off")) - (span - (str (text "chats:action.mute")))) - (button - ("class" "lowered small {% if not channel.id in user.channel_mutes -%} hidden {%- endif %}") - ("ui_ident" "channel.unmute:{{ channel.id }}") - ("onclick" "mute_channel('{{ channel.id }}', false)") - (icon (text "bell-ring")) - (span - (str (text "chats:action.unmute")))) - ; ... - (text "{%- endif %}") - (button - ("class" "lowered small") ("onclick" "update_channel_title('{{ channel.id }}')") (text "{{ icon \"pencil\" }}") (span @@ -60,14 +46,32 @@ (text "{{ icon \"trash\" }}") (span (text "{{ text \"general:action.delete\" }}"))) - (text "{% elif selected_community == 0 %}") + (text "{%- endif %} {% if selected_community == 0 %}") + ; mute/unmute + (button + ("class" "{% if channel.id in user.channel_mutes -%} hidden {%- endif %}") + ("ui_ident" "channel.mute:{{ channel.id }}") + ("onclick" "mute_channel('{{ channel.id }}')") + (icon (text "bell-off")) + (span + (str (text "chats:action.mute")))) + (button + ("class" "{% if not channel.id in user.channel_mutes -%} hidden {%- endif %}") + ("ui_ident" "channel.unmute:{{ channel.id }}") + ("onclick" "mute_channel('{{ channel.id }}', false)") + (icon (text "bell-ring")) + (span + (str (text "chats:action.unmute")))) + ; ... + (text "{% if user.id != channel.owner -%}") + ; group chat member controls (button ("onclick" "kick_member('{{ channel.id }}', '{{ user.id }}')") ("class" "red") (text "{{ icon \"door-open\" }}") (span (text "{{ text \"chats:action.leave\" }}"))) - (text "{%- endif %}")))) + (text "{%- endif %} {%- endif %}")))) (text "{% endfor %}")) (text "{% if selected_community == 0 and selected_channel -%}") (div diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index c797ea8..6b2832a 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -1210,6 +1210,8 @@ (text "{%- endif %}")) (div ("class" "flex gap_2 hidden") + ("onclick" "window.EMOJI_PICKER_REACTION_MESSAGE_ID = '{{ message.id }}'") + (text "{{ self::emoji_picker(element_id=\"react_emoji_picker_field\", render_dialog=false, render_button=true, small=true) }}") (text "{{ self::message_actions(owner=user, message=message, can_manage_message=can_manage_message) }}"))) (text "{%- endif %}") (div @@ -1232,14 +1234,12 @@ ("ui_ident" "emoji_{{ emoji }}") ("onclick" "trigger('me::message_react', [event.target.parentElement.parentElement, '{{ message.id }}', '{{ emoji }}'])") (span (text "{{ emoji|emojis|safe }} {{ num }}"))) - (text "{%- endfor %}") - - (div - ("class" "hidden") - (text "{{ self::emoji_picker(element_id=\"react_emoji_picker_field\", render_dialog=false, render_button=true, small=true) }}")))) + (text "{%- endfor %}"))) (text "{% if grouped -%}") (div - ("class" "hidden") + ("class" "hidden flex gap_2 items_center") + ("onclick" "window.EMOJI_PICKER_REACTION_MESSAGE_ID = '{{ message.id }}'") + (text "{{ self::emoji_picker(element_id=\"react_emoji_picker_field\", render_dialog=false, render_button=true, small=true) }}") (text "{{ self::message_actions(owner=user, message=message, can_manage_message=can_manage_message) }}")) (text "{%- endif %}")))) @@ -2740,7 +2740,7 @@ (text "{%- endmacro %}") (text "{% macro advertisement(size=\"Leaderboard\") -%}") -(text "{% if not is_supporter and config.enable_user_ads -%}") +(text "{% if user and not is_supporter and config.enable_user_ads -%}") (object ("class" "tetratto_ad") ("data-ad-size" "{{ size }}")) (text "{%- endif %}") (text "{%- endmacro %}") diff --git a/crates/app/src/routes/api/v1/ads.rs b/crates/app/src/routes/api/v1/ads.rs index 15c80bd..505b49e 100644 --- a/crates/app/src/routes/api/v1/ads.rs +++ b/crates/app/src/routes/api/v1/ads.rs @@ -6,8 +6,9 @@ use crate::{ }; use axum::{ extract::Path, - response::{IntoResponse, Redirect}, + response::{Html, IntoResponse}, Extension, Json, + http::StatusCode, }; use tetratto_core::model::{ economy::UserAd, @@ -139,8 +140,56 @@ pub async fn click_request( let data = &(data.read().await).0; let user = get_user_from_token!(jar, data); + let bad_return = ( + StatusCode::FOUND, + [ + ( + "Set-Cookie".to_string(), + format!( + "Atto-Clicked=true; Path=/api/v1/ads/host/{host}/{id}/click; Max-Age=86400" + ), + ), + ("Location".to_string(), data.0.0.host.clone()), + ], + Html(""), + ); + + if jar.get("Atto-Clicked").is_some() { + // we've already clicked this ad, don't charge + match data.get_ad_by_id(id).await { + Ok(x) => { + return ( + StatusCode::FOUND, + [ + ( + "Set-Cookie".to_string(), + format!( + "Atto-Clicked=true; Path=/api/v1/ads/host/{host}/{id}/click; Max-Age=86400" + ), + ), + ("Location".to_string(), x.target), + ], + Html(""), + ); + } + Err(_) => return bad_return, + } + } + match data.ad_click(host, id, user).await { - Ok(t) => Redirect::to(&t), - Err(_) => Redirect::to(&data.0.0.host), + Ok(t) => ( + StatusCode::FOUND, + [ + ( + "Set-Cookie".to_string(), + format!( + "Atto-Clicked=true; Path=/api/v1/ads/host/{host}/{id}/click; Max-Age=86400" + ), + ), + ("Location".to_string(), t), + ], + Html(""), + ), + Err(_) => bad_return, } } diff --git a/crates/app/src/routes/pages/chats.rs b/crates/app/src/routes/pages/chats.rs index 65ff437..4134888 100644 --- a/crates/app/src/routes/pages/chats.rs +++ b/crates/app/src/routes/pages/chats.rs @@ -348,12 +348,25 @@ pub async fn channels_request( None }; + let membership = match data + .0 + .get_membership_by_owner_community(user.id, community) + .await + { + Ok(m) => m, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + }; + + let can_manage_channels = membership.role.check(CommunityPermission::MANAGE_CHANNELS) + | user.permissions.check(FinePermission::MANAGE_CHANNELS); + let lang = get_lang!(jar, data.0); let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; context.insert("channels", &channels); context.insert("page", &props.page); + context.insert("can_manage_channels", &can_manage_channels); context.insert("members", &members); context.insert("channel", &channel); diff --git a/crates/app/src/routes/pages/economy.rs b/crates/app/src/routes/pages/economy.rs index 97fc264..6f0c419 100644 --- a/crates/app/src/routes/pages/economy.rs +++ b/crates/app/src/routes/pages/economy.rs @@ -5,7 +5,7 @@ use axum::{ }; use crate::cookie::CookieJar; use tetratto_core::model::{ - economy::{CoinTransferMethod, UserAd, UserAdSize}, + economy::{CoinTransferMethod, UserAdSize}, Error, }; use crate::{assets::initial_context, get_lang, get_user_from_token, State}; @@ -254,18 +254,17 @@ pub async fn random_ad_request( Query(props): Query, ) -> impl IntoResponse { let data = data.read().await; + let headers = [( + "content-security-policy", + "default-src 'self' *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' *; script-src 'self' 'unsafe-inline' *; worker-src * blob:; object-src 'self' *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: *; frame-ancestors *", + )]; + let ad = match data.0.random_ad_charged(props.size.clone()).await { Ok(x) => x, - Err(_) => UserAd { - id: 0, - created: 0, - upload_id: 0, - owner: data.0.0.0.system_user, - target: data.0.0.0.host.clone(), - last_charge_time: 0, - is_running: true, - size: props.size, - }, + Err(_) => { + // no ad found, show nothing + return (headers, Html(String::new())); + } }; let mut context = tera::Context::new(); @@ -276,10 +275,7 @@ pub async fn random_ad_request( // return ( - [( - "content-security-policy", - "default-src 'self' *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' *; script-src 'self' 'unsafe-inline' *; worker-src * blob:; object-src 'self' *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: *; frame-ancestors *", - )], + headers, Html(data.1.render("economy/ad.html", &context).unwrap()), ) } @@ -291,19 +287,17 @@ pub async fn known_ad_request( Path(id): Path, ) -> impl IntoResponse { let data = data.read().await; + let headers = [ + ( + "content-security-policy", + "default-src 'self' *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' *; script-src 'self' 'unsafe-inline' *; worker-src * blob:; object-src 'self' *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: *; frame-ancestors *", + ), + ("Cache-Control", "no-cache"), + ]; + let ad = match data.0.get_ad_by_id(id).await { Ok(x) => x, - Err(_) => UserAd { - // polyfill ad - id: 0, - created: 0, - upload_id: 0, - owner: data.0.0.0.system_user, - target: data.0.0.0.host.clone(), - last_charge_time: 0, - is_running: true, - size: props.size, - }, + Err(_) => return (headers, Html(String::new())), }; let mut context = tera::Context::new(); @@ -314,13 +308,7 @@ pub async fn known_ad_request( // return ( - [ - ( - "content-security-policy", - "default-src 'self' *.spotify.com musicbrainz.org; img-src * data:; media-src *; font-src *; style-src 'unsafe-inline' 'self' *; script-src 'self' 'unsafe-inline' *; worker-src * blob:; object-src 'self' *; upgrade-insecure-requests; connect-src * localhost; frame-src 'self' blob: *; frame-ancestors *", - ), - ("Cache-Control", "no-cache"), - ], + headers, Html(data.1.render("economy/ad.html", &context).unwrap()), ) }