From 95800ffe216a83bc0eba994ecc53ed22860fe90e Mon Sep 17 00:00:00 2001 From: ilotterytea Date: Mon, 8 Dec 2025 22:17:05 +0500 Subject: upd: include paths --- lib/accounts.php | 101 +++++++++++++++ lib/alert.php | 40 ++++++ lib/captcha.php | 151 +++++++++++++++++++++++ lib/config.sample.php | 74 +++++++++++ lib/emote.php | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/images.php | 78 ++++++++++++ lib/partials.php | 179 +++++++++++++++++++++++++++ lib/user.php | 102 ++++++++++++++++ lib/utils.php | 68 +++++++++++ lib/version.php | 11 ++ 10 files changed, 1136 insertions(+) create mode 100644 lib/accounts.php create mode 100644 lib/alert.php create mode 100644 lib/captcha.php create mode 100644 lib/config.sample.php create mode 100644 lib/emote.php create mode 100644 lib/images.php create mode 100644 lib/partials.php create mode 100644 lib/user.php create mode 100644 lib/utils.php create mode 100644 lib/version.php (limited to 'lib') diff --git a/lib/accounts.php b/lib/accounts.php new file mode 100644 index 0000000..51cb3f6 --- /dev/null +++ b/lib/accounts.php @@ -0,0 +1,101 @@ + 401, + "message" => "Unauthorized", + "data" => null + ]); + } else { + header("Location: /account"); + } + } + + return false; + } + + include_once "config.php"; + + $db = new PDO(DB_URL, DB_USER, DB_PASS); + + $key = $_SERVER["HTTP_AUTHORIZATION"] ?? $_COOKIE["secret_key"]; + + $stmt = $db->prepare("SELECT id, username FROM users WHERE secret_key = ?"); + $stmt->execute([$key]); + + if ($row = $stmt->fetch()) { + $_SESSION["user_id"] = $row["id"]; + $_SESSION["user_name"] = $row["username"]; + + $stmt = $db->prepare("UPDATE users SET last_active_at = UTC_TIMESTAMP WHERE id = ?"); + $stmt->execute([$row["id"]]); + + // fetching role + $stmt = $db->prepare("SELECT * FROM roles r + INNER JOIN role_assigns ra ON ra.user_id = ? + WHERE r.id = ra.role_id + "); + $stmt->execute([$row["id"]]); + + $_SESSION["user_role"] = null; + + if ($role_row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $_SESSION["user_role"] = $role_row; + } + + $stmt = $db->prepare("SELECT es.*, aes.is_default FROM emote_sets es + INNER JOIN acquired_emote_sets aes ON aes.emote_set_id = es.id + WHERE aes.user_id = ? + ORDER BY + CASE WHEN es.id = ? THEN 0 ELSE 1 END, + es.id + "); + $stmt->execute([$row["id"], $_SESSION["user_active_emote_set_id"] ?? ""]); + + $emote_sets = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (!isset($_SESSION["user_active_emote_set_id"])) { + foreach ($emote_sets as $es) { + if ($es["is_default"]) { + $_SESSION["user_active_emote_set"] = $es; + $_SESSION["user_active_emote_set_id"] = $es["id"]; + } + } + } + + $_SESSION["user_emote_sets"] = $emote_sets; + } else { + session_regenerate_id(); + session_unset(); + setcookie("secret_key", "", time() - 1000); + + if ($required) { + if (isset($_SERVER["HTTP_ACCEPT"]) && $_SERVER["HTTP_ACCEPT"] == "application/json") { + http_response_code(401); + echo json_encode([ + "status_code" => 401, + "message" => "Unauthorized", + "data" => null + ]); + } else { + header("Location: /account"); + } + } + } + + $db = null; + $stmt = null; + return isset($_SESSION["user_name"]); +} \ No newline at end of file diff --git a/lib/alert.php b/lib/alert.php new file mode 100644 index 0000000..823a97a --- /dev/null +++ b/lib/alert.php @@ -0,0 +1,40 @@ + $status, + "message" => $error, + "data" => null + ]); + } else { + header("Location: $path" . (str_contains($path, "?") ? "&" : "?") . "error_status=$status&error_reason=$error"); + } +} + +function display_alert() +{ + if (!isset($_GET["error_status"], $_GET["error_reason"])) { + return; + } + + $status = $_GET["error_status"]; + $reason = str_safe($_GET["error_reason"], 100); + $ok = substr($status, 0, 1) == '2'; + + echo '' ?> +
+ +

+
+ + + + + "COAL", + "1" => "GEM", +]); // Rating names. The schema is [ "id/rating_point" => "name" ]. +define("RATING_EMOTE_MIN_VOTES", 10); // Minimal amount of votes to display emote rating. + +// UPLOADS +define("ANONYMOUS_UPLOAD", false); // Allow anonymous upload for emotes. +define("ANONYMOUS_DEFAULT_NAME", "Anonymous"); // Default uploader name for anonymous emotes. It's also used when original uploader has been deleted. + +// EMOTES +define("EMOTE_UPLOAD", true); // Enable emote upload. +define("EMOTE_NAME_MAX_LENGTH", 100); // Max length for emote name. +define("EMOTE_COMMENT_MAX_LENGTH", 100); // Max length for emote comment. +define("EMOTE_VISIBILITY_DEFAULT", 2); // Default visibility for emotes. 0 - unlisted, 1 - public, 2 - pending approval (same as unlisted). +define("EMOTE_MAX_SIZE", [128, 128]); // Max size of emote. +define("EMOTE_NAME_REGEX", "/^[A-Za-z0-9_]+$/"); // RegEx filter for emote names. +define("EMOTE_STORE_ORIGINAL", true); // Store original uploads of emotes. + +// TAGS +define("TAGS_ENABLE", true); // Allow emote tagging. +define("TAGS_CODE_REGEX", "/^[A-Za-z0-9_]+$/"); +define("TAGS_MAX_COUNT", 10); // Maximum tags per emote. Set -1 for unlimited amount. + +// EMOTESETS +define("EMOTESET_PUBLIC_LIST", true); // Show emotesets public. + +// MODERATION +define("MOD_SYSTEM_DASHBOARD", true); // Enable system dashboard for moderators (/system). +define("MOD_EMOTES_APPROVE", true); // Enable manual emote approval (/system/emotes). + +// REPORTS +define("REPORTS_ENABLE", true); // Enable emote, user reports. + +// ACCOUNTS +define("ACCOUNT_REGISTRATION_ENABLE", true); // Enable account registration. +define("ACCOUNT_COOKIE_MAX_LIFETIME", 86400 * 30); // Remember user for a month. +define("ACCOUNT_USERNAME_REGEX", "/^[A-Za-z0-9_]+$/"); // RegEx filter for account usernames. +define("ACCOUNT_USERNAME_LENGTH", [2, 20]); // [Min, Max] length for account usernames. +define("ACCOUNT_PASSWORD_MIN_LENGTH", 10); // Minimal length for passwords. +define("ACCOUNT_SECRET_KEY_LENGTH", 32); // The length for secret keys. +define("ACCOUNT_PFP_MAX_SIZE", [128, 128]); // Max dimensions for account pictures. +define("ACCOUNT_BANNER_MAX_SIZE", [1920, 1080]); // Max dimensions for account banners. +define("ACCOUNT_BADGE_MAX_SIZE", [72, 72]); // Max dimensions for account badges. +define("ACCOUNT_PUBLIC_LIST", true); // The public list of accounts. +define("ACCOUNT_LOG_ACTIONS", true); // Log user's actions (emote addition, etc.). + +// TWITCH +define("TWITCH_REGISTRATION_ENABLE", false); // Enable account registration via Twitch. +define("TWITCH_CLIENT_ID", "AAAAAAAAA"); // Client ID of your Twitch application. +define("TWITCH_SECRET_KEY", "BBBBBBBBB"); // Secret key of your Twitch application. +define("TWITCH_REDIRECT_URI", ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http") . "://$_SERVER[HTTP_HOST]/account/login/twitch.php"); // Redirect URI of your Twitch application. + +// CAPTCHA +define("CAPTCHA_ENABLE", true); // Enable built-in captcha. +define("CAPTCHA_SIZE", [580, 220]); // Captcha size. +define("CAPTCHA_FORCE_USERS", false); // Force authorized users to solve captcha. + +// FOR DEVELOPERS +define("CLIENT_REQUIRES_JSON", isset($_SERVER["HTTP_ACCEPT"]) && $_SERVER["HTTP_ACCEPT"] == "application/json"); \ No newline at end of file diff --git a/lib/emote.php b/lib/emote.php new file mode 100644 index 0000000..ce39c09 --- /dev/null +++ b/lib/emote.php @@ -0,0 +1,332 @@ +id = $arr["id"]; + $e->code = $arr["code"]; + $e->ext = $arr["ext"] ?? "webp"; + $e->uploaded_by = $arr["uploaded_by"]; + $e->created_at = strtotime($arr["created_at"] ?? 0); + $e->is_in_user_set = $arr["is_in_user_set"] ?? false; + $e->visibility = $arr["visibility"]; + $e->source = $arr["source"] ?? null; + $e->tags = $arr["tags"] ?? []; + + if (isset($arr["total_rating"], $arr["average_rating"])) { + $e->rating = [ + "total" => $arr["total_rating"], + "average" => $arr["average_rating"] + ]; + } else { + $e->rating = $arr["rating"] ?? null; + } + + return $e; + } + + public static function from_array_with_user(array $arr, PDO &$db): Emote + { + if ($arr["uploaded_by"]) { + $arr["uploaded_by"] = User::get_user_by_id($db, $arr["uploaded_by"]); + } + + return Emote::from_array($arr); + } + + function get_id() + { + return $this->id; + } + + function get_code() + { + return $this->code; + } + + function get_ext() + { + return $this->ext; + } + + function get_created_at() + { + return $this->created_at; + } + + function get_uploaded_by() + { + return $this->uploaded_by; + } + + function is_added_by_user() + { + return $this->is_in_user_set; + } + + function get_rating() + { + return $this->rating; + } + + function get_visibility() + { + return $this->visibility; + } + + function get_source() + { + return $this->source; + } + + function get_tags(): array + { + return $this->tags; + } +} + +class Emoteset +{ + public string $id; + public string $name; + public User|null $owner; + public array $emotes; + + public bool $is_default; + + public static function from_array(array $arr): Emoteset + { + $s = new Emoteset(); + + $s->id = $arr["id"]; + $s->name = $arr["name"]; + $s->owner = $arr["owner_id"]; + $s->emotes = $arr["emotes"] ?? []; + $s->is_default = $arr["is_default"] ?? false; + + return $s; + } + + public static function from_array_extended(array $arr, string $user_id, PDO &$db): Emoteset + { + if ($arr["owner_id"]) { + $arr["owner_id"] = User::get_user_by_id($db, $arr["owner_id"]); + } + + $arr["emotes"] = fetch_all_emotes_from_emoteset($db, $arr["id"], $user_id); + + return Emoteset::from_array($arr); + } + + public static function get_all_user_emotesets(PDO &$db, string $user_id): array + { + $stmt = $db->prepare("SELECT es.*, aes.is_default FROM emote_sets es + INNER JOIN acquired_emote_sets aes ON aes.emote_set_id = es.id + WHERE aes.user_id = ? + "); + $stmt->execute([$user_id]); + + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + $emote_sets = []; + + foreach ($rows as $row) { + array_push($emote_sets, Emoteset::from_array_extended($row, $user_id, $db)); + } + + return $emote_sets; + } +} + +function fetch_all_emotes_from_emoteset(PDO &$db, string $emote_set_id, string $user_id, int|null $limit = null): array +{ + // fetching emotes + $sql = "SELECT + e.id, e.created_at, e.visibility, + CASE + WHEN esc.code IS NOT NULL THEN esc.code + ELSE e.code + END AS code, + CASE + WHEN esc.code IS NOT NULL THEN e.code + ELSE NULL + END AS original_code, + CASE WHEN up.private_profile = FALSE OR up.id = ? THEN e.uploaded_by ELSE NULL END AS uploaded_by + FROM emotes e + LEFT JOIN user_preferences up ON up.id = e.uploaded_by + INNER JOIN emote_set_contents AS esc + ON esc.emote_set_id = ? + WHERE esc.emote_id = e.id"; + + if ($limit) { + $sql .= " LIMIT $limit"; + } + + $stmt = $db->prepare($sql); + $stmt->execute([$user_id, $emote_set_id]); + + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + $emotes = []; + + // fetching uploaders + foreach ($rows as $row) { + if ($row["uploaded_by"]) { + $row["uploaded_by"] = User::get_user_by_id($db, $row["uploaded_by"]); + } + + array_push($emotes, Emote::from_array($row)); + } + + return $emotes; +} + +function html_random_emote(PDO &$db) +{ + $stmt = $db->prepare("SELECT id, code FROM emotes WHERE visibility = 1 ORDER BY RAND() LIMIT 1"); + $stmt->execute(); + + if ($row = $stmt->fetch()) { + echo '' + ?> +
+ + +
+ prepare("SELECT e.id, e.code FROM emotes e + INNER JOIN emote_sets es ON es.is_featured = TRUE + INNER JOIN emote_set_contents esc ON es.id = esc.emote_set_id + WHERE e.visibility = 1 AND e.id = esc.emote_id ORDER BY esc.added_at DESC LIMIT 1"); + $stmt->execute(); + + if ($row = $stmt->fetch()) { + echo '' + ?> + + id}'>"; + + if ($e->is_added_by_user()) { + echo ''; + } + + // icon + echo '
'; + $scale = $emote_wall ? "3" : ((string) $scale); + echo "{$e->code}"; + echo '
'; + + // info + echo '
'; + + echo "

