From 0b76e5d6a8d33e64e67438c10cd3d12e622d042b Mon Sep 17 00:00:00 2001 From: ilotterytea Date: Sun, 26 Oct 2025 18:07:48 +0500 Subject: feat: add sounds (wip doe) --- database.sql | 8 +++ lib/config.php | 3 + sounds/add.php | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ static/style.css | 51 ++++++++++++++- 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 sounds/add.php diff --git a/database.sql b/database.sql index 42a6b50..ee3d285 100644 --- a/database.sql +++ b/database.sql @@ -20,4 +20,12 @@ CREATE TABLE IF NOT EXISTS sounds ( code TEXT NOT NULL, uploaded_at TIMESTAMP NOT NULL DEFAULT UTC_TIMESTAMP, uploaded_by BIGINT REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS added_sounds ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + sound_id BIGINT NOT NULL REFERENCES sounds(id), + emote_id TEXT NOT NULL, + added_at TIMESTAMP NOT NULL DEFAULT UTC_TIMESTAMP, + UNIQUE (sound_id, emote_id) ); \ No newline at end of file diff --git a/lib/config.php b/lib/config.php index bdc399b..e57714e 100644 --- a/lib/config.php +++ b/lib/config.php @@ -13,4 +13,7 @@ define('INSTANCE_NAME', $c['instance']['name'] ?? $_SERVER['HTTP_HOST']); define('SOUND_DIRECTORY', $c['sound']['directory'] ?? "{$_SERVER['DOCUMENT_ROOT']}/static/userdata/sounds"); define('SOUND_DIRECTORY_PREFIX', $c['sound']['directory_prefix'] ?? "/static/userdata/sounds"); +define('EMOTE_FETCH_BETTERTTV', boolval($c['emote']['fetch_betterttv'] ?? '1') ?? true); +define('EMOTE_FETCH_7TV', boolval($c['emote']['fetch_7tv'] ?? '1') ?? true); + define('IS_JSON_REQUEST', isset($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')); \ No newline at end of file diff --git a/sounds/add.php b/sounds/add.php new file mode 100644 index 0000000..9a008de --- /dev/null +++ b/sounds/add.php @@ -0,0 +1,190 @@ +prepare('SELECT id, code + FROM sounds + WHERE id = ? + '); + $stmt->execute([$_GET['id']]); + $sound = $stmt->fetch(PDO::FETCH_ASSOC) ?: null; + + // TODO: check if user has twitch connection + $twitch = $_SESSION['user']['connections'][0]; + + if ($sound) { + $emotes = []; + + if (EMOTE_FETCH_BETTERTTV) { + // fetching channel emotes + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://api.betterttv.net/3/cached/users/twitch/" . $twitch['user_alias_id']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + curl_close($ch); + + // parsing channel emotes + $response = json_decode($response, true); + $bttvemotes = []; + foreach ($response['channelEmotes'] as $e) { + array_push($bttvemotes, [ + 'code' => $e['code'], + 'eid' => 'betterttv:' . $e['id'], + 'url' => 'https://cdn.betterttv.net/emote/' . $e['id'] . '/2x.webp', + 'uploader' => $twitch['user_alias_name'] + ]); + } + foreach ($response['sharedEmotes'] as $e) { + array_push($bttvemotes, [ + 'code' => $e['code'], + 'eid' => 'betterttv:' . $e['id'], + 'url' => 'https://cdn.betterttv.net/emote/' . $e['id'] . '/2x.webp', + 'uploader' => $e['user']['displayName'] + ]); + } + + // fetching global emotes + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://api.betterttv.net/3/cached/emotes/global"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + curl_close($ch); + + // parsing global emotes + $response = json_decode($response, true); + foreach ($response as $e) { + array_push($bttvemotes, [ + 'code' => $e['code'], + 'eid' => 'betterttv:' . $e['id'], + 'url' => 'https://cdn.betterttv.net/emote/' . $e['id'] . '/2x.webp' + ]); + } + + $emotes['BetterTTV'] = $bttvemotes; + } + + if (EMOTE_FETCH_7TV) { + // fetching channel emotes + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://7tv.io/v3/users/twitch/" . $twitch['user_alias_id']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + curl_close($ch); + + // parsing channel emotes + $response = json_decode($response, true); + $stvemotes = []; + foreach ($response['emote_set']['emotes'] as $e) { + array_push($stvemotes, [ + 'code' => $e['name'], + 'eid' => '7tv:' . $e['id'], + 'url' => 'https://cdn.7tv.app/emote/' . $e['id'] . '/2x.webp', + 'uploader' => $e['data']['owner']['display_name'] + ]); + } + + // fetching global emotes + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://7tv.io/v3/emote-sets/global"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + curl_close($ch); + + // parsing global emotes + $response = json_decode($response, true); + foreach ($response['emotes'] as $e) { + array_push($stvemotes, [ + 'code' => $e['name'], + 'eid' => '7tv:' . $e['id'], + 'url' => 'https://cdn.7tv.app/emote/' . $e['id'] . '/2x.webp', + 'uploader' => $e['data']['owner']['display_name'] + ]); + } + + $emotes['7TV'] = $stvemotes; + } + } +} else { + $stmt = $db->prepare('SELECT * FROM sounds ORDER BY uploaded_at DESC'); + $stmt->execute(); + $sounds = $stmt->fetchAll(PDO::FETCH_ASSOC); +} +?> + + + + + Add sound - <?= INSTANCE_NAME ?> + + + + + + +
+
+
+
+

Adding sound -

+
+
+ +
+
+
+
+

Preview

+
+
+

wip

+
+
+
+
+ $pe): ?> +
+
+

+
+
+ + +
+
+ <?= $e['code'] ?> +
+

+ +

by

+ +
+
+ + +

No emotes.

+ +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/static/style.css b/static/style.css index 6a62cb5..864a4fb 100644 --- a/static/style.css +++ b/static/style.css @@ -1,4 +1,5 @@ :root { + --primary-0: #81b180; --primary-1: #97ca96; --primary-2: #c6eec5; --primary-3: #d5e9d5; @@ -77,6 +78,18 @@ header .brand>a { border-top-right-radius: 4px; } +.box>.content:has(.box) { + background: var(--primary-0); +} + +.box:has(.box)>.tab { + margin-bottom: unset; +} + +a>.box:hover { + background: var(--primary-2); +} + .sound-list { display: flex; flex-direction: row; @@ -84,7 +97,8 @@ header .brand>a { gap: 16px; } -.sound-item { +.sound-item, +.emote-item { display: flex; flex-direction: column; justify-content: center; @@ -95,6 +109,37 @@ header .brand>a { font-size: 12px; } +a:has(.emote-item) { + text-decoration: none; + color: unset; +} + +.emote-item { + width: 92px; + height: 92px; +} + +.emote-item>.code, +.emote-item>.author { + max-width: 5em; + min-height: 1em; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.emote-item>.icon { + flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; +} + +.emote-item>.icon>img { + max-width: 64px; + max-height: 64px; +} + .row { display: flex; flex-direction: row; @@ -105,6 +150,10 @@ header .brand>a { flex-direction: column; } +.wrap { + flex-wrap: wrap; +} + .justify-center { justify-content: center; } -- cgit v1.2.3