Compare commits
8 Commits
7f74e8e1a7
...
bb27092e3b
Author | SHA1 | Date | |
---|---|---|---|
bb27092e3b | |||
2b05053b47 | |||
ebc66e3222 | |||
9e9026b87e | |||
0fa2fea361 | |||
61c2245054 | |||
3b949e70bd | |||
be890bbba0 |
@@ -6,13 +6,13 @@ edition = "2024"
|
||||
[dependencies]
|
||||
axum = "0.8"
|
||||
axum-extra = { version = "0.10", features = ["cookie"] }
|
||||
axum-messages = "0.8.0"
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
axum-messages = "0.8"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
maud = { version = "0.27", features = ["axum"] }
|
||||
rust-i18n = "3.1.5"
|
||||
rust-i18n = "3.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite", "macros", "chrono"] }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
tower-http = { version = "0.6", features = ["fs"] }
|
||||
tower-sessions = "0.14.0"
|
||||
tower-sessions = "0.14"
|
||||
uuid = { version = "1.17", features = ["v4", "serde"] }
|
||||
|
130
bad/create.sh
Executable file
130
bad/create.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fetch and merge text and JSON files from URLs
|
||||
# Usage: ./create.sh [output_file]
|
||||
|
||||
OUTPUT_FILE="${1:-merged_output.txt}"
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
FINAL_TEMP="$TEMP_DIR/combined.txt"
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURE YOUR URLS AND COMMENTS HERE
|
||||
# =============================================================================
|
||||
|
||||
URLS_AND_COMMENTS=(
|
||||
"https://raw.githubusercontent.com/dsojevic/profanity-list/refs/heads/main/en.txt"
|
||||
"https://raw.githubusercontent.com/dsojevic/profanity-list/refs/heads/main/emoji.txt"
|
||||
"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/refs/heads/master/de"
|
||||
"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/refs/heads/master/en"
|
||||
"https://raw.githubusercontent.com/zacanger/profane-words/refs/heads/master/words.json"
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
|
||||
# Check if jq is available for JSON processing
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo -e "${YELLOW}Warning: jq not found. JSON files will be skipped.${NC}"
|
||||
echo -e "${YELLOW}Install jq with: apt-get install jq (Ubuntu/Debian) or brew install jq (macOS)${NC}"
|
||||
HAS_JQ=false
|
||||
else
|
||||
HAS_JQ=true
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Text/JSON File Merger${NC}"
|
||||
echo "Processing $(echo "${URLS_AND_COMMENTS[@]}" | grep -c 'https://' || true) URLs..."
|
||||
|
||||
# Process URLs and comments
|
||||
for line in "${URLS_AND_COMMENTS[@]}"; do
|
||||
# Handle comments
|
||||
if [[ "$line" =~ ^[[:space:]]*# ]]; then
|
||||
echo "$line" >> "$FINAL_TEMP"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip lines that don't look like URLs
|
||||
if [[ ! "$line" =~ ^https?:// ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Fetching: $line${NC}"
|
||||
|
||||
# Create temporary file for this URL
|
||||
URL_TEMP="$TEMP_DIR/url_content.tmp"
|
||||
|
||||
# Fetch the URL
|
||||
if curl -s -f "$line" -o "$URL_TEMP" 2>/dev/null; then
|
||||
echo -e "${GREEN}✓ Successfully fetched${NC}"
|
||||
|
||||
# Check if the content is JSON
|
||||
if [ "$HAS_JQ" = true ] && jq empty "$URL_TEMP" 2>/dev/null; then
|
||||
echo -e "${BLUE} → Detected JSON format, extracting strings...${NC}"
|
||||
|
||||
# Try to extract strings from JSON array or object
|
||||
# Handle different JSON structures
|
||||
if jq -e 'type == "array"' "$URL_TEMP" >/dev/null 2>&1; then
|
||||
# JSON array - extract all string values
|
||||
jq -r '.[] | select(type == "string")' "$URL_TEMP" >> "$FINAL_TEMP" 2>/dev/null || {
|
||||
echo -e "${RED} ✗ Failed to parse JSON array${NC}"
|
||||
echo "# ERROR: Could not parse JSON from $line" >> "$FINAL_TEMP"
|
||||
}
|
||||
elif jq -e 'type == "object"' "$URL_TEMP" >/dev/null 2>&1; then
|
||||
# JSON object - extract all string values
|
||||
jq -r 'recurse | select(type == "string")' "$URL_TEMP" >> "$FINAL_TEMP" 2>/dev/null || {
|
||||
echo -e "${RED} ✗ Failed to parse JSON object${NC}"
|
||||
echo "# ERROR: Could not parse JSON from $line" >> "$FINAL_TEMP"
|
||||
}
|
||||
else
|
||||
echo -e "${RED} ✗ Unsupported JSON structure${NC}"
|
||||
echo "# ERROR: Unsupported JSON structure from $line" >> "$FINAL_TEMP"
|
||||
fi
|
||||
else
|
||||
# Regular text file - append as-is
|
||||
echo -e "${BLUE} → Processing as text file...${NC}"
|
||||
cat "$URL_TEMP" >> "$FINAL_TEMP"
|
||||
fi
|
||||
|
||||
# Clean up URL temp file
|
||||
rm -f "$URL_TEMP"
|
||||
else
|
||||
echo -e "${RED}✗ Failed to fetch: $line${NC}"
|
||||
echo "# ERROR: Could not fetch $line" >> "$FINAL_TEMP"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if we have any content
|
||||
if [ ! -s "$FINAL_TEMP" ]; then
|
||||
echo -e "${RED}No content to process${NC}"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Processing content...${NC}"
|
||||
|
||||
# Remove duplicates, sort alphabetically, and save to output file
|
||||
# Keep comments at the top, sort the rest
|
||||
{
|
||||
grep '^#' "$FINAL_TEMP" 2>/dev/null || true
|
||||
grep -v '^#' "$FINAL_TEMP" 2>/dev/null | grep -v '^[[:space:]]*$' | sort -u
|
||||
} > "$OUTPUT_FILE"
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# Show results
|
||||
line_count=$(wc -l < "$OUTPUT_FILE")
|
||||
echo -e "${GREEN}✓ Complete! Merged content saved to: $OUTPUT_FILE${NC}"
|
||||
echo -e "${GREEN}Total lines: $line_count${NC}"
|
||||
|
||||
# Show a preview of the content
|
||||
if [ -s "$OUTPUT_FILE" ]; then
|
||||
echo -e "${YELLOW}Preview (first 10 non-comment lines):${NC}"
|
||||
grep -v '^#' "$OUTPUT_FILE" | head -10
|
||||
fi
|
||||
|
3223
bad/merged_output.txt
Normal file
3223
bad/merged_output.txt
Normal file
File diff suppressed because it is too large
Load Diff
14
deploy.sh
Executable file
14
deploy.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cargo t
|
||||
|
||||
cargo b -r --target x86_64-unknown-linux-musl
|
||||
strip target/x86_64-unknown-linux-musl/release/website
|
||||
|
||||
ssh root@aef25.digidow.eu "/usr/bin/systemctl stop aef"
|
||||
scp target/x86_64-unknown-linux-musl/release/website root@aef25.digidow.eu:/srv/aef/aef
|
||||
scp -r static/ root@aef25.digidow.eu:/srv/aef/
|
||||
ssh root@aef25.digidow.eu "systemctl start aef"
|
||||
|
@@ -3,5 +3,52 @@ digital_shadows: "Digitaler Schatten"
|
||||
icon_camera: "📸"
|
||||
found_camera_title: "Kamera '%{name}' gefunden"
|
||||
found_camera_body: "✨ Du bist #%{amount} der/die diese Kamera gefunden hat ✨"
|
||||
ask_to_change_name: "%{name}, do you want to be named something different? No worries, change here 👇"
|
||||
funny_name_change_placeholder: "✨ Your new name starts here ✨"
|
||||
ask_to_change_name: "%{name}, möchtest du anders genannt werden? Kein Problem, hier kannst du deinen Namen ändern 👇"
|
||||
funny_name_change_placeholder: "✨ Dein neuer Name beginnt hier ✨"
|
||||
|
||||
# Index page
|
||||
who_owns_data: "Wem gehören deine <mark>Daten</mark>?"
|
||||
digital_shadow_description: "Was passiert, wenn dein digitaler Schatten Gestalt annimmt: greifbar, fragend, beobachtend?"
|
||||
artists_list: "Künstler*innen: René Mayrhofer (AT), Philipp Hofer (AT), Laura Poulbot (FR), Airan Berg (AT), Andrea Hummer (AT), Ilona Roth (DE/AT), Linda Huber (AT), Gisela Klammsteiner (AT), Sara Koniarek (AT), Simon Sharkey (GB), Valerio Iurato (IT), Doris Roth (DE), Alina Lugovskaya (UA/RU), Selina Nowak (AT), JeanClaude Grieco (AR/AT), Florian Böttcher (AT), Ethem Saygieder-Fischer (AT)"
|
||||
artists_easter_egg: ", Marie Birner (Couch)"
|
||||
project_quote: "Digital Shadows konfrontiert Besucher*innen mit ihrem digitalen Selbst – kopiert, vermessen, analysiert. Ein Experiment über Datenmacht, Sichtbarkeit und Kontrolle im digitalen Zeitalter."
|
||||
project_quote_attribution: "— Digital Shadows Team"
|
||||
project_description: "Digital Shadows lädt die Teilnehmer*innen ein, Fragen digitaler und physischer Identität, Datensicherheit und Kontrolle zu erleben. In immersiven Zonen, die mit choreografischen Elementen verwoben sind, begegnen Besucher*innen sich selbst gespiegelt, kopiert, vermessen und verlieren sich gleichzeitig in einem System, das mehr über sie weiß, als sie preisgeben wollen. Zwischen Spiel und Analyse, Verbergen und Transparenz entsteht eine Reflexion über Identität im Zeitalter von Gesichtserkennung, Deepfakes und algorithmischer Profilierung. Wie täuscht man eine Kamera? Wie sichtbar möchte ich sein? Wem gehört, was ich hinterlasse, und wer profitiert davon? Dieses Experiment ist ein kollaboratives Unterfangen zwischen Wissenschaft und Kunst, das Macht, Sichtbarkeit und Selbstbestimmung im digitalen Raum greifbar macht. Durch eine Erkundung digitaler Materialität und algorithmischer Intelligenz entfaltet sich eine manchmal absurde, immer unmittelbare Reflexion über unsere Rolle in datengetriebenen Welten, bis wir unserem digitalen Dilemma gegenüberstehen und die Wahl noch immer bei uns liegt."
|
||||
what_to_do_title: "Was tun mit diesen Informationen?"
|
||||
visit_booth_title: "Besuche unseren Stand"
|
||||
visit_booth_description: "Wir freuen uns darauf, dich an unserem Stand in der Post City Linz zu sehen, wo unser Team dir zeigt, was passiert, wenn dein digitaler Schatten allmächtig wird. "
|
||||
jku_link_title: "Zur JKU Informationsseite"
|
||||
find_out_more: "Erfahre mehr"
|
||||
location_postcity: "Wo: Postcity Linz"
|
||||
play_game_title: "Spiele unser Spiel"
|
||||
play_game_description: "Schon mal durch Linz gewandert mit dem Ziel, (versteckte) Kameras zu finden? Nun, wenn du so jemand bist, dann ist unser 'Kameras entdecken'-Spiel ideal für dich! "
|
||||
game_link_title: "Zur Spiel-Seite"
|
||||
location_linz: "Wo: überall in Linz"
|
||||
|
||||
# Game page
|
||||
game_title: "Wer findet die meisten Kameras?"
|
||||
game_explanation_todo: "Willkommen zu unserem Überwachungsbewusstseinsspiel! Als Teil unserer Digital Shadows Ausstellung beim Ars Electronica Festival haben wir QR-Codes bei Überwachungskameras in ganz Linz platziert. Deine Mission: Entdecke die Kameras, scanne unsere Codes und finde heraus, wie allgegenwärtig öffentliche Überwachung wirklich ist. Wir sind aber nur Menschen – wir haben nur einen kleinen Teil aller Kameras erfasst, die unsere Stadt beobachten. Wer beobachtet wen in unseren öffentlichen Räumen? Die Jagd beginnt jetzt! 🕵️"
|
||||
save_button: "Speichern"
|
||||
cameras_found: "Du hast %{found}/%{total} Kameras gefunden:"
|
||||
highscore_title: "Bestenliste"
|
||||
not_found_title: "ups"
|
||||
error_already_found_title: "Versuche eine neue Kamera zu finden!"
|
||||
error_already_found_body: "Du hast diese Kamera bereits gesammelt."
|
||||
error_already_found_footer: ""
|
||||
error_name_too_short_title: "Das ist zu wenig!"
|
||||
error_name_too_short_body: "Wir brauchen mehr Informationen über dich. Gib uns mindestens %{expected} Zeichen für deinen neuen Namen!"
|
||||
error_name_too_long_title: "Das ist zu viel!"
|
||||
error_name_too_long_body: "Wir leben nur im Jahr (20)25, also verwende bitte weniger als %{expected} Zeichen für deinen neuen Namen."
|
||||
error_bad_word_title: "Hmmm. Dieser Name gefällt mir nicht."
|
||||
error_bad_word_body: "Bitte wähle einen anderen!"
|
||||
error_bad_word_footer: ""
|
||||
received_characters: "Erhaltene Zeichen"
|
||||
|
||||
# Page layout
|
||||
see_ranking: "Sieh deine Platzierung"
|
||||
new_name_title: "Neuer Name!"
|
||||
new_name_message: "Fühlt es sich viel anders an?"
|
||||
footer_text: "Footer "
|
||||
footer_todo: "noch zu vervollständigen"
|
||||
footer_links: "mit Links"
|
||||
impressum: "Impressum"
|
||||
|
@@ -5,3 +5,50 @@ found_camera_title: "Camera '%{name}' found"
|
||||
found_camera_body: "✨ You are #%{amount} who has found this camera ✨"
|
||||
ask_to_change_name: "%{name}, do you want to be named something different? No worries, change here 👇"
|
||||
funny_name_change_placeholder: "✨ Your new name starts here ✨"
|
||||
|
||||
# Index page
|
||||
who_owns_data: "Who owns your <mark>data</mark>?"
|
||||
digital_shadow_description: "What happens when your digital shadow takes shape: tangible, interrogative, observant?"
|
||||
artists_list: "Artists: René Mayrhofer (AT), Philipp Hofer (AT), Laura Poulbot (FR), Airan Berg (AT), Andrea Hummer (AT), Ilona Roth (DE/AT), Linda Huber (AT), Gisela Klammsteiner (AT), Sara Koniarek (AT), Simon Sharkey (GB), Valerio Iurato (IT), Doris Roth (DE), Alina Lugovskaya (UA/RU), Selina Nowak (AT), JeanClaude Grieco (AR/AT), Florian Böttcher (AT), Ethem Saygieder-Fischer (AT)"
|
||||
artists_easter_egg: ", Marie Birner (Couch)"
|
||||
project_quote: "Digital Shadows confronts visitors with their digital self – copied, measured, analyzed. An experiment on data power, visibility, and control in the digital age."
|
||||
project_quote_attribution: "— Digital Shadows Team"
|
||||
project_description: "Digital Shadows invites the participants to experience questions of digital and physical identity, data security, and control. In immersive zones woven with choreographic elements, visitors encounter themselves mirrored, copied, measured and simultaneously lose themselves in a system that knows more about them than they intend to reveal. Between play and analysis, concealment and transparency, a reflection emerges on identity in the age of facial recognition, deepfakes, and algorithmic profiling. How does one fool a camera? How visible do I want to be? Who owns what I leave behind, and who profits from it? This experiment is a collaborative endeavor between science and art, making power, visibility, and self-determination in digital space tangible. Through an exploration of digital materiality and algorithmic intelligence, a sometimes absurd, always immediate reflection unfolds on our role in data-driven worlds until we face our digital dilemma, and the choice is still ours to make."
|
||||
what_to_do_title: "What to do with this information?"
|
||||
visit_booth_title: "Visit our booth"
|
||||
visit_booth_description: "We will be delighted to see you at our booth in the Post City Linz, where our team will show you, what happens when your Digital Shadow becomes allmighty. "
|
||||
jku_link_title: "Go to JKU Information Page"
|
||||
find_out_more: "Find out more"
|
||||
location_postcity: "Where: Postcity Linz"
|
||||
play_game_title: "Play our game"
|
||||
play_game_description: "Ever wandered through Linz with the aim to find (hidden) cameras? Well, if you are that kind of person than our 'Discover cameras' game will be ideal for you! "
|
||||
game_link_title: "Go to Game Page"
|
||||
location_linz: "Where: all over Linz"
|
||||
|
||||
# Game page
|
||||
game_title: "Who finds the most cameras?"
|
||||
game_explanation_todo: "Welcome to our public surveillance awareness game! As part of our Digital Shadows exhibition at Ars Electronica Festival, we've placed QR codes near surveillance cameras throughout Linz. Your mission: spot the cameras, scan our codes, and discover how pervasive public monitoring really is. We're only human though – we've mapped just a small subset of all the cameras watching our city. Who's watching whom in our public spaces? The hunt begins now! 🕵️"
|
||||
save_button: "Save"
|
||||
cameras_found: "You have found %{found}/%{total} cameras:"
|
||||
highscore_title: "Highscore"
|
||||
not_found_title: "uups"
|
||||
error_already_found_title: "Try to find a new camera!"
|
||||
error_already_found_body: "You have already collected this camera."
|
||||
error_already_found_footer: ""
|
||||
error_name_too_short_title: "That's too little!"
|
||||
error_name_too_short_body: "We need more information about you. Give us at least %{expected} characters for you new name!"
|
||||
error_name_too_long_title: "That's too much!"
|
||||
error_name_too_long_body: "We only live in (20)25, so please use less than %{expected} characters for your new name."
|
||||
error_bad_word_title: "Hmmm. I don't like this name."
|
||||
error_bad_word_body: "Please choose a different one!"
|
||||
error_bad_word_footer: ""
|
||||
received_characters: "Received characters"
|
||||
|
||||
# Page layout
|
||||
see_ranking: "See your ranking"
|
||||
new_name_title: "New name!"
|
||||
new_name_message: "Does it feel much different?"
|
||||
footer_text: "Footer "
|
||||
footer_todo: "to be completed"
|
||||
footer_links: "with links"
|
||||
impressum: "Impressum"
|
||||
|
62
src/game.rs
62
src/game.rs
@@ -1,14 +1,14 @@
|
||||
use crate::{language::language, page::Page, Backend, NameUpdateError};
|
||||
use crate::{Backend, NameUpdateError, language::language, page::Page};
|
||||
use axum::{
|
||||
Form, Router,
|
||||
extract::{Path, State},
|
||||
http::HeaderMap,
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::{get, post},
|
||||
Form, Router,
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use axum_messages::Messages;
|
||||
use maud::{html, Markup, PreEscaped};
|
||||
use maud::{Markup, PreEscaped, html};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
@@ -30,27 +30,32 @@ async fn index(
|
||||
page.messages(messages);
|
||||
let markup = page.content(html! {
|
||||
hgroup {
|
||||
h1 { "Who finds the most cameras?" }
|
||||
h1 { (t!("game_title")) }
|
||||
}
|
||||
p {
|
||||
mark { "TODO: Explanation of AEF / digital shadows / search game" }
|
||||
mark { (t!("game_explanation_todo")) }
|
||||
}
|
||||
|
||||
div.mb-sm { (t!("ask_to_change_name", name = client.get_display_name())) }
|
||||
|
||||
form action="/name" method="post" {
|
||||
fieldset role="group" {
|
||||
input name="name" placeholder=(t!("funny_name_change_placeholder")) aria-label="Name" required;
|
||||
input type="submit" value="Save";
|
||||
input
|
||||
name="name"
|
||||
placeholder=(t!("funny_name_change_placeholder"))
|
||||
aria-label="Name"
|
||||
required;
|
||||
input type="submit" value=(t!("save_button"));
|
||||
}
|
||||
}
|
||||
|
||||
p.mb-0 {
|
||||
"You have found "
|
||||
(sightings.len())
|
||||
"/"
|
||||
(amount_total_cameras)
|
||||
" cameras:"
|
||||
({
|
||||
t!(
|
||||
"cameras_found", found = sightings.len(), total =
|
||||
amount_total_cameras
|
||||
)
|
||||
})
|
||||
progress value=(sightings.len()) max=(amount_total_cameras);
|
||||
}
|
||||
|
||||
@@ -61,7 +66,7 @@ async fn index(
|
||||
}
|
||||
|
||||
p {
|
||||
h2 { "Highscore" }
|
||||
h2 { (t!("highscore_title")) }
|
||||
ul.iterated {
|
||||
@for rank in highscore {
|
||||
li.card {
|
||||
@@ -105,7 +110,12 @@ async fn game(
|
||||
if let Ok(number) = backend.client_found_camera(&client, &camera).await {
|
||||
messages.info(format!("found-cam|{}|{number}", camera.name));
|
||||
} else {
|
||||
messages.info("err|Try to find a new camera!|You have already collected this camera.|");
|
||||
messages.info(format!(
|
||||
"err|{}|{}|{}",
|
||||
t!("error_already_found_title"),
|
||||
t!("error_already_found_body"),
|
||||
t!("error_already_found_footer")
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Redirect::to("/game"))
|
||||
@@ -114,7 +124,7 @@ async fn game(
|
||||
async fn not_found(cookies: CookieJar, headers: HeaderMap) -> Markup {
|
||||
let lang = language(&cookies, &headers);
|
||||
Page::new(lang).content(html! {
|
||||
h1 { "uups" }
|
||||
h1 { (t!("not_found_title")) }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -133,8 +143,26 @@ async fn set_name(
|
||||
|
||||
match backend.set_client_name(&client, &form.name).await {
|
||||
Ok(()) => messages.info("set-name-succ"),
|
||||
Err(NameUpdateError::TooShort(expected, actual)) => messages.info(format!("err|That's too little!|We need more information about you. Give us at least {expected} characters for you new name!|Received characters: {actual}")),
|
||||
Err(NameUpdateError::TooLong(expected, actual)) => messages.info(format!("err|That's too much!|We only live in (20)25, so please use less than {expected} characters for your new name.|Received characters: {actual}")),
|
||||
Err(NameUpdateError::TooShort(expected, actual)) => messages.info(format!(
|
||||
"err|{}|{}|{}: {}",
|
||||
t!("error_name_too_short_title"),
|
||||
t!("error_name_too_short_body", expected = expected),
|
||||
t!("received_characters"),
|
||||
actual
|
||||
)),
|
||||
Err(NameUpdateError::TooLong(expected, actual)) => messages.info(format!(
|
||||
"err|{}|{}|{}: {}",
|
||||
t!("error_name_too_long_title"),
|
||||
t!("error_name_too_long_body", expected = expected),
|
||||
t!("received_characters"),
|
||||
actual
|
||||
)),
|
||||
Err(NameUpdateError::ContainsBadWord) => messages.info(format!(
|
||||
"err|{}|{}|{}",
|
||||
t!("error_bad_word_title"),
|
||||
t!("error_bad_word_body"),
|
||||
t!("error_bad_word_footer")
|
||||
)),
|
||||
};
|
||||
|
||||
// Redirect back to the game page
|
||||
|
98
src/index.rs
98
src/index.rs
@@ -1,7 +1,7 @@
|
||||
use crate::{language::language, page::Page};
|
||||
use axum::http::HeaderMap;
|
||||
use axum_extra::extract::CookieJar;
|
||||
use maud::{html, Markup};
|
||||
use maud::{Markup, PreEscaped, html};
|
||||
|
||||
pub(super) async fn index(cookies: CookieJar, headers: HeaderMap) -> Markup {
|
||||
let lang = language(&cookies, &headers);
|
||||
@@ -9,60 +9,48 @@ pub(super) async fn index(cookies: CookieJar, headers: HeaderMap) -> Markup {
|
||||
rust_i18n::set_locale(lang.to_locale());
|
||||
|
||||
let page = Page::new(lang);
|
||||
page.content(
|
||||
html! {
|
||||
h1 { (t!("digital_shadows")) }
|
||||
hgroup {
|
||||
h2 {
|
||||
"Who owns your "
|
||||
mark { "data" }
|
||||
"?"
|
||||
}
|
||||
p {
|
||||
"What happens when your digital shadow takes shape: tangible, interrogative, observant?"
|
||||
}
|
||||
}
|
||||
p {
|
||||
"Artists: René Mayrhofer (AT), Philipp Hofer (AT), Laura Poulbot (FR), Airan Berg (AT), Andrea Hummer (AT), Ilona Roth (DE/AT), Linda Huber (AT), Gisela Klammsteiner (AT), Sara Koniarek (AT), Simon Sharkey (GB), Valerio Iurato (IT), Doris Roth (DE), Alina Lugovskaya (UA/RU), Selina Nowak (AT), JeanClaude Grieco (AR/AT), Florian Böttcher (AT), Ethem Saygieder-Fischer (AT)"
|
||||
span.easteregg { ", Marie Birner (Couch)" }
|
||||
}
|
||||
blockquote {
|
||||
"Digital Shadows confronts visitors with their digital self – copied, measured, analyzed. An experiment on data power, visibility, and control in the digital age."
|
||||
footer {
|
||||
cite { "— Digital Shadows Team" }
|
||||
}
|
||||
}
|
||||
p {
|
||||
"Digital Shadows invites the participants to experience questions of digital and physical identity, data security, and control. In immersive zones woven with choreographic elements, visitors encounter themselves mirrored, copied, measured and simultaneously lose themselves in a system that knows more about them than they intend to reveal. Between play and analysis, concealment and transparency, a reflection emerges on identity in the age of facial recognition, deepfakes, and algorithmic profiling. How does one fool a camera? How visible do I want to be? Who owns what I leave behind, and who profits from it? This experiment is a collaborative endeavor between science and art, making power, visibility, and self-determination in digital space tangible. Through an exploration of digital materiality and algorithmic intelligence, a sometimes absurd, always immediate reflection unfolds on our role in data-driven worlds until we face our digital dilemma, and the choice is still ours to make."
|
||||
}
|
||||
|
||||
h2 { "What to do with this information?" }
|
||||
|
||||
div.grid.gap-lg {
|
||||
article {
|
||||
header { "Visit our booth" }
|
||||
|
||||
"We will be delighted to see you at our booth in the Post City Linz, where our team will show you, what happens when your Digital Shadow becomes allmighty. "
|
||||
|
||||
a
|
||||
href="https://www.jku.at/ars-electronica-2025-panic-yes-no/digital-shadows/"
|
||||
target="_blank"
|
||||
title="Go to JKU Information Page" {
|
||||
"Find out more"
|
||||
}
|
||||
|
||||
footer { "Where: Postcity Linz" }
|
||||
}
|
||||
article {
|
||||
header { "Play our game" }
|
||||
|
||||
"Ever wandered through Linz with the aim to find (hidden) cameras? Well, if you are that kind of person than our 'Discover cameras' game will be ideal for you! "
|
||||
|
||||
a href="/game" title="Go to Game Page" { "Find out more" }
|
||||
|
||||
footer { "Where: all over Linz" }
|
||||
}
|
||||
page.content(html! {
|
||||
h1 { (t!("digital_shadows")) }
|
||||
hgroup {
|
||||
h2 { (PreEscaped(t!("who_owns_data"))) }
|
||||
p { (t!("digital_shadow_description")) }
|
||||
}
|
||||
p {
|
||||
(t!("artists_list"))
|
||||
span.easteregg { (t!("artists_easter_egg")) }
|
||||
}
|
||||
blockquote {
|
||||
(t!("project_quote"))
|
||||
footer {
|
||||
cite { (t!("project_quote_attribution")) }
|
||||
}
|
||||
}
|
||||
)
|
||||
p { (t!("project_description")) }
|
||||
|
||||
h2 { (t!("what_to_do_title")) }
|
||||
|
||||
div.grid.gap-lg {
|
||||
article {
|
||||
header { (t!("visit_booth_title")) }
|
||||
|
||||
(t!("visit_booth_description"))
|
||||
|
||||
a
|
||||
href="https://www.jku.at/ars-electronica-2025-panic-yes-no/digital-shadows/"
|
||||
target="_blank"
|
||||
title=(t!("jku_link_title")) { (t!("find_out_more")) }
|
||||
|
||||
footer { (t!("location_postcity")) }
|
||||
}
|
||||
article {
|
||||
header { (t!("play_game_title")) }
|
||||
|
||||
(t!("play_game_description"))
|
||||
|
||||
a href="/game" title=(t!("game_link_title")) { (t!("find_out_more")) }
|
||||
|
||||
footer { (t!("location_linz")) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
46
src/main.rs
46
src/main.rs
@@ -1,9 +1,14 @@
|
||||
use crate::model::client::Client;
|
||||
use axum::{http::HeaderMap, routing::get, Router};
|
||||
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
||||
use axum::{Router, http::HeaderMap, routing::get};
|
||||
use axum_extra::extract::{CookieJar, cookie::Cookie};
|
||||
use axum_messages::MessagesManagerLayer;
|
||||
use sqlx::{pool::PoolOptions, sqlite::SqliteConnectOptions, SqlitePool};
|
||||
use std::{fmt::Display, str::FromStr, sync::Arc};
|
||||
use sqlx::{SqlitePool, pool::PoolOptions, sqlite::SqliteConnectOptions};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::Display,
|
||||
str::FromStr,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_sessions::{MemoryStore, SessionManagerLayer};
|
||||
use uuid::Uuid;
|
||||
@@ -73,6 +78,36 @@ struct Req {
|
||||
pub(crate) enum NameUpdateError {
|
||||
TooLong(usize, usize),
|
||||
TooShort(usize, usize),
|
||||
ContainsBadWord,
|
||||
}
|
||||
|
||||
static BAD_WORDS: LazyLock<HashSet<String>> = LazyLock::new(|| {
|
||||
const BAD_WORDS_FILE: &str = include_str!("../bad/merged_output.txt");
|
||||
|
||||
BAD_WORDS_FILE
|
||||
.lines()
|
||||
.map(|line| line.trim())
|
||||
.filter(|line| !line.is_empty() && !line.starts_with('#')) // Skip empty lines and comments
|
||||
.map(|word| {
|
||||
word.to_lowercase()
|
||||
.chars()
|
||||
.filter(|c| c.is_alphabetic())
|
||||
.collect()
|
||||
})
|
||||
.filter(|word: &String| !word.is_empty())
|
||||
.collect()
|
||||
});
|
||||
|
||||
fn contains_bad_word(text: &str) -> bool {
|
||||
let cleaned_text: String = text
|
||||
.to_lowercase()
|
||||
.chars()
|
||||
.filter(|c| c.is_alphabetic())
|
||||
.collect();
|
||||
|
||||
BAD_WORDS
|
||||
.iter()
|
||||
.any(|bad_word| cleaned_text.contains(bad_word))
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
@@ -105,6 +140,9 @@ impl Backend {
|
||||
if name.len() < 3 {
|
||||
return Err(NameUpdateError::TooShort(3, name.len()));
|
||||
}
|
||||
if contains_bad_word(name) {
|
||||
return Err(NameUpdateError::ContainsBadWord);
|
||||
}
|
||||
|
||||
match self {
|
||||
Backend::Sqlite(db) => {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use crate::{random_names::get_name_by_uuid, Backend};
|
||||
use crate::{Backend, random_names::get_name_by_uuid};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use crate::{model::client::Client, Backend};
|
||||
use crate::{Backend, model::client::Client};
|
||||
|
||||
pub(crate) struct Rank {
|
||||
pub(crate) rank: i64,
|
||||
|
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
model::{camera::Camera, client::Client},
|
||||
Backend,
|
||||
model::{camera::Camera, client::Client},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{types::chrono::NaiveDateTime, FromRow};
|
||||
use sqlx::{FromRow, types::chrono::NaiveDateTime};
|
||||
|
||||
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||
pub struct SightingDb {
|
||||
|
42
src/page.rs
42
src/page.rs
@@ -1,8 +1,6 @@
|
||||
use crate::Language;
|
||||
use axum_messages::Messages;
|
||||
use maud::{html, Markup, DOCTYPE};
|
||||
|
||||
// TODO: set dynamic meta lang attribute
|
||||
use maud::{DOCTYPE, Markup, html};
|
||||
|
||||
pub(crate) struct Page {
|
||||
lang: Language,
|
||||
@@ -62,7 +60,7 @@ impl Page {
|
||||
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
||||
link rel="stylesheet" href="/static/pico.min.css";
|
||||
link rel="stylesheet" href="/static/style.css";
|
||||
title { "Digital Shadows" }
|
||||
title { (t!("digital_shadows")) }
|
||||
}
|
||||
body {
|
||||
header.container {
|
||||
@@ -70,7 +68,7 @@ impl Page {
|
||||
ul {
|
||||
li {
|
||||
a href="/" {
|
||||
strong { "Digital Shadows" }
|
||||
strong { (t!("digital_shadows")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,26 +97,28 @@ impl Page {
|
||||
article class="succ msg" {
|
||||
header { (t!("found_camera_title", name = found_camera.0)) }
|
||||
(t!("found_camera_body", amount = found_camera.1))
|
||||
footer { a href="#ranking" { "See your ranking" } }
|
||||
footer {
|
||||
a href="#ranking" { (t!("see_ranking")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@if self.new_name {
|
||||
div.flex {
|
||||
article class="name msg" {
|
||||
header { "New name!" }
|
||||
"Does it feel much different?"
|
||||
div.flex {
|
||||
article class="name msg" {
|
||||
header { (t!("new_name_title")) }
|
||||
(t!("new_name_message"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@if let Some(err) = &self.err {
|
||||
div.flex {
|
||||
article class="error msg" {
|
||||
header { (err.0) }
|
||||
(err.1)
|
||||
footer { (err.2) }
|
||||
div.flex {
|
||||
article class="error msg" {
|
||||
header { (err.0) }
|
||||
(err.1)
|
||||
footer { (err.2) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section { (content) }
|
||||
@@ -126,12 +126,12 @@ impl Page {
|
||||
|
||||
footer.container {
|
||||
small {
|
||||
"Footer "
|
||||
mark { "to be completed" }
|
||||
a href="#" { "with links" }
|
||||
(t!("footer_text"))
|
||||
mark { (t!("footer_todo")) }
|
||||
a href="#" { (t!("footer_links")) }
|
||||
" • "
|
||||
a target="_blank" href="https://www.digidow.eu/impressum/" {
|
||||
"Impressum"
|
||||
(t!("impressum"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user