add: store ad clicked state in cookies

This commit is contained in:
trisua 2025-08-12 15:09:08 -04:00
parent 83971b3d20
commit befd9096b1
5 changed files with 119 additions and 65 deletions

View file

@ -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

View file

@ -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 %}")

View file

@ -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,
}
}

View file

@ -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);

View file

@ -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<RandomAdQuery>,
) -> 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<usize>,
) -> 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()),
)
}