Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48408cba61 |
No files matched your search
@@ -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"
|
||||
@@ -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`.
|
||||
@@ -0,0 +1,2 @@
|
||||
[general]
|
||||
dirs = ["static"]
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
+1452
-398
File diff suppressed because it is too large.
Load diff
@@ -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 +0,0 @@
|
||||
— $${{value}}
|
||||
@@ -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
@@ -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
@@ -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 @@
|
||||
<strong>$${{page_number}}</strong>
|
||||
<strong>{{page_number}}</strong>
|
||||
@@ -1 +0,0 @@
|
||||
<pre>$${{description}}</pre>
|
||||
@@ -1 +0,0 @@
|
||||
<strong>$${{title}}</strong>
|
||||
@@ -1 +0,0 @@
|
||||
<a href="$${{url}}">$${{url}}</a>
|
||||
+1
-1
@@ -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
@@ -1,2 +1,2 @@
|
||||
<h2>Error</h2>
|
||||
<p>$${{message}}</p>
|
||||
<p>{{message}}</p>
|
||||
+6
-4
@@ -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
@@ -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}}
|
||||
@@ -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 @@
|
||||
<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>
|
||||
@@ -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}}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 @@
|
||||
— <a href="$${{icon_url}}">icon</a>
|
||||
- <a href="{{icon_url}}">icon</a>
|
||||
+14
-14
@@ -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
@@ -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 @@
|
||||
<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 @@
|
||||
<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 @@
|
||||
<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 @@
|
||||
<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 @@
|
||||
<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 @@
|
||||
<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>
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
Reference in new issue
Block a user