feat: add initial Flask web app for Discord server listing
Introduces backend with server retrieval and API integration, front-end UI with templates and styles, and configuration files for deployment.
This commit is contained in:
10
src/ver/0.0.2/data/routes.json
Normal file
10
src/ver/0.0.2/data/routes.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"view_server": {
|
||||||
|
"rule": "/server/<serverID>",
|
||||||
|
"strict_slashes": false
|
||||||
|
},
|
||||||
|
"view_server_default": {
|
||||||
|
"rule": "/server/",
|
||||||
|
"strict_slashes": false
|
||||||
|
}
|
||||||
|
}
|
||||||
198
src/ver/0.0.2/main.py
Normal file
198
src/ver/0.0.2/main.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# main.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import humanfriendly
|
||||||
|
import requests
|
||||||
|
from flask import Flask, render_template, redirect, url_for, abort, request
|
||||||
|
from pymongo import MongoClient
|
||||||
|
|
||||||
|
# --- Flask App Initialization ---
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# --- Logging Configuration ---
|
||||||
|
if app.debug:
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
app.logger.addHandler(handler)
|
||||||
|
app.logger.setLevel(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
app.logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
try:
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
settings_path = os.path.join(base_dir, 'settings.json')
|
||||||
|
app.logger.info(f"Looking for settings.json at: {settings_path}")
|
||||||
|
with open(settings_path, 'r', encoding='utf-8') as f:
|
||||||
|
settings = json.load(f)
|
||||||
|
app.logger.info("settings.json loaded successfully.")
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
|
app.logger.error(f"Error loading settings.json: {e}")
|
||||||
|
settings = {
|
||||||
|
"bot_token": "YOUR_BOT_TOKEN_HERE",
|
||||||
|
"mongo": "mongodb://localhost:27017/",
|
||||||
|
"site_name": "My Server List",
|
||||||
|
"client_id": "YOUR_CLIENT_ID_HERE"
|
||||||
|
}
|
||||||
|
|
||||||
|
bot_token = settings.get("bot_token")
|
||||||
|
if not bot_token or bot_token == "YOUR_BOT_TOKEN_HERE":
|
||||||
|
raise ValueError("Bot Token is not configured in your settings.json file.")
|
||||||
|
|
||||||
|
mongo_uri = settings.get("mongo")
|
||||||
|
if not mongo_uri:
|
||||||
|
raise ValueError("MongoDB connection URI ('mongo') is not configured in your settings.json file.")
|
||||||
|
|
||||||
|
# --- Database Connection (MongoDB) ---
|
||||||
|
client = MongoClient(mongo_uri)
|
||||||
|
db = client.BytesBump
|
||||||
|
servers_collection = db.servers
|
||||||
|
app.logger.info("Successfully connected to MongoDB.")
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_globals():
|
||||||
|
"""Injects global variables into all templates."""
|
||||||
|
client_id = settings.get("client_id")
|
||||||
|
bot_invite_url = None
|
||||||
|
if client_id and client_id != "YOUR_CLIENT_ID_HERE":
|
||||||
|
bot_invite_url = f"https://discord.com/api/oauth2/authorize?client_id={client_id}&permissions=537152577&scope=bot"
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
bot_invite_url=bot_invite_url,
|
||||||
|
site_name=settings.get("site_name", "Server List")
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_all_servers_from_db():
|
||||||
|
"""Fetches all server documents from the MongoDB collection."""
|
||||||
|
return list(servers_collection.find({}))
|
||||||
|
|
||||||
|
# --- RESTORED HELPER FUNCTIONS ---
|
||||||
|
DISCORD_API_URL = "https://discord.com/api/v10"
|
||||||
|
|
||||||
|
def get_discord_guild_data(server_id):
|
||||||
|
"""Fetches guild (server) information from the Discord API."""
|
||||||
|
headers = {"Authorization": f"Bot {bot_token}"}
|
||||||
|
url = f"{DISCORD_API_URL}/guilds/{server_id}?with_counts=true"
|
||||||
|
app.logger.debug(f"Requesting guild data from Discord API for ID: {server_id}")
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
app.logger.error(f"Error fetching guild data from Discord API: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_discord_invite_data(invite_code):
|
||||||
|
"""Fetches invite information from the Discord API."""
|
||||||
|
url = f"{DISCORD_API_URL}/invites/{invite_code}"
|
||||||
|
app.logger.debug(f"Requesting invite data from Discord API for code: {invite_code}")
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
app.logger.error(f"Error fetching invite data from Discord API: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# --- Flask Routes ---
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(err):
|
||||||
|
"""Custom 404 error handler."""
|
||||||
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
"""Renders the home page with a list of all servers."""
|
||||||
|
servers_from_db = get_all_servers_from_db()
|
||||||
|
servers_for_template = []
|
||||||
|
|
||||||
|
for server_in_db in servers_from_db:
|
||||||
|
server_id = server_in_db.get('_id')
|
||||||
|
if not server_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
discord_data = get_discord_guild_data(str(server_id))
|
||||||
|
if not discord_data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
icon_hash = discord_data.get('icon')
|
||||||
|
server_name = discord_data.get('name', 'Unknown')
|
||||||
|
|
||||||
|
server_info = {
|
||||||
|
'_id': server_id,
|
||||||
|
'server_name': server_name,
|
||||||
|
'description': discord_data.get('description') or "No description available.",
|
||||||
|
'tags': server_in_db.get('tags', 'No tags set'),
|
||||||
|
'icon_url': f"https://cdn.discordapp.com/icons/{server_id}/{icon_hash}.png" if icon_hash else f"https://placehold.co/512x512/2c2f33/ffffff?text={server_name[0]}"
|
||||||
|
}
|
||||||
|
servers_for_template.append(server_info)
|
||||||
|
|
||||||
|
template_context = {
|
||||||
|
"servers": servers_for_template,
|
||||||
|
"len": len,
|
||||||
|
"human": humanfriendly,
|
||||||
|
}
|
||||||
|
return render_template('index.html', **template_context)
|
||||||
|
|
||||||
|
@app.route("/server/<string:server_name>")
|
||||||
|
def view_server(server_name):
|
||||||
|
"""Renders the page for a specific server using its name."""
|
||||||
|
original_server_name = server_name.replace('-', ' ')
|
||||||
|
server_db_data = servers_collection.find_one({"server_name": original_server_name})
|
||||||
|
|
||||||
|
if not server_db_data:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
server_id = server_db_data.get('_id')
|
||||||
|
discord_guild_data = get_discord_guild_data(server_id)
|
||||||
|
|
||||||
|
if not discord_guild_data:
|
||||||
|
abort(503)
|
||||||
|
|
||||||
|
invite_code = server_db_data.get('invite_code')
|
||||||
|
if invite_code:
|
||||||
|
discord_guild_data["invite"] = f"https://discord.gg/{invite_code}"
|
||||||
|
else:
|
||||||
|
vanity_code = discord_guild_data.get('vanity_url_code')
|
||||||
|
discord_guild_data["invite"] = f"https://discord.gg/{vanity_code}" if vanity_code else None
|
||||||
|
|
||||||
|
template_context = {
|
||||||
|
"server": server_db_data,
|
||||||
|
"discord": discord_guild_data,
|
||||||
|
"len": len,
|
||||||
|
"human": humanfriendly,
|
||||||
|
}
|
||||||
|
return render_template('view_server.html', **template_context)
|
||||||
|
|
||||||
|
@app.route('/browse/', defaults={'tag_name': None})
|
||||||
|
@app.route('/browse/<string:tag_name>')
|
||||||
|
def browse(tag_name):
|
||||||
|
"""Renders a page for browsing servers by tag."""
|
||||||
|
all_servers = get_all_servers_from_db()
|
||||||
|
all_tags = set()
|
||||||
|
for server in all_servers:
|
||||||
|
tags_string = server.get('tags')
|
||||||
|
if isinstance(tags_string, str):
|
||||||
|
tags_list = [tag.strip() for tag in tags_string.split(',') if tag.strip()]
|
||||||
|
all_tags.update(tags_list)
|
||||||
|
|
||||||
|
sorted_tags = sorted(list(all_tags), key=str.lower)
|
||||||
|
|
||||||
|
if tag_name:
|
||||||
|
query = {"tags": {"$regex": tag_name, "$options": "i"}}
|
||||||
|
filtered_servers = list(servers_collection.find(query))
|
||||||
|
else:
|
||||||
|
filtered_servers = all_servers
|
||||||
|
|
||||||
|
template_context = {
|
||||||
|
"all_tags": sorted_tags,
|
||||||
|
"servers": filtered_servers,
|
||||||
|
"current_tag": tag_name,
|
||||||
|
}
|
||||||
|
return render_template('browse.html', **template_context)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True, port=5000)
|
||||||
5
src/ver/0.0.2/package.json
Normal file
5
src/ver/0.0.2/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "kServers",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "BytesBump Rehash"
|
||||||
|
}
|
||||||
11
src/ver/0.0.2/requirements.txt
Normal file
11
src/ver/0.0.2/requirements.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
humanfriendly
|
||||||
|
Python.js
|
||||||
|
discord
|
||||||
|
discord.py
|
||||||
|
colorama
|
||||||
|
psycopg2-binary
|
||||||
|
dnspython
|
||||||
|
PyYAML
|
||||||
|
requests
|
||||||
|
flask
|
||||||
|
flask_dance
|
||||||
7
src/ver/0.0.2/settings.json
Normal file
7
src/ver/0.0.2/settings.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"site_name": "kServers",
|
||||||
|
"mongo": "mongodb://HOST:PORT/DATABASE",
|
||||||
|
"bot_token": "",
|
||||||
|
"client_id": "1234567891234567891",
|
||||||
|
"bot_invite_url": "https://discord.com/oauth2/authorize?client_id=1234567891234567891&permissions=536955969&integration_type=0&scope=bot"
|
||||||
|
}
|
||||||
BIN
src/ver/0.0.2/static/favicon.ico
Normal file
BIN
src/ver/0.0.2/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
133
src/ver/0.0.2/static/js/app.js
Normal file
133
src/ver/0.0.2/static/js/app.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/* -----------------------------------------------
|
||||||
|
/* How to use? : Check the GitHub README
|
||||||
|
/* ----------------------------------------------- */
|
||||||
|
|
||||||
|
/* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */
|
||||||
|
/*
|
||||||
|
particlesJS.load('particles-js', 'particles.json', function() {
|
||||||
|
console.log('particles.js loaded - callback');
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Otherwise just put the config content (json): */
|
||||||
|
|
||||||
|
particlesJS('particles-js',
|
||||||
|
|
||||||
|
{
|
||||||
|
"particles": {
|
||||||
|
"number": {
|
||||||
|
"value": 80,
|
||||||
|
"density": {
|
||||||
|
"enable": true,
|
||||||
|
"value_area": 2000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"value": "random"
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"type": "circle",
|
||||||
|
"stroke": {
|
||||||
|
"width": 0,
|
||||||
|
"color": "#000000"
|
||||||
|
},
|
||||||
|
"polygon": {
|
||||||
|
"nb_sides": 5
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"src": "img/github.svg",
|
||||||
|
"width": 100,
|
||||||
|
"height": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"value": 0.5,
|
||||||
|
"random": false,
|
||||||
|
"anim": {
|
||||||
|
"enable": false,
|
||||||
|
"speed": 1,
|
||||||
|
"opacity_min": 0.1,
|
||||||
|
"sync": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"value": 5,
|
||||||
|
"random": true,
|
||||||
|
"anim": {
|
||||||
|
"enable": false,
|
||||||
|
"speed": 40,
|
||||||
|
"size_min": 0.1,
|
||||||
|
"sync": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"line_linked": {
|
||||||
|
"enable": true,
|
||||||
|
"distance": 150,
|
||||||
|
"color": "#ffffff",
|
||||||
|
"opacity": 0.4,
|
||||||
|
"width": 1
|
||||||
|
},
|
||||||
|
"move": {
|
||||||
|
"enable": true,
|
||||||
|
"speed": 6,
|
||||||
|
"direction": "none",
|
||||||
|
"random": false,
|
||||||
|
"straight": false,
|
||||||
|
"out_mode": "out",
|
||||||
|
"attract": {
|
||||||
|
"enable": false,
|
||||||
|
"rotateX": 600,
|
||||||
|
"rotateY": 1200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interactivity": {
|
||||||
|
"detect_on": "canvas",
|
||||||
|
"events": {
|
||||||
|
"onhover": {
|
||||||
|
"enable": true,
|
||||||
|
"mode": "grab"
|
||||||
|
},
|
||||||
|
"onclick": {
|
||||||
|
"enable": true,
|
||||||
|
"mode": "push"
|
||||||
|
},
|
||||||
|
"resize": true
|
||||||
|
},
|
||||||
|
"modes": {
|
||||||
|
"grab": {
|
||||||
|
"distance": 400,
|
||||||
|
"line_linked": {
|
||||||
|
"opacity": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bubble": {
|
||||||
|
"distance": 400,
|
||||||
|
"size": 40,
|
||||||
|
"duration": 2,
|
||||||
|
"opacity": 8,
|
||||||
|
"speed": 3
|
||||||
|
},
|
||||||
|
"repulse": {
|
||||||
|
"distance": 200
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"particles_nb": 4
|
||||||
|
},
|
||||||
|
"remove": {
|
||||||
|
"particles_nb": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"retina_detect": true,
|
||||||
|
"config_demo": {
|
||||||
|
"hide_card": false,
|
||||||
|
"background_color": "#b61924",
|
||||||
|
"background_image": "",
|
||||||
|
"background_position": "50% 50%",
|
||||||
|
"background_repeat": "no-repeat",
|
||||||
|
"background_size": "cover"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
1541
src/ver/0.0.2/static/js/particles.js
Normal file
1541
src/ver/0.0.2/static/js/particles.js
Normal file
File diff suppressed because it is too large
Load Diff
52
src/ver/0.0.2/static/styles/base.css
Normal file
52
src/ver/0.0.2/static/styles/base.css
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
html, body {
|
||||||
|
overflow-y: scroll;
|
||||||
|
margin: 0; /* This removes the extra space */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #000;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
margin: 0; /* This removes the extra space */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is the updated rule */
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
#particles-js {
|
||||||
|
z-index: -1;
|
||||||
|
position: fixed;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item.active::after {
|
||||||
|
content: ' «';
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.col-md-3 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/ver/0.0.2/static/styles/browse.css
Normal file
15
src/ver/0.0.2/static/styles/browse.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.list-group-item.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item.active::after {
|
||||||
|
content: ' «';
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Example: Add a bit more space above the tag list when on mobile devices */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.col-md-3 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/ver/0.0.2/static/styles/index.css
Normal file
35
src/ver/0.0.2/static/styles/index.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
.servers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center; /* This keeps the cards centered */
|
||||||
|
opacity: 0%;
|
||||||
|
padding: 10px; /* This adds some nice spacing around the cards */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: rgba(211, 211, 211, 0.55) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item.active::after {
|
||||||
|
content: ' «';
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Example: Add a bit more space above the tag list when on mobile devices */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.col-md-3 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/ver/0.0.2/static/styles/view_server.css
Normal file
48
src/ver/0.0.2/static/styles/view_server.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.container {
|
||||||
|
margin-top : 10px;
|
||||||
|
background: rgba(211, 211, 211, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
#serverName {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 30px;
|
||||||
|
width: 80%;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info > img {
|
||||||
|
height: 200px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-column > a {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-column > .card-body {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
visibility: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body > a {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
42
src/ver/0.0.2/templates/404.html
Normal file
42
src/ver/0.0.2/templates/404.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
|
||||||
|
<title>Not Found | {{site_name}}</title>
|
||||||
|
<style>
|
||||||
|
.center-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 80vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="center-container">
|
||||||
|
<div class="card shadow-lg p-4 text-light" style="background-color: rgba(44, 47, 51, 0.8);">
|
||||||
|
<h1 class="display-1">404</h1>
|
||||||
|
<h2>Page Not Found</h2>
|
||||||
|
<p class="lead">Sorry, the page you are looking for does not exist.</p>
|
||||||
|
<a href="{{ url_for('index') }}" class="btn btn-primary mt-3">Go Home</a>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
<p class="text-muted small">
|
||||||
|
<strong>Note for server owners:</strong> If you're seeing this page for your server, its name may have recently changed on Discord. You can fix the link by running the `{p}sync` command in your server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="particles-js"></div>
|
||||||
|
<script src="{{url_for('static', filename='js/particles.js')}}"></script>
|
||||||
|
<script src="{{url_for('static', filename='js/app.js')}}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
28
src/ver/0.0.2/templates/base.html
Normal file
28
src/ver/0.0.2/templates/base.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
|
||||||
|
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
|
||||||
|
{% block links %}{% endblock %}
|
||||||
|
<title>{% block title %}{{ site_name }}{% endblock %}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
<div id="particles-js"></div>
|
||||||
|
<script src="{{url_for('static', filename='js/particles.js')}}"></script>
|
||||||
|
<script src="{{url_for('static', filename='js/app.js')}}"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
80
src/ver/0.0.2/templates/browse.html
Normal file
80
src/ver/0.0.2/templates/browse.html
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Browse Servers | {{ site_name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block links %}
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='styles/index.css')}}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container my-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<a href="{{ url_for('index') }}" class="btn btn-outline-light">← Back to Home</a>
|
||||||
|
<h1 class="text-light">Browse Servers</h1>
|
||||||
|
</div>
|
||||||
|
<hr class="text-light">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h4 class="text-light">Tags</h4>
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="{{ url_for('browse') }}" class="list-group-item list-group-item-action {% if not current_tag %}active{% endif %}">
|
||||||
|
All Servers
|
||||||
|
</a>
|
||||||
|
{% for tag in all_tags %}
|
||||||
|
<a href="{{ url_for('browse', tag_name=tag) }}" class="list-group-item list-group-item-action {% if tag == current_tag %}active{% endif %}">
|
||||||
|
{{ tag }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="servers">
|
||||||
|
{% if servers %}
|
||||||
|
{% for server in servers %}
|
||||||
|
<div class="card shadow-lg" style="width: 18rem; margin:5px;">
|
||||||
|
<img class="card-img-top" src="{{server['icon_url']}}">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-header bg-danger bg-gradient text-light" style="margin-bottom:5px;">
|
||||||
|
<strong>{{server['server_name']}}</strong>
|
||||||
|
</div>
|
||||||
|
<p class="card-text server-description">{{ server['description'] }}</p>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>Tags:</strong> {{ server['tags'] }}
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-outline-success d-block mx-auto" href="/server/{{ server['server_name']|replace(' ', '-') }}">View Server</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="text-light text-center mt-5">
|
||||||
|
<h4>No servers found with the tag "{{ current_tag }}".</h4>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(() => {
|
||||||
|
// This is the part that renders markdown
|
||||||
|
const md = window.markdownit();
|
||||||
|
const elements = document.getElementsByClassName("server-description");
|
||||||
|
for (var i = 0; i < elements.length; i++) {
|
||||||
|
elements[i].innerHTML = md.render(elements[i].innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ADD THIS ANIMATION ---
|
||||||
|
// This fades in the server list, making it visible
|
||||||
|
$(".servers").animate({
|
||||||
|
opacity: "100%"
|
||||||
|
}, 600)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
58
src/ver/0.0.2/templates/index.html
Normal file
58
src/ver/0.0.2/templates/index.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='styles/index.css')}}">
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
<title>Home | {{site_name}}</title>
|
||||||
|
</head>
|
||||||
|
<body>}
|
||||||
|
|
||||||
|
<div class="container text-center my-4">
|
||||||
|
<h1><a href="{{ url_for('index') }}" class="text-light text-decoration-none">{{ site_name }}</a></h1>
|
||||||
|
<a href="{{ url_for('browse') }}" class="btn btn-secondary mt-2">Browse by Tag</a>
|
||||||
|
<a href="{{ bot_invite_url }}" class="btn btn-primary invite-button" target="https://discord.com/oauth2/authorize?client_id=1285545069591527466&permissions=537012969&integration_type=0&scope=bot">🤖 Invite Bot</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="servers">
|
||||||
|
{% for server in servers %}
|
||||||
|
<div class="card shadow-lg" style="width: 18rem; margin:5px;">
|
||||||
|
<img class="card-img-top" src="{{server['icon_url']}}">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-header bg-danger bg-gradient text-light" style="margin-bottom:5px;">
|
||||||
|
<strong>{{server['server_name']}}</strong>
|
||||||
|
</div>
|
||||||
|
<p class="card-text server-description">{{ server['description'] }}</p>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>Tags:</strong> {{ server['tags'] }}
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-success d-block mx-auto" href="/server/{{ server['server_name']|replace(' ', '-') }}">View Server</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="particles-js"></div>
|
||||||
|
<script src="{{url_for('static', filename='js/particles.js')}}"></script>
|
||||||
|
<script src="{{url_for('static', filename='js/app.js')}}"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(() => {
|
||||||
|
const md = window.markdownit();
|
||||||
|
const elements = document.getElementsByClassName("server-description");
|
||||||
|
for (var i = 0; i < elements.length; i++) {
|
||||||
|
elements[i].innerHTML = md.render(elements[i].innerHTML);
|
||||||
|
}
|
||||||
|
$(".servers").animate({
|
||||||
|
opacity: "100%"
|
||||||
|
}, 600)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
66
src/ver/0.0.2/templates/view_server.html
Normal file
66
src/ver/0.0.2/templates/view_server.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha341-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='styles/base.css')}}">
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='styles/index.css')}}">
|
||||||
|
<title>{{ discord['name'] }} | {{site_name}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container my-4">
|
||||||
|
<a href="{{ url_for('index') }}" class="btn btn-outline-light">← Back to Server List</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="card shadow-lg text-light">
|
||||||
|
<div class="card-header bg-danger bg-gradient text-light text-center h3">
|
||||||
|
{{ discord.name }}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
{% if discord.icon %}
|
||||||
|
<img src="https://cdn.discordapp.com/icons/{{ discord.id }}/{{ discord.icon }}.png?size=256" class="img-fluid rounded-circle border border-5 border-danger mb-3">
|
||||||
|
{% else %}
|
||||||
|
<img src="https://placehold.co/256x256/2c2f33/ffffff?text={{ discord.name[0] }}" class="img-fluid rounded-circle border border-5 border-danger mb-3">
|
||||||
|
{% endif %}
|
||||||
|
{% if discord.invite %}
|
||||||
|
<a href="{{ discord.invite }}" class="btn btn-success btn-lg my-3" target="_blank">Join Server</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h4>Description</h4>
|
||||||
|
<p id="description-text">{{ server.description or "No description available." }}</p>
|
||||||
|
<hr>
|
||||||
|
<h4>Tags</h4>
|
||||||
|
<p>{{ server.tags or "No tags set." }}</p>
|
||||||
|
<hr>
|
||||||
|
<h4>Server Stats</h4>
|
||||||
|
<p>
|
||||||
|
<span class="badge bg-primary me-2">Members: {{ human.format_number(discord.approximate_member_count) }}</span>
|
||||||
|
<span class="badge bg-secondary me-2">Roles: {{ discord.roles | length }}</span>
|
||||||
|
<span class="badge bg-warning text-dark me-2">Boosts: {{ discord.premium_subscription_count }} (Tier {{ discord.premium_tier }})</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="particles-js"></div>
|
||||||
|
<script src="{{url_for('static', filename='js/particles.js')}}"></script>
|
||||||
|
<script src="{{url_for('static', filename='js/app.js')}}"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.4/markdown-it.js"></script>
|
||||||
|
<script>
|
||||||
|
// Render markdown for the description
|
||||||
|
const md = window.markdownit();
|
||||||
|
const descriptionElement = document.getElementById("description-text");
|
||||||
|
if (descriptionElement) {
|
||||||
|
descriptionElement.innerHTML = md.render(descriptionElement.innerHTML);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user