{$e->code}

"; + if ($e->get_uploaded_by()) { + echo "

{$e->uploaded_by->username}

"; + } + + echo '
'; + } +} + +function html_display_emoteset(array $emotesets) +{ + foreach ($emotesets as $es) { + echo ""; + + echo '
'; + echo "

$es->name

"; + echo '
'; + + echo '
'; + + foreach ($es->emotes as $e) { + echo "{$e->code}"; + } + + echo '
'; + + } +} + +function html_emotelist_mode() +{ + echo '' ?> + + + + + +
+ +
+
" method="GET"> + ">
+ + + + + +
+
+
+ 1) { + echo '' ?> + + id = $arr["{$prefix}badge_id"]; + + return $b; + } +} + +class Role +{ + public string $name; + public Badge|null $badge; + + public static function from_array(array $arr): Role|null + { + if (!isset($arr["role_name"])) { + return null; + } + + $r = new Role(); + + $r->name = $arr["role_name"]; + $r->badge = Badge::from_array($arr, "role"); + + return $r; + } +} + +class User +{ + public string $id; + public string $username; + public int $joined_at; + public int $last_active_at; + + public Badge|null $custom_badge; + + public Role|null $role; + + public bool $private_profile; + + public static function from_array(array $arr): User + { + $u = new User(); + + $u->id = $arr["id"]; + $u->username = $arr["username"]; + $u->joined_at = strtotime($arr["joined_at"] ?? "0"); + $u->last_active_at = strtotime($arr["last_active_at"] ?? "0"); + + $u->private_profile = $row["private_profile"] ?? false; + + $u->custom_badge = Badge::from_array($arr, "custom"); + + $u->role = Role::from_array($arr); + + return $u; + } + + public static function get_user_by_id(PDO &$db, string $user_id): User|null + { + $stmt = $db->prepare("SELECT + u.id, + u.username, + u.joined_at, + u.last_active_at, + + up.private_profile, + r.name AS role_name, + r.badge_id AS role_badge_id, + ub.badge_id AS custom_badge_id + FROM users u + INNER JOIN user_preferences up ON up.id = u.id + LEFT JOIN role_assigns ra ON ra.user_id = u.id + LEFT JOIN roles r ON r.id = ra.role_id + LEFT JOIN user_badges ub ON ub.user_id = u.id + WHERE u.id = ? + "); + $stmt->execute([$user_id]); + + $u = null; + + if ($uploader_row = $stmt->fetch()) { + $u = User::from_array($uploader_row); + } + + return $u; + } +} \ No newline at end of file diff --git a/lib/utils.php b/lib/utils.php new file mode 100644 index 0000000..87d96c6 --- /dev/null +++ b/lib/utils.php @@ -0,0 +1,68 @@ + 1 ? "s" : ""); + } else if ($days == 0 && $hours == 0) { + return "$minutes minute" . ($minutes > 1 ? "s" : ""); + } else if ($days == 0) { + return "$hours hour" . ($hours > 1 ? "s" : ""); + } else { + return "$days day" . ($days > 1 ? "s" : ""); + } +} + +function clamp(int $current, int $min, int $max): int +{ + return max($min, min($max, $current)); +} + +function in_range(float $value, float $min, float $max): bool +{ + return $min <= $value && $value <= $max; +} \ No newline at end of file diff --git a/lib/version.php b/lib/version.php new file mode 100644 index 0000000..d666783 --- /dev/null +++ b/lib/version.php @@ -0,0 +1,11 @@ +