diff options
| author | ilotterytea <iltsu@alright.party> | 2025-10-26 18:07:48 +0500 |
|---|---|---|
| committer | ilotterytea <iltsu@alright.party> | 2025-10-26 18:07:48 +0500 |
| commit | 0b76e5d6a8d33e64e67438c10cd3d12e622d042b (patch) | |
| tree | 40e208d358eb9201341d09ef397e77a1d44aa48c | |
| parent | a8a338599b3d891ee72a48a5f01f25c1ded31f21 (diff) | |
| -rw-r--r-- | database.sql | 8 | ||||
| -rw-r--r-- | lib/config.php | 3 | ||||
| -rw-r--r-- | sounds/add.php | 190 | ||||
| -rw-r--r-- | static/style.css | 51 |
4 files changed, 251 insertions, 1 deletions
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 @@ +<?php +include_once $_SERVER['DOCUMENT_ROOT'] . '/lib/config.php'; +include_once $_SERVER['DOCUMENT_ROOT'] . '/lib/partials.php'; +include_once $_SERVER['DOCUMENT_ROOT'] . '/lib/users.php'; + +authenticate_user(); + +$db = new PDO(DB_URL, DB_USER, DB_PASS); + +if (isset($_GET['id']) && !empty(trim($_GET['id']))) { + $stmt = $db->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); +} +?> +<!DOCTYPE html> +<html> + +<head> + <title>Add sound - <?= INSTANCE_NAME ?></title> + <link rel="stylesheet" href="/static/style.css"> + <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> +</head> + +<body> + <?php html_header() ?> + <main class="row gap-8"> + <section class="column gap-8"> + <div class="box"> + <div class="tab"> + <p>Adding sound - <?= $sound['code'] ?></p> + </div> + <div class="content column justify-center align-center gap-16"> + <audio controls> + <source src="<?= sprintf("%s/%d.ogg", SOUND_DIRECTORY_PREFIX, $sound['id']) ?>" + type="audio/ogg"> + </audio> + </div> + </div> + <div class="box" id="chat-preview"> + <div class="tab"> + <p>Preview</p> + </div> + <div class="content chat-preview"> + <p>wip</p> + </div> + </div> + </section> + <section class="column gap-8"> + <?php foreach ($emotes as $pname => $pe): ?> + <div class="box"> + <div class="tab"> + <p><?= $pname ?></p> + </div> + <div class="content row wrap gap-8"> + <?php foreach ($pe as $e): ?> + <a href="/sounds/add.php?id=<?= $sound['id'] ?>&eid=<?= $e['eid'] ?>"> + <div class="box emote-item"> + <div class="icon"> + <img src="<?= $e['url'] ?>" alt="<?= $e['code'] ?>" loading="lazy"> + </div> + <p class="code" title="<?= $e['code'] ?>"><?= $e['code'] ?></p> + <?php if (isset($e['uploader'])): ?> + <p class="author" title="by <?= $e['uploader'] ?>">by <?= $e['uploader'] ?></p> + <?php endif; ?> + </div> + </a> + <?php endforeach; ?> + <?php if (empty($pe)): ?> + <p><i>No emotes.</i></p> + <?php endif; ?> + </div> + </div> + <?php endforeach; ?> + </section> + </main> +</body> + +<script> + +</script> + +</html>
\ 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; } |
