summaryrefslogtreecommitdiff
path: root/emotes
diff options
context:
space:
mode:
Diffstat (limited to 'emotes')
-rw-r--r--emotes/reupload.php331
1 files changed, 331 insertions, 0 deletions
diff --git a/emotes/reupload.php b/emotes/reupload.php
new file mode 100644
index 0000000..b6598f9
--- /dev/null
+++ b/emotes/reupload.php
@@ -0,0 +1,331 @@
+<?php
+include "{$_SERVER['DOCUMENT_ROOT']}/lib/accounts.php";
+include_once "{$_SERVER['DOCUMENT_ROOT']}/lib/config.php";
+include_once "{$_SERVER['DOCUMENT_ROOT']}/lib/alert.php";
+include_once "{$_SERVER['DOCUMENT_ROOT']}/lib/partials.php";
+
+if (!CONFIG['reupload']['enable']) {
+ generate_alert("/404.php", "Emote reupload is disabled", 403);
+ exit;
+}
+
+authorize_user();
+?>
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Reupload emotes - <?= CONFIG['instance']['name'] ?></title>
+ <link rel="stylesheet" href="/static/style.css">
+ <link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon">
+</head>
+
+<body>
+ <div class="container">
+ <div class="wrapper">
+ <?php html_navigation_bar() ?>
+ <?php display_alert() ?>
+
+ <noscript>JavaScript is required.</noscript>
+
+ <section class="content row">
+ <section class="column small-gap" style="width:20em; max-width: 20em;">
+ <section class="box">
+ <div class="box navtab">Reupload Emote</div>
+ <form class="box content" id="reupload-form">
+ <table class="vertical left">
+ <tr>
+ <th>URL:</th>
+ <td><input type="text" name="url" required
+ placeholder="https://7tv.app/emotes/ABCDEF"></td>
+ </tr>
+ </table>
+ <button type="submit">Re-upload</button>
+ </form>
+ </section>
+
+ <section class="box">
+ <p>
+ This runs entirely on the client.
+ Confirm platform availability before starting.
+ <?php if (!CONFIG['emote']['urlupload']): ?>
+ <u>The process uses significant memory because images are loaded into RAM; large batches
+ can exceed several gigabytes.</u>
+ <?php endif; ?>
+ </p>
+ <p>Supported platforms: <b>7TV</b>, <b>BetterTTV</b></p>
+ </section>
+
+ <section class="box" id="actions">
+ </section>
+ </section>
+ <section class="column grow">
+ <section class="box">
+ <div class="box navtab">
+ <p>Reuploaded emotes</p>
+ </div>
+ <div class="box content items flex" id="reuploaded-emotes">
+ </div>
+ </section>
+ </section>
+ </section>
+ </div>
+ </div>
+</body>
+
+<script>
+ async function uploadEmote(r) {
+ logAction(`Uploading ${r.name}...`);
+ const form = new FormData();
+
+ <?php if (CONFIG['emote']['urlupload']): ?>
+ form.append("file", r.url);
+ <?php else: ?>
+ // fetching image
+ const imageResponse = await fetch(r.url);
+ if (!imageResponse.ok) {
+ throw new Error(`Failed to fetch image: ${r.url} (${imageResponse})`);
+ }
+ let blob = await imageResponse.blob();
+ let file = new File([blob], r.filename, { type: blob.type });
+ form.append("file", file);
+ <?php endif; ?>
+
+ form.append("visibility", "1");
+ form.append("notes", r.notes);
+ form.append("source", r.source);
+ form.append("tos", "1");
+ form.append("code", r.name);
+
+ return await fetch("/emotes/upload.php", {
+ body: form,
+ method: "POST",
+ headers: {
+ Accept: "application/json"
+ }
+ })
+ .then((r) => r.json())
+ .then((r) => {
+ <?php if (!CONFIG['emote']['urlupload']): ?>
+ blob = null;
+ file = null;
+ <?php endif; ?>
+ return r;
+ });
+ }
+
+ function processEmote(provider, j) {
+ const id = j.id;
+
+ const output = {
+ url: null,
+ filename: null,
+ name: null,
+ source: null,
+ notes: null
+ };
+
+ if (provider == "7tv") {
+ j.host.files.forEach((x) => {
+ if (x.name == "4x.gif") {
+ output.url = `https://cdn.7tv.app/emote/${id}/4x.gif`;
+ output.filename = x.name;
+ } else if (x.name == "4x.png") {
+ output.url = `https://cdn.7tv.app/emote/${id}/4x.png`;
+ output.filename = x.name;
+ }
+ });
+ output.name = j.name;
+ output.source = `https://7tv.app/emotes/${id}`;
+ output.notes = `Reuploaded from ${output.source} - ${j.name}`;
+ if (j.owner) {
+ output.notes += ` by ${j.owner.username}`;
+ }
+ } else if (provider == "betterttv") {
+ output.name = j.code;
+ output.source = `https://betterttv.com/emotes/${id}`;
+ output.notes = `Reuploaded from ${output.source} - ${j.code}`;
+ if (j.user) {
+ output.notes += ` by ${j.user.name}`;
+ }
+ output.url = `https://cdn.betterttv.net/emote/${id}/3x.${j.imageType}`;
+ output.filename = `3x.${j.imageType}`;
+ }
+
+ return output;
+ }
+
+ function getEmotesByID(provider, host, id) {
+ let url = null;
+ switch (provider) {
+ case "7tv":
+ url = `https://7tv.io/v3/emotes/${id}`;
+ break;
+ case "betterttv":
+ url = `https://api.betterttv.net/3/emotes/${id}`;
+ break;
+ default:
+ break;
+ }
+
+ return fetch(url)
+ .then((r) => {
+ if (!r.ok) {
+ throw new Error("Emotes not found!");
+ }
+ return r.json();
+ })
+ .then((j) => {
+ logAction("Parsing emotes...");
+ return [processEmote(provider, j)];
+ });
+ }
+
+ function getEmotesByUser(provider, host, id) {
+ let url = null;
+ switch (provider) {
+ case "7tv":
+ url = `https://7tv.io/v3/users/${id}`;
+ break;
+ case "betterttv":
+ url = `https://api.betterttv.net/3/users/${id}`;
+ break;
+ default:
+ break;
+ }
+
+ return fetch(url)
+ .then((r) => {
+ if (!r.ok) {
+ throw new Error("Emotes not found!");
+ }
+ return r.json();
+ })
+ .then((j) => {
+ logAction("Parsing emotes...");
+ const emotes = [];
+ if (provider == "7tv") {
+ j.connections.forEach((c) => {
+ c.emote_set.emotes.forEach((x) => {
+ emotes.push(processEmote(provider, x.data));
+ });
+ });
+ }
+ else if (provider == "betterttv") {
+ [...j.sharedEmotes, ...j.channelEmotes].forEach((x) => {
+ emotes.push(processEmote(provider, x));
+ });
+ }
+ return emotes;
+ });
+ }
+
+ function displayEmote(data) {
+ const root = document.getElementById("reuploaded-emotes");
+ const emote = document.createElement("a");
+ emote.classList.add("box", "emote", "column", "justify-center", "items-center");
+ emote.href = `/emotes/?id=${data.id}`;
+
+ const imageWrapper = document.createElement("div");
+ imageWrapper.classList.add("flex", "justify-center", "items-center", "grow", "emote-icon");
+ emote.append(imageWrapper);
+
+ const image = document.createElement("img");
+ image.src = `/static/userdata/emotes/${data.id}/2x.webp`;
+ image.alt = data.code;
+ image.loading = "lazy";
+ imageWrapper.append(image);
+
+ const descWrapper = document.createElement("div");
+ descWrapper.classList.add("flex", "column", "justify-bottom", "items-center", "emote-desc");
+ emote.append(descWrapper);
+
+ const name = document.createElement("h1");
+ name.textContent = data.code;
+ name.title = data.code;
+ descWrapper.append(name);
+
+ const author = document.createElement("p");
+ author.textContent = data.uploaded_by.username;
+ descWrapper.append(author);
+
+ root.append(emote);
+ }
+
+ function logAction(text) {
+ const root = document.getElementById("actions");
+
+ const action = document.createElement("div");
+ action.classList.add("small-gap", "row");
+
+ const content = document.createElement("p");
+ content.innerHTML = text;
+ action.append(content);
+
+ if (root.lastElementChild) {
+ root.removeChild(root.lastElementChild);
+ }
+ root.prepend(action);
+ }
+
+ window.onload = () => {
+ const form = document.getElementById("reupload-form");
+ let formBlocked = false;
+
+ form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ if (formBlocked) {
+ alert('Please wait until the current work is completed!');
+ return;
+ }
+
+ formBlocked = true;
+
+ const f = new FormData(form);
+ form.reset();
+
+ const url = new URL(f.get("url"));
+ let pathname = url.pathname;
+ if (pathname[0] == '/') {
+ pathname = pathname.substring(1);
+ }
+ const pathParts = pathname.split("/");
+
+ let emoteCount = 0;
+ let uploadedCount = 0;
+
+ switch (url.host) {
+ case "betterttv.com":
+ case "7tv.app":
+ const p = url.host.split(".")[0];
+ let func = null;
+ if (pathParts[0] == 'emotes') {
+ logAction(`Retrieving emote ID ${pathParts[1]} from ${p}...`);
+ func = () => getEmotesByID(p, url.origin, pathParts[1]);
+ } else if (pathParts[0] == 'users') {
+ logAction(`Retrieving user ID ${pathParts[1]} from ${p}...`);
+ func = () => getEmotesByUser(p, url.origin, pathParts[1]);
+ }
+
+ func().then((r) => {
+ emoteCount = r.length;
+ r.forEach((x) => {
+ uploadEmote(x).then((x) => {
+ uploadedCount++;
+ displayEmote(x.data);
+ logAction(`Uploaded ${x.data.code} emote! (${uploadedCount}/${emoteCount})`);
+ if (uploadedCount >= emoteCount) {
+ formBlocked = false;
+ }
+ });
+ });
+ });
+ break;
+ default:
+ break;
+ }
+ });
+ };
+</script>
+
+</html> \ No newline at end of file