Refactor database path resolution in cogs and improve cog loading error handling. Update database connections in Giveaway, Nightmode, and Owner cogs to use a centralized path resolver. Enhance cog loading feedback with success and failure messages.
This commit is contained in:
@@ -192,8 +192,17 @@ COG_CLASSES: tuple[type, ...] = (
|
|||||||
|
|
||||||
|
|
||||||
async def setup(bot: Axiom) -> None:
|
async def setup(bot: Axiom) -> None:
|
||||||
|
loaded = 0
|
||||||
for cog_cls in COG_CLASSES:
|
for cog_cls in COG_CLASSES:
|
||||||
|
try:
|
||||||
await bot.add_cog(cog_cls(bot))
|
await bot.add_cog(cog_cls(bot))
|
||||||
print(Fore.RED + Style.BRIGHT + f"Loaded cog: {cog_cls.__name__}")
|
loaded += 1
|
||||||
|
print(Fore.GREEN + Style.BRIGHT + f"Loaded cog: {cog_cls.__name__}")
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
Fore.RED
|
||||||
|
+ Style.BRIGHT
|
||||||
|
+ f"Failed cog {getattr(cog_cls, '__name__', cog_cls)}: {type(e).__name__}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
print(Fore.RED + Style.BRIGHT + f"All {BotName} Cogs loaded successfully.")
|
print(Fore.GREEN + Style.BRIGHT + f"Loaded {loaded}/{len(COG_CLASSES)} {BotName} cogs.")
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ import os
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
from utils.cv2 import CV2
|
from utils.cv2 import CV2
|
||||||
|
|
||||||
db_folder = 'db'
|
from utils.db_paths import db_path as resolve_db_path
|
||||||
|
|
||||||
db_file = 'giveaways.db'
|
db_file = 'giveaways.db'
|
||||||
db_path = os.path.join(db_folder, db_file)
|
giveaways_db_path = resolve_db_path(db_file)
|
||||||
connection = sqlite3.connect(db_path)
|
connection = sqlite3.connect(giveaways_db_path)
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ class Giveaway(commands.Cog):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
async def cog_load(self) -> None:
|
async def cog_load(self) -> None:
|
||||||
self.connection = await aiosqlite.connect(db_path)
|
self.connection = await aiosqlite.connect(giveaways_db_path)
|
||||||
self.cursor = await self.connection.cursor()
|
self.cursor = await self.connection.cursor()
|
||||||
await self.check_for_ended_giveaways()
|
await self.check_for_ended_giveaways()
|
||||||
self.GiveawayEnd.start()
|
self.GiveawayEnd.start()
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ from utils.Tools import *
|
|||||||
from utils.cv2 import CV2
|
from utils.cv2 import CV2
|
||||||
from discord.ui import TextDisplay, Separator, ActionRow, LayoutView, Container
|
from discord.ui import TextDisplay, Separator, ActionRow, LayoutView, Container
|
||||||
from utils.config import OWNER_IDS_STR
|
from utils.config import OWNER_IDS_STR
|
||||||
|
from utils.db_paths import db_path as resolve_db_path
|
||||||
|
|
||||||
# Database setup
|
# Database setup
|
||||||
db_folder = "db"
|
nightmode_db_path = resolve_db_path("anti.db")
|
||||||
db_file = "anti.db"
|
|
||||||
db_path = os.path.join(db_folder, db_file)
|
|
||||||
|
|
||||||
|
|
||||||
class Nightmode(commands.Cog):
|
class Nightmode(commands.Cog):
|
||||||
@@ -34,7 +33,7 @@ class Nightmode(commands.Cog):
|
|||||||
self.color = 0xFF0000
|
self.color = 0xFF0000
|
||||||
|
|
||||||
async def initialize_db(self):
|
async def initialize_db(self):
|
||||||
self.db = await aiosqlite.connect(db_path)
|
self.db = await aiosqlite.connect(nightmode_db_path)
|
||||||
await self.db.execute("""
|
await self.db.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS Nightmode (
|
CREATE TABLE IF NOT EXISTS Nightmode (
|
||||||
guildId TEXT,
|
guildId TEXT,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
# ╚══════════════════════════════════════════════════════════════════╝
|
# ╚══════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from utils.db_paths import db_path
|
from utils.db_paths import db_path as resolve_db_path
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord import *
|
from discord import *
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
||||||
@@ -71,12 +71,12 @@ BACK_COMPAT_BADGE_DICT = DISCORD_BADGE_EMOJIS
|
|||||||
|
|
||||||
db_folder = 'db'
|
db_folder = 'db'
|
||||||
db_file = 'badges.db'
|
db_file = 'badges.db'
|
||||||
db_path = os.path.join(db_folder, db_file)
|
badges_db_path = resolve_db_path(db_file)
|
||||||
FONT_PATH = os.path.join('utils', 'arial.ttf')
|
FONT_PATH = os.path.join('utils', 'arial.ttf')
|
||||||
|
|
||||||
# --- Database Setup ---
|
# --- Database Setup ---
|
||||||
os.makedirs(db_folder, exist_ok=True)
|
os.makedirs(os.path.dirname(badges_db_path) or 'db', exist_ok=True)
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(badges_db_path)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
c.execute('CREATE TABLE IF NOT EXISTS badges (user_id INTEGER PRIMARY KEY)')
|
c.execute('CREATE TABLE IF NOT EXISTS badges (user_id INTEGER PRIMARY KEY)')
|
||||||
@@ -136,7 +136,7 @@ class Owner(commands.Cog):
|
|||||||
self.client = client
|
self.client = client
|
||||||
self.staff = set()
|
self.staff = set()
|
||||||
self.np_cache = []
|
self.np_cache = []
|
||||||
self.db_path = db_path('np.db')
|
self.db_path = resolve_db_path('np.db')
|
||||||
self.stop_tour = False
|
self.stop_tour = False
|
||||||
self.bot_owner_ids = BOT_OWNER_IDS
|
self.bot_owner_ids = BOT_OWNER_IDS
|
||||||
self.client.loop.create_task(self.setup_database())
|
self.client.loop.create_task(self.setup_database())
|
||||||
|
|||||||
@@ -64,26 +64,11 @@ export default async function GuildsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("https://discord.com/api/users/@me/guilds", {
|
const { fetchUserGuilds } = await import("@/lib/guild-auth");
|
||||||
headers: {
|
userGuilds = await fetchUserGuilds(session.accessToken);
|
||||||
Authorization: `Bearer ${session.accessToken}`,
|
} catch (err: any) {
|
||||||
},
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
userGuilds = await res.json();
|
|
||||||
} else {
|
|
||||||
const body = await res.text().catch(() => "");
|
|
||||||
console.error("Discord guilds fetch failed:", res.status, body);
|
|
||||||
userDiscordError =
|
|
||||||
res.status === 401
|
|
||||||
? "Discord session expired — please sign in again."
|
|
||||||
: "Failed to fetch your Discord servers.";
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Discord API Error:", err);
|
console.error("Discord API Error:", err);
|
||||||
userDiscordError = "Error connecting to Discord.";
|
userDiscordError = err?.message || "Error connecting to Discord.";
|
||||||
}
|
}
|
||||||
|
|
||||||
const MANAGE_GUILD = BigInt(0x20);
|
const MANAGE_GUILD = BigInt(0x20);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* Discord guild permission helpers for dashboard access control.
|
* Discord guild permission helpers for dashboard access control.
|
||||||
|
* Includes short-lived cache + 429 retry to avoid Discord rate limits.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface DiscordGuild {
|
export interface DiscordGuild {
|
||||||
id: string;
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
icon?: string | null;
|
||||||
permissions: string;
|
permissions: string;
|
||||||
owner?: boolean;
|
owner?: boolean;
|
||||||
}
|
}
|
||||||
@@ -11,6 +14,14 @@ export interface DiscordGuild {
|
|||||||
const MANAGE_GUILD = BigInt(0x20);
|
const MANAGE_GUILD = BigInt(0x20);
|
||||||
const ADMINISTRATOR = BigInt(0x8);
|
const ADMINISTRATOR = BigInt(0x8);
|
||||||
|
|
||||||
|
const CACHE_TTL_MS = 60_000;
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
|
||||||
|
type CacheEntry = { guilds: DiscordGuild[]; expiresAt: number };
|
||||||
|
|
||||||
|
const guildCache = new Map<string, CacheEntry>();
|
||||||
|
const inflight = new Map<string, Promise<DiscordGuild[]>>();
|
||||||
|
|
||||||
export function canManageGuild(guild: DiscordGuild): boolean {
|
export function canManageGuild(guild: DiscordGuild): boolean {
|
||||||
if (guild.owner) return true;
|
if (guild.owner) return true;
|
||||||
try {
|
try {
|
||||||
@@ -24,15 +35,80 @@ export function canManageGuild(guild: DiscordGuild): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUserGuilds(
|
function cacheKey(accessToken: string): string {
|
||||||
accessToken: string
|
// Avoid storing raw token as object key longer than needed — short fingerprint is enough for process cache
|
||||||
): Promise<DiscordGuild[]> {
|
return `tok:${accessToken.slice(0, 12)}:${accessToken.length}:${accessToken.slice(-8)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function invalidateUserGuildsCache(accessToken?: string) {
|
||||||
|
if (!accessToken) {
|
||||||
|
guildCache.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guildCache.delete(cacheKey(accessToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUserGuildsUncached(accessToken: string): Promise<DiscordGuild[]> {
|
||||||
|
let attempt = 0;
|
||||||
|
while (attempt < MAX_RETRIES) {
|
||||||
|
attempt += 1;
|
||||||
const res = await fetch("https://discord.com/api/users/@me/guilds", {
|
const res = await fetch("https://discord.com/api/users/@me/guilds", {
|
||||||
headers: { Authorization: `Bearer ${accessToken}` },
|
headers: { Authorization: `Bearer ${accessToken}` },
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
});
|
});
|
||||||
if (!res.ok) return [];
|
|
||||||
return res.json();
|
if (res.ok) {
|
||||||
|
return (await res.json()) as DiscordGuild[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 429) {
|
||||||
|
let retryAfter = 1;
|
||||||
|
try {
|
||||||
|
const body = await res.json();
|
||||||
|
retryAfter = Number(body.retry_after ?? 1);
|
||||||
|
} catch {
|
||||||
|
const header = res.headers.get("retry-after");
|
||||||
|
retryAfter = header ? Number(header) : 1;
|
||||||
|
}
|
||||||
|
const waitMs = Math.min(Math.max(retryAfter * 1000, 300), 5000);
|
||||||
|
console.warn(`Discord guilds rate-limited — retry in ${waitMs}ms (attempt ${attempt}/${MAX_RETRIES})`);
|
||||||
|
await new Promise((r) => setTimeout(r, waitMs));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await res.text().catch(() => "");
|
||||||
|
console.error("Discord guilds fetch failed:", res.status, body);
|
||||||
|
throw new Error(
|
||||||
|
res.status === 401
|
||||||
|
? "Discord session expired — please sign in again."
|
||||||
|
: `Failed to fetch your Discord servers. (${res.status})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to fetch your Discord servers. (rate limited)");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchUserGuilds(accessToken: string): Promise<DiscordGuild[]> {
|
||||||
|
const key = cacheKey(accessToken);
|
||||||
|
const cached = guildCache.get(key);
|
||||||
|
if (cached && cached.expiresAt > Date.now()) {
|
||||||
|
return cached.guilds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = inflight.get(key);
|
||||||
|
if (existing) return existing;
|
||||||
|
|
||||||
|
const promise = fetchUserGuildsUncached(accessToken)
|
||||||
|
.then((guilds) => {
|
||||||
|
guildCache.set(key, { guilds, expiresAt: Date.now() + CACHE_TTL_MS });
|
||||||
|
return guilds;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
inflight.delete(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
inflight.set(key, promise);
|
||||||
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function userCanManageGuild(
|
export async function userCanManageGuild(
|
||||||
|
|||||||
Reference in New Issue
Block a user