2 Commits
Author SHA1 Message Date
Elias Wendland b5bca1c59a Release 0.2.1
CI and release / Detect release commit (push) Successful in 29s
CI and release / Run tests (push) Successful in 4m27s
CI and release / Build and publish container (push) Successful in 10m33s
CI and release / Create release (push) Successful in 28s
2026-08-30 16:55:00 +02:00
Elias Wendland 48408cba61 Release 0.2.0
CI and release / Run tests (push) Failing after 33s
CI and release / Detect release commit (push) Successful in 36s
CI and release / Build and publish container (push) Successful in 6m30s
CI and release / Create release (push) Successful in 15s
2026-08-25 21:58:43 +02:00
44 changed files with 1875 additions and 515 deletions

No files matched your search

+7 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tg-archive-bot"
version = "0.1.0"
version = "0.2.1"
edition = "2024"
license = "GPL-3.0-or-later"
description = "Bot for archiving messages in TeenGovernment and related servers"
@@ -10,11 +10,17 @@ repository = "https://git.ewenlau.net/ewenlau/tg-archive-bot"
homepage = "https://git.ewenlau.net/ewenlau/tg-archive-bot"
[dependencies]
askama = "0.14"
axum = { version = "0.8.4", features = ["form"] }
axum-extra = { version = "0.12.6", features = ["cookie"] }
dotenv = "0.15.0"
poise = "0.6.2"
rand = "0.9"
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
sqlx = { version = "0.9.0", features = ["postgres", "runtime-tokio", "macros"] }
tokio = { version = "1.52.3", features = ["full"] }
tower-sessions = "0.15"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
urlencoding = "2"
+2
View File
@@ -5,3 +5,5 @@ This is the bot meant for archiving messages in TeenGovernment and related serve
The bot archives new messages and edits, including attachments and embeds. Messages stay in the archive if they are deleted from Discord.
An image is available at git.ewenlau.net/ewenlau/tg-archive-bot:latest for the stable version and git.ewenlau.net/ewenlau/tg-archive-bot:dev for the development (or testing) version.
The web archive uses Discord OAuth2 with the `identify` scope. Add `/login/discord/callback` as a redirect in the Discord developer portal, then set `TG_BOT_DISCORD_CLIENT_ID`, `TG_BOT_DISCORD_CLIENT_SECRET`, and `TG_BOT_DISCORD_REDIRECT_URI`.
+2
View File
@@ -0,0 +1,2 @@
[general]
dirs = ["static"]
+3
View File
@@ -31,6 +31,9 @@ services:
- postgres
environment:
TG_BOT_DISCORD_TOKEN: YOUR_DISCORD_TOKEN_HERE
TG_BOT_DISCORD_CLIENT_ID: YOUR_DISCORD_CLIENT_ID_HERE
TG_BOT_DISCORD_CLIENT_SECRET: YOUR_DISCORD_CLIENT_SECRET_HERE
TG_BOT_DISCORD_REDIRECT_URI: http://localhost:3000/login/discord/callback
TG_BOT_DATABASE_URL: postgres://postgres:YOUR_POSTGRES_PASSWORD_HERE@postgres:5432/tg_archive_bot
TG_BOT_LOG: info
TG_BOT_WEB_ADDRESS: 0.0.0.0:3000
+2
View File
@@ -1,7 +1,9 @@
pub mod git;
pub mod ping;
pub mod stats;
pub mod version;
pub use git::git;
pub use ping::ping;
pub use stats::stats;
pub use version::version;
+33
View File
@@ -0,0 +1,33 @@
use crate::messaging::trace_message;
use crate::{Context, Error, web};
use tracing::{debug, trace};
#[doc = "Show archive statistics"]
#[poise::command(slash_command, prefix_command)]
pub async fn stats(ctx: Context<'_>) -> Result<(), Error> {
trace!(
"stats command called by user {} in guild {}",
ctx.author().id.get(),
ctx.guild_id().unwrap().get()
);
let stats = web::archive_stats(&ctx.data().pool).await?;
let msg = format!(
"# Archive Statistics\n## Messages\nArchived messages: {}\nArchived users: {}\nChannels: {}\nServers: {}\n## Storage usage\nTotal archive: {}\nMessages and metadata: {}\nAttachments: {}",
stats.messages,
stats.users,
stats.channels,
stats.servers,
web::format_bytes(stats.total_storage),
web::format_bytes(stats.message_storage),
web::format_bytes(stats.attachment_storage),
);
trace_message(
&msg,
ctx.channel_id().to_string(),
ctx.guild_id().unwrap().to_string(),
)
.await;
ctx.say(msg).await?;
debug!("Stats command performed for user {}", ctx.author().name);
Ok(())
}
+29 -6
View File
@@ -12,7 +12,6 @@ pub struct Data {
pub pool: sqlx::PgPool,
}
// Define standard types for Poise context, commands, and errors.
pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Context<'a> = poise::Context<'a, Data, Error>;
@@ -37,7 +36,6 @@ async fn main() {
trace!("Env loaded");
// Grab envs
trace!("Loading database url");
let database_url =
env::var("TG_BOT_DATABASE_URL").expect("Expected a database url in the environment");
@@ -51,9 +49,15 @@ async fn main() {
let web_address = env::var("TG_BOT_WEB_ADDRESS").unwrap_or_else(|_| "0.0.0.0:3000".to_string());
trace!("Web address loaded");
let discord_client_id = env::var("TG_BOT_DISCORD_CLIENT_ID")
.expect("Expected a Discord client ID in the environment");
let discord_client_secret = env::var("TG_BOT_DISCORD_CLIENT_SECRET")
.expect("Expected a Discord client secret in the environment");
let discord_redirect_uri = env::var("TG_BOT_DISCORD_REDIRECT_URI")
.expect("Expected a Discord redirect URI in the environment");
info!("Starting bot...");
// Initialize database
debug!("Initalizing database");
let pool = connect_database(&database_url)
.await
@@ -71,7 +75,14 @@ async fn main() {
let web_listener = tokio::net::TcpListener::bind(&web_address)
.await
.expect("Failed to bind web server");
tokio::spawn(web::run(web_listener, pool.clone()));
tokio::spawn(web::run(
web_listener,
pool.clone(),
token.clone(),
discord_client_id,
discord_client_secret,
discord_redirect_uri,
));
debug!("Web server started");
trace!("Loading intents");
@@ -102,7 +113,12 @@ async fn main() {
}
})
},
commands: vec![commands::ping(), commands::version(), commands::git()],
commands: vec![
commands::ping(),
commands::version(),
commands::git(),
commands::stats(),
],
..Default::default()
})
.setup(|ctx, _ready, framework| {
@@ -171,5 +187,12 @@ async fn connect_database(database_url: &str) -> Result<sqlx::PgPool, Error> {
}
fn quote_identifier(identifier: &str) -> String {
format!("\"{}\"", identifier.replace('"', "\"\""))
let mut escaped = String::with_capacity(identifier.len());
for character in identifier.chars() {
escaped.push(character);
if character == '"' {
escaped.push(character);
}
}
format!("\"{}\"", escaped)
}
+1449 -395
View File
File diff suppressed because it is too large. Load diff
+4 -3
View File
@@ -1,5 +1,6 @@
<form method="post" action="$${{action}}">
<input type="hidden" name="search" value="$${{search}}">
$${{additional_fields}} <label>Page <input type="number" name="page" min="1" max="$${{total_pages}}" required></label>
<form method="post" action="{{action}}">
<input type="hidden" name="search" value="{{search}}">
{% if let Some(search_by) = search_by %} <input type="hidden" name="search_by" value="{{search_by}}">
{% endif %} <label>Page <input type="number" name="page" min="1" max="{{total_pages}}" required></label>
<button type="submit">Go</button>
</form>
-1
View File
@@ -1 +0,0 @@
— $${{value}}
+1 -1
View File
@@ -1 +1 @@
<li><a href="/attachments/$${{attachment_id}}?version=$${{message_version}}" target="_blank">View $${{filename}}</a> — $${{size}} bytes$${{content_type}}$${{description}}</li>
<li><a href="/attachments/{{attachment_id}}?version={{message_version}}" target="_blank">View {{filename}}</a> - {{size}} bytes{% if let Some(value) = content_type %} - {{value}}{% endif %}{% if let Some(value) = description %} - {{value}}{% endif %}</li>
+13 -12
View File
@@ -1,22 +1,23 @@
<h2>$${{channel_name}}</h2>
<h2>{{channel_name}}</h2>
<p>You can see this channel because {{access_reason}}.</p>
<dl>
<dt>Channel ID</dt><dd>$${{channel_id}}</dd>
<dt>Server</dt><dd><a href="/servers/$${{server_id}}">$${{server_name}}</a></dd>
<dt>Messages</dt><dd>$${{message_count}}</dd>
<dt>Observed users</dt><dd>$${{user_count}}</dd>
<dt>Message versions</dt><dd>$${{version_count}}</dd>
<dt>Archived data</dt><dd>$${{total_storage}}</dd>
<dt>Message content</dt><dd>$${{message_storage}}</dd>
<dt>Attachments</dt><dd>$${{attachment_storage}}</dd>
<dt>Channel ID</dt><dd>{{channel_id}}</dd>
<dt>Server</dt><dd><a href="/servers/{{server_id}}">{{server_name}}</a></dd>
<dt>Messages</dt><dd>{{message_count}}</dd>
<dt>Observed users</dt><dd>{{user_count}}</dd>
<dt>Message versions</dt><dd>{{version_count}}</dd>
<dt>Archived data</dt><dd>{{total_storage}}</dd>
<dt>Message content</dt><dd>{{message_storage}}</dd>
<dt>Attachments</dt><dd>{{attachment_storage}}</dd>
</dl>
<form method="get" action="/channels/$${{channel_id}}/messages">
<form method="get" action="/channels/{{channel_id}}/messages">
<button type="submit">View messages in this channel</button>
</form>
<h3>Known names</h3>
<ul>
$${{names}}
{{names|safe}}
</ul>
<h3>Observed users</h3>
<ul>
$${{users}}
{{users|safe}}
</ul>
+1 -1
View File
@@ -1 +1 @@
<li><a href="/channels/$${{channel_id}}"><strong>$${{channel_name}}</strong> ($${{channel_id}})</a> in <a href="/servers/$${{server_id}}">$${{server_name}}</a></li>
<li><a href="/channels/{{channel_id}}"><strong>{{channel_name}}</strong> ({{channel_id}})</a> in <a href="/servers/{{server_id}}">{{server_name}}</a></li>
+1 -1
View File
@@ -1 +1 @@
<strong>$${{page_number}}</strong>
<strong>{{page_number}}</strong>
-1
View File
@@ -1 +0,0 @@
<pre>$${{description}}</pre>
-1
View File
@@ -1 +0,0 @@
<strong>$${{title}}</strong>
-1
View File
@@ -1 +0,0 @@
<a href="$${{url}}">$${{url}}</a>
+1 -1
View File
@@ -1 +1 @@
<li>$${{title}}$${{description}}$${{url}}</li>
<li>{% if let Some(value) = title %}<strong>{{value}}</strong>{% endif %}{% if let Some(value) = description %}<pre>{{value}}</pre>{% endif %}{% if let Some(value) = url %}<a href="{{value}}">{{value}}</a>{% endif %}</li>
+1 -1
View File
@@ -1,2 +1,2 @@
<h2>Error</h2>
<p>$${{message}}</p>
<p>{{message}}</p>
+6 -4
View File
@@ -1,8 +1,10 @@
<h2>Archive</h2>
<p>Archived $${{message_count}} messages from $${{user_count}} users across $${{channel_count}} channels on $${{server_count}} servers.</p>
<p>Archived {{message_count}} messages from {{user_count}} users across {{channel_count}} channels on {{server_count}} servers.</p>
<h3>Storage usage</h3>
<dl>
<dt>Total archive</dt><dd>$${{total_storage}}</dd>
<dt>Messages and metadata</dt><dd>$${{message_storage}}</dd>
<dt>Attachments</dt><dd>$${{attachment_storage}}</dd>
<dt>Total archive</dt><dd>{{total_storage}}</dd>
<dt>Messages and metadata</dt><dd>{{message_storage}}</dd>
<dt>Attachments</dt><dd>{{attachment_storage}}</dd>
</dl>
<p>If you cannot see any messages, try and send a message on a server the bot is in.</p>
+5 -5
View File
@@ -1,7 +1,7 @@
<h2>$${{title}}</h2>
$${{search_form}}
<p>$${{item_count}} $${{item_name}} found.</p>
<h2>{{title}}</h2>
{{search_form|safe}}
<p>{{item_count}} {{item_name}} found.</p>
<ul>
$${{items}}
{{items|safe}}
</ul>
$${{pagination}}
{{pagination|safe}}
+6
View File
@@ -0,0 +1,6 @@
<h2>Log in</h2>
<p>It is required to log in with Discord to view the archive.</p>
<p>The login happens on the official Discord website. This site does not have access to your email or password.</p>
<form method="get" action="/login/discord">
<button type="submit">Log in with Discord</button>
</form>
+1 -1
View File
@@ -1 +1 @@
<li><a href="/messages/$${{message_id}}">Message $${{message_id}}</a> by <a href="/users/$${{author_id}}"><strong>$${{author}}</strong></a> in <a href="/servers/$${{server_id}}">$${{server}}</a> / <a href="/channels/$${{channel_id}}">$${{channel}}</a> at $${{timestamp}} — $${{attachment_count}} attachments, $${{embed_count}} embeds</li>
<li><a href="/messages/{{message_id}}">Message {{message_id}}</a> by <a href="/users/{{author_id}}"><strong>{{author}}</strong></a> in <a href="/servers/{{server_id}}">{{server}}</a> / <a href="/channels/{{channel_id}}">{{channel}}</a> at {{timestamp}} - {{attachment_count}} attachments, {{embed_count}} embeds</li>
+5 -5
View File
@@ -1,7 +1,7 @@
<h2>$${{title}}</h2>
$${{search_form}}
<p>$${{item_count}} archived messages found.</p>
<h2>{{title}}</h2>
{{search_form|safe}}
<p>{{item_count}} archived messages found.</p>
<ol>
$${{items}}
{{items|safe}}
</ol>
$${{pagination}}
{{pagination|safe}}
+4 -4
View File
@@ -1,9 +1,9 @@
<form method="post" action="$${{action}}">
<label>Search messages: <input type="search" name="search" value="$${{search}}"></label>
<form method="post" action="{{action}}">
<label>Search messages: <input type="search" name="search" value="{{search}}"></label>
<label>Search by:
<select name="search_by">
<option value="content"$${{content_selected}}>Content</option>
<option value="timestamp"$${{timestamp_selected}}>Timestamp</option>
<option value="content"{{content_selected}}>Content</option>
<option value="timestamp"{{timestamp_selected}}>Timestamp</option>
</select>
</label>
<button type="submit">Search</button>
+2 -2
View File
@@ -1,3 +1,3 @@
<form method="get" action="/messages/$${{message_id}}">
<button type="submit" name="version" value="$${{version}}">$${{label}}</button>
<form method="get" action="/messages/{{message_id}}">
<button type="submit" name="version" value="{{version}}">{{label}}</button>
</form>
+4 -4
View File
@@ -1,9 +1,9 @@
<nav aria-label="Message versions">
<p>Version $${{current_version}} of $${{version_count}} — archived $${{archived_at}}</p>
<p>Version {{current_version}} of {{version_count}} - archived {{archived_at}}</p>
<p>
$${{previous_version}}$${{next_version}} </p>
<form method="get" action="/messages/$${{message_id}}">
<label>Version <input type="number" name="version" min="1" max="$${{version_count}}" value="$${{current_version}}" required></label>
{{previous_version|safe}}{{next_version|safe}} </p>
<form method="get" action="/messages/{{message_id}}">
<label>Version <input type="number" name="version" min="1" max="{{version_count}}" value="{{current_version}}" required></label>
<button type="submit">View version</button>
</form>
</nav>
+10 -9
View File
@@ -1,18 +1,19 @@
<h2>Message $${{message_id}}</h2>
$${{version_navigation}}
<h2>Message {{message_id}}</h2>
<p>You can see this message because {{access_reason}}.</p>
{{version_navigation|safe}}
<dl>
<dt>Author</dt><dd><a href="/users/$${{author_id}}">$${{author}} ($${{author_id}})</a></dd>
<dt>Server</dt><dd><a href="/servers/$${{server_id}}">$${{server}}</a></dd>
<dt>Channel</dt><dd><a href="/channels/$${{channel_id}}">$${{channel}}</a></dd>
<dt>Timestamp</dt><dd>$${{timestamp}}</dd>
<dt>Author</dt><dd><a href="/users/{{author_id}}">{{author}} ({{author_id}})</a></dd>
<dt>Server</dt><dd><a href="/servers/{{server_id}}">{{server}}</a></dd>
<dt>Channel</dt><dd><a href="/channels/{{channel_id}}">{{channel}}</a></dd>
<dt>Timestamp</dt><dd>{{timestamp}}</dd>
</dl>
<h3>Content</h3>
<pre>$${{content}}</pre>
<pre>{{content}}</pre>
<h3>Attachments</h3>
<ul>
$${{attachments}}
{{attachments|safe}}
</ul>
<h3>Embeds</h3>
<ol>
$${{embeds}}
{{embeds|safe}}
</ol>
+4 -3
View File
@@ -1,4 +1,5 @@
<form method="post" action="$${{action}}">
<input type="hidden" name="search" value="$${{search}}">
$${{additional_fields}} <button type="submit" name="page" value="$${{page_number}}">$${{label}}</button>
<form method="post" action="{{action}}">
<input type="hidden" name="search" value="{{search}}">
{% if let Some(search_by) = search_by %} <input type="hidden" name="search_by" value="{{search_by}}">
{% endif %} <button type="submit" name="page" value="{{page_number}}">{{label}}</button>
</form>
+26 -9
View File
@@ -1,23 +1,40 @@
<!doctype html>
<html lang="en">
<html class="theme-{{theme}}" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>$${{title}} — TG Archive</title>
<title>{{ title }} - TG Archive</title>
<link rel="stylesheet" href="/theme.css">
</head>
<body>
<header>
<h1>TG Archive</h1>
<nav>
<header class="site-header">
<strong class="site-brand">TG Archive</strong>
{% if logged_in %} <nav class="site-navigation">
<a href="/">Home</a> |
<a href="/servers">Servers</a> |
<a href="/channels">Channels</a> |
<a href="/users">Users</a> |
<a href="/messages">Messages</a>
</nav>
<a href="/messages">Messages</a> |
<form method="post" action="/logout"><button type="submit">Log out</button></form>
</nav>{% endif %}
</header>
<main>
$${{body}}
<main class="page-content">
{{ body|safe }}
</main>
<footer class="site-footer">
<p class="footer-legal">Source code available <a href="https://git.ewenlau.net/ewenlau/tg-archive-bot">here</a>.<br>
Copyright © 2026 Ewi and contributors<br>
This content is licensed under GPL-3.0.</p>
<form class="footer-theme" method="post" action="/theme">
<label>Theme
<select name="theme">
<option value="white"{% if theme == "white" %} selected{% endif %}>White</option>
<option value="black"{% if theme == "black" %} selected{% endif %}>Black</option>
<option value="oled"{% if theme == "oled" %} selected{% endif %}>OLED</option>
</select>
</label>
<button type="submit">Apply</button>
</form>
</footer>
</body>
</html>
+4 -4
View File
@@ -1,10 +1,10 @@
<nav aria-label="Pages">
<p>
$${{first_pages}}
{{first_pages|safe}}
</p>
$${{arbitrary_page}}
{{arbitrary_page|safe}}
<p>
$${{final_pages}}
{{final_pages|safe}}
</p>
<p>Page $${{current_page}} of $${{total_pages}}</p>
<p>Page {{current_page}} of {{total_pages}}</p>
</nav>
+2 -2
View File
@@ -1,4 +1,4 @@
<form method="post" action="$${{action}}">
<label>$${{label}}: <input type="search" name="search" value="$${{search}}"></label>
<form method="post" action="{{action}}">
<label>{{label}}: <input type="search" name="search" value="{{search}}"></label>
<button type="submit">Search</button>
</form>
+1 -1
View File
@@ -1 +1 @@
— <a href="$${{icon_url}}">icon</a>
- <a href="{{icon_url}}">icon</a>
+14 -14
View File
@@ -1,31 +1,31 @@
<h2>$${{server_name}}</h2>
<h2>{{server_name}}</h2>
<dl>
<dt>Server ID</dt><dd>$${{server_id}}</dd>
<dt>Messages</dt><dd>$${{message_count}}</dd>
<dt>Archived users</dt><dd>$${{user_count}}</dd>
<dt>Archived channels</dt><dd>$${{channel_count}}</dd>
<dt>Message versions</dt><dd>$${{version_count}}</dd>
<dt>Archived data</dt><dd>$${{total_storage}}</dd>
<dt>Message content</dt><dd>$${{message_storage}}</dd>
<dt>Attachments</dt><dd>$${{attachment_storage}}</dd>
<dt>Server ID</dt><dd>{{server_id}}</dd>
<dt>Messages</dt><dd>{{message_count}}</dd>
<dt>Archived users</dt><dd>{{user_count}}</dd>
<dt>Archived channels</dt><dd>{{channel_count}}</dd>
<dt>Message versions</dt><dd>{{version_count}}</dd>
<dt>Archived data</dt><dd>{{total_storage}}</dd>
<dt>Message content</dt><dd>{{message_storage}}</dd>
<dt>Attachments</dt><dd>{{attachment_storage}}</dd>
</dl>
<form method="get" action="/servers/$${{server_id}}/messages">
<form method="get" action="/servers/{{server_id}}/messages">
<button type="submit">View messages in this server</button>
</form>
<h3>Known names</h3>
<ul>
$${{names}}
{{names|safe}}
</ul>
<h3>Known icons</h3>
<ul>
$${{icons}}
{{icons|safe}}
</ul>
<h3>Channels</h3>
<ul>
$${{channels}}
{{channels|safe}}
</ul>
<h3>Observed users</h3>
<p>Users are included after one of their messages is archived in this server.</p>
<ul>
$${{users}}
{{users|safe}}
</ul>
+1 -1
View File
@@ -1 +1 @@
<li><a href="/servers/$${{guild_id}}"><strong>$${{guild_name}}</strong> ($${{guild_id}})</a>$${{icon}}</li>
<li><a href="/servers/{{guild_id}}"><strong>{{guild_name}}</strong> ({{guild_id}})</a>{{icon|safe}}</li>
+1 -1
View File
@@ -1 +1 @@
<li><a href="$${{avatar_url}}"><img src="$${{avatar_url}}" alt="Archived user avatar" width="64" height="64"></a> — first seen $${{first_seen}}, last seen $${{last_seen}}</li>
<li><a href="{{avatar_url}}"><img src="{{avatar_url}}" alt="Archived user avatar" width="64" height="64"></a> - first seen {{first_seen}}, last seen {{last_seen}}</li>
+1 -1
View File
@@ -1 +1 @@
<li><a href="/channels/$${{channel_id}}"><strong>$${{channel_name}}</strong></a> — $${{message_count}} messages</li>
<li><a href="/channels/{{channel_id}}"><strong>{{channel_name}}</strong></a> - {{message_count}} messages</li>
+1 -1
View File
@@ -1 +1 @@
<li><a href="$${{icon_url}}"><img src="$${{icon_url}}" alt="Archived server icon" width="64" height="64"></a> — first seen $${{first_seen}}, last seen $${{last_seen}}</li>
<li><a href="{{icon_url}}"><img src="{{icon_url}}" alt="Archived server icon" width="64" height="64"></a> - first seen {{first_seen}}, last seen {{last_seen}}</li>
+1 -1
View File
@@ -1 +1 @@
<li><strong>$${{name}}</strong> — first seen $${{first_seen}}, last seen $${{last_seen}}</li>
<li><strong>{{name}}</strong> - first seen {{first_seen}}, last seen {{last_seen}}</li>
+1 -1
View File
@@ -1 +1 @@
<li><a href="/servers/$${{server_id}}"><strong>$${{server_name}}</strong></a> — $${{message_count}} messages</li>
<li><a href="/servers/{{server_id}}"><strong>{{server_name}}</strong></a> - {{message_count}} messages</li>
+1 -1
View File
@@ -1 +1 @@
<li><a href="/users/$${{user_id}}"><strong>$${{username}}</strong></a> — $${{message_count}} messages</li>
<li><a href="/users/{{user_id}}"><strong>{{username}}</strong></a> - {{message_count}} messages</li>
+209
View File
@@ -0,0 +1,209 @@
:root {
color-scheme: light;
font-family: Arial, sans-serif;
}
html {
background: #f5f5f5;
color: #202020;
}
html.theme-black {
background: #181818;
color: #e6e6e6;
color-scheme: dark;
}
html.theme-oled {
background: #000;
color: #e6e6e6;
color-scheme: dark;
}
body {
box-sizing: border-box;
display: flex;
flex-direction: column;
line-height: 1.45;
margin: 0 auto;
max-width: 1100px;
min-height: 100vh;
padding: 0 18px;
}
a {
color: #245b91;
}
a:hover {
color: #123f6b;
}
.theme-black a,
.theme-oled a {
color: #8ebbea;
}
.theme-black a:hover,
.theme-oled a:hover {
color: #b8d8f5;
}
.site-header,
.site-footer {
padding: 18px 0;
}
.site-header {
align-items: center;
display: flex;
gap: 24px;
justify-content: space-between;
}
.site-brand {
font-size: 1.2rem;
white-space: nowrap;
}
.site-navigation {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.site-navigation form {
display: inline;
}
.page-content {
background: #fff;
border: 1px solid #d8d8d8;
flex: 1;
padding: 24px;
}
.theme-black .page-content {
background: #222;
border-color: #444;
}
.theme-oled .page-content {
background: #000;
border-color: #444;
}
button,
input,
select {
border: 1px solid #999;
border-radius: 3px;
box-sizing: border-box;
font: inherit;
padding: 7px;
}
button {
background: #f8f8f8;
color: #202020;
cursor: pointer;
}
button:hover {
background: #e9e9e9;
}
.theme-black button,
.theme-black input,
.theme-black select,
.theme-oled button,
.theme-oled input,
.theme-oled select {
background: #292929;
border-color: #666;
color: #eee;
}
.theme-black button:hover,
.theme-oled button:hover {
background: #3d3d3d;
}
form {
margin: 12px 0;
}
li {
margin: 8px 0;
}
dt {
font-weight: bold;
margin-top: 12px;
}
dd {
margin-left: 0;
}
pre {
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.site-footer {
display: flex;
gap: 24px;
justify-content: space-between;
}
.site-footer > * {
margin: 0;
}
.footer-legal {
text-align: left;
}
.footer-theme {
text-align: right;
}
@media (max-width: 700px) {
body {
padding: 0 10px;
}
.site-header,
.site-footer {
align-items: stretch;
flex-direction: column;
gap: 10px;
padding: 12px 0;
}
.page-content {
padding: 16px;
}
.site-navigation {
line-height: 1.8;
}
.footer-theme {
text-align: left;
}
input,
select {
max-width: 100%;
}
img,
video,
iframe {
height: auto;
max-width: 100%;
}
}
+15 -14
View File
@@ -1,30 +1,31 @@
<h2>$${{username}}</h2>
<h2>{{username}}</h2>
<p>You can see this user because they have messages in a channel where {{access_reason}}.</p>
<dl>
<dt>User ID</dt><dd>$${{user_id}}</dd>
<dt>Messages</dt><dd>$${{message_count}}</dd>
<dt>Servers</dt><dd>$${{server_count}}</dd>
<dt>Channels</dt><dd>$${{channel_count}}</dd>
<dt>Message versions</dt><dd>$${{version_count}}</dd>
<dt>Archived data</dt><dd>$${{total_storage}}</dd>
<dt>Message content</dt><dd>$${{message_storage}}</dd>
<dt>Attachments</dt><dd>$${{attachment_storage}}</dd>
<dt>User ID</dt><dd>{{user_id}}</dd>
<dt>Messages</dt><dd>{{message_count}}</dd>
<dt>Servers</dt><dd>{{server_count}}</dd>
<dt>Channels</dt><dd>{{channel_count}}</dd>
<dt>Message versions</dt><dd>{{version_count}}</dd>
<dt>Archived data</dt><dd>{{total_storage}}</dd>
<dt>Message content</dt><dd>{{message_storage}}</dd>
<dt>Attachments</dt><dd>{{attachment_storage}}</dd>
</dl>
<form method="get" action="/users/$${{user_id}}/messages">
<form method="get" action="/users/{{user_id}}/messages">
<button type="submit">View messages by this user</button>
</form>
<h3>Known usernames</h3>
<ul>
$${{names}}
{{names|safe}}
</ul>
<h3>Known avatars</h3>
<ul>
$${{avatars}}
{{avatars|safe}}
</ul>
<h3>Servers</h3>
<ul>
$${{servers}}
{{servers|safe}}
</ul>
<h3>Channels</h3>
<ul>
$${{channels}}
{{channels|safe}}
</ul>
+1 -1
View File
@@ -1 +1 @@
<li><a href="/users/$${{discord_id}}"><strong>$${{discord_username}}</strong> ($${{discord_id}})</a></li>
<li><a href="/users/{{discord_id}}"><strong>{{discord_username}}</strong> ({{discord_id}})</a></li>