-
-
Save rock88/187dd08d4cbbe160eff90b5f1aad8564 to your computer and use it in GitHub Desktop.
UserScript for Tampermonkey
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name MFB TG Stickers | |
// @namespace https://madfanboy.com | |
// @version 1.0.5 | |
// @description MFB TG Stickers | |
// @match https://madfanboy.com/forum/* | |
// @copyright 2024, madfanboy.com | |
// @updateURL https://gist.githubusercontent.com/rock88/187dd08d4cbbe160eff90b5f1aad8564/raw | |
// @downloadURL https://gist.githubusercontent.com/rock88/187dd08d4cbbe160eff90b5f1aad8564/raw | |
// @require https://code.jquery.com/jquery-latest.js | |
// @require https://cdn.jsdelivr.net/npm/js-cookie@3.0.1/dist/js.cookie.min.js | |
// @require https://cdn.jsdelivr.net/gh/nodeca/pako@master/dist/pako.min.js | |
// @require https://cdn.jsdelivr.net/gh/photopea/UPNG.js@master/UPNG.js | |
// @require https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js | |
// @require https://unpkg.com/@lottiefiles/lottie-player@latest/dist/tgs-player.js | |
// ==/UserScript== | |
$(document).ready(() => { | |
// Settings | |
const Settings = { | |
get searchText() { | |
return Cookies.get(this.keys.searchText) || 'Pepe' | |
}, | |
set searchText(value) { | |
Cookies.set(this.keys.searchText, value) | |
}, | |
get size() { | |
return Cookies.get(this.keys.size) || '128' | |
}, | |
set size(value) { | |
Cookies.set(this.keys.size, value) | |
}, | |
get provider() { | |
return Cookies.get(this.keys.provider) || 'telegram_static' | |
}, | |
set provider(value) { | |
Cookies.set(this.keys.provider, value) | |
}, | |
keys: { | |
searchText: 'tg-stickers-search-text', | |
size: 'tg-stickers-size', | |
provider: 'tg-stickers-provider', | |
}, | |
} | |
// ContentRenderer | |
class ContentRenderer { | |
static async render(type, content, height) { | |
switch (type) { | |
case 'images': | |
return await ContentResizer.resizeImage(content, height) | |
case 'webp': | |
return await ContentResizer.resizeRemote(content, height, 'resize_webp') | |
case 'video': | |
return await ContentResizer.resizeRemote(content, height, 'resize_video') | |
case 'lottie': | |
return await ContentResizer.resizeLottie(content, height) | |
default: | |
throw 'Unknown content type' | |
} | |
} | |
static makeDrawable(width, height, requiredHeight) { | |
const canvas = document.createElement('canvas') | |
const context = canvas.getContext('2d') | |
const size = { width: Math.round((width / height) * requiredHeight), height: Math.round(requiredHeight) } | |
canvas.width = size.width | |
canvas.height = size.height | |
return { canvas: canvas, context: context, size: size } | |
} | |
} | |
class ContentUtilities { | |
static base64(arrayBuffer) { | |
return btoa([].reduce.call(new Uint8Array(arrayBuffer), (p, c) => p + String.fromCharCode(c), '')) | |
} | |
static async upload_to_imgur(image) { | |
const form = new FormData() | |
form.append('image', image) | |
form.append('title', 'Title') | |
form.append('album', 'RjQbS5iU3FWKkpi') | |
const headers = { 'Authorization': 'Client-ID 764cd8809e98dfa' } | |
const response = await fetch('https://api.imgur.com/3/image', { method: 'POST', body: form, headers: headers }) | |
const json = await response.json() | |
if (json.data.link != undefined) { | |
return json.data.link | |
} | |
throw json.data.error | |
} | |
static async upload_to_imgbb(image) { | |
const form = new FormData() | |
form.append('image', image) | |
const response = await fetch('https://api.imgbb.com/1/upload?key=de037c19251b9fed9f4a0146bc197391', { method: 'POST', body: form }) | |
const json = await response.json() | |
if (json.data.url != undefined) { | |
return json.data.url | |
} | |
throw json.data.error | |
} | |
} | |
class ContentResizer { | |
static proxy = 'https://api.allorigins.win/raw?url=' | |
static api = 'https://mfbstickers.app.swift.cloud' | |
static async resizeImage(content, height) { | |
if (content[height]) { | |
return content[height] | |
} | |
return await ContentResizer.resizeRemote(content['512'], height, 'resize_image') | |
} | |
static async resizeRemote(content, height, method) { | |
const response = await fetch(`${this.api}/${method}`, { method: 'POST', body: JSON.stringify({ url: content, height: height }) }) | |
return await response.text() | |
} | |
static async loadLottie(url) { | |
const response = await fetch(this.proxy + encodeURIComponent(url)) | |
const blob = await response.blob() | |
try { | |
return JSON.parse(await blob.text()) | |
} catch { | |
return JSON.parse(pako.inflate(await blob.arrayBuffer(), { to: 'string' })) | |
} | |
} | |
static async resizeLottie(content, height) { | |
const json = await this.loadLottie(content) | |
const drawable = ContentRenderer.makeDrawable(json.w, json.h, height) | |
const config = { | |
renderer: 'canvas', | |
animationData: json, | |
rendererSettings: { context: drawable.context, clearCanvas: true } | |
} | |
const animation = lottie.loadAnimation(config) | |
const renderer = new APNGRenderer(drawable.size, (1 / animation.frameRate) * 1000) | |
for (let i = 0; i < animation.totalFrames; i++) { | |
animation.goToAndStop(i, true) | |
renderer.addFrame(drawable) | |
} | |
return await ContentUtilities.upload_to_imgbb(renderer.data()) | |
} | |
} | |
class APNGRenderer { | |
constructor(size, delay) { | |
this.size = size | |
this.delay = Math.max(delay, 12) | |
this.delays = [this.delay] | |
this.buffers = [] | |
} | |
addFrame(drawable) { | |
this.buffers.push(drawable.context.getImageData(0, 0, this.size.width, this.size.height).data.buffer) | |
this.delays.push(this.delay) | |
} | |
data() { | |
const png = UPNG.encode(this.buffers, this.size.width, this.size.height, 0, this.delays) | |
return ContentUtilities.base64(png) | |
} | |
} | |
// HTMLHelper | |
class HTMLHelper { | |
static overlaySize = 250 | |
static renderStickers(app, container, result) { | |
const previews = { | |
'image': this.imagePreview, | |
'video': this.videoPreview, | |
'text': this.textPreview | |
} | |
result.packs.forEach(pack => { | |
pack.stickers.forEach((sticker, index) => { | |
container.append(previews[result.preview](app, result, `${pack.title} ${index + 1}`, sticker)) | |
}) | |
}) | |
} | |
static imagePreview(app, result, title, sticker) { | |
const element = `<img loading="lazy" src="${sticker.preview}" width="56" height="56" style="object-fit: cover;" alt="${title}" title="${title}" />` | |
return HTMLHelper.embedPreview(element, app, result, sticker) | |
} | |
static videoPreview(app, result, title, sticker) { | |
const element = `<video src="${sticker.preview}" width="56" height="56" style="object-fit: cover; margin: 3px; vertical-align: middle;" alt="${title}" title="${title}" />` | |
return HTMLHelper.embedPreview(element, app, result, sticker) | |
} | |
static textPreview(app, result, title, sticker) { | |
const text = `<p style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px; text-align: center;">${title.slice(0, 8)} ${sticker.preview}</p>` | |
const element = `<div style="position: relative; display: inline-block;" title="${title}"><img src="" style="width: 56px; height: 56px;" />${text}</div>` | |
return HTMLHelper.embedPreview(element, app, result, sticker) | |
} | |
static embedPreview(element, app, result, sticker) { | |
const link = $(`<a href="#">${element}</a>`) | |
link.on('mouseover', () => { HTMLHelper.showOverlay(result, sticker) }) | |
link.on('mouseout', () => { HTMLHelper.hideOverlay() }) | |
link.click(() => { | |
app.click(result, sticker) | |
return false | |
}) | |
return link | |
} | |
static showOverlay(result, sticker) { | |
if (HTMLHelper.timeout) { | |
clearTimeout(HTMLHelper.timeout) | |
} | |
const size = `${HTMLHelper.overlaySize}px` | |
const overlay = $('#tg-stickers-overlay') | |
overlay.empty() | |
overlay.show() | |
switch (result.content) { | |
case 'images': | |
return overlay.append(`<img src="${sticker.content['512']}" width="${size}" height="${size}"><img>`) | |
case 'webp': | |
return overlay.append(`<img src="${sticker.content}" width="${size}" height="${size}"><img>`) | |
case 'video': | |
return overlay.append(`<video src="${sticker.content}" width="${size}" height="${size}" style="object-fit: cover; vertical-align: middle;" autoplay loop></video>`) | |
case 'lottie': | |
const player = sticker.content.endsWith('.tgs') ? 'tgs-player' : 'lottie-player' | |
return overlay.append(`<${player} src="${sticker.content}" background="transparent" speed="1" style="width: ${size}; height: ${size};" loop autoplay></lottie-player>`) | |
default: | |
break | |
} | |
} | |
static hideOverlay() { | |
const overlay = $('#tg-stickers-overlay') | |
overlay.empty() | |
HTMLHelper.timeout = setTimeout(() => { overlay.hide() }, 300) | |
} | |
static showActivityIndicator(text) { | |
if (!$('#tg-stickers-activity').length) { | |
$('#tg-stickers').append($('<div id="tg-stickers-activity" class="panel" style="width: 200px; position: absolute; top: 60%; left: 50%; transform: translate(-50%, -50%); text-align: center; padding: 4px;"></div>')) | |
} | |
$('#tg-stickers-activity').text(text) | |
} | |
static hideActivityIndicator() { | |
$('#tg-stickers-activity').remove() | |
} | |
static showError(text) { | |
HTMLHelper.hideActivityIndicator() | |
if (!$('#tg-stickers-error').length) { | |
$('<div id="tg-stickers-error" class="rules"></div>').insertBefore($("#tg-stickers")) | |
} | |
$('#tg-stickers-error').text(text) | |
if (HTMLHelper.timeout) { | |
clearTimeout(HTMLHelper.timeout) | |
} | |
HTMLHelper.timeout = setTimeout(() => { HTMLHelper.hideError() }, 3500) | |
} | |
static hideError() { | |
$('#tg-stickers-error').remove() | |
} | |
static insertImage(src) { | |
insert_text(`[img]${src}[/img]`, true) | |
} | |
} | |
// App | |
class App { | |
timeout = null | |
elements = { | |
search: "#tg-stickers-search", | |
size: "#tg-stickers-size", | |
style: "#tg-stickers-style", | |
container: "#tg-stickers-container" | |
} | |
api = 'https://mfbstickers.app.swift.cloud' | |
processing = false | |
run() { | |
if (!$('.smiley-box').length) { | |
return console.log('no smiley-box, exit...') | |
} | |
$('body').append(`<div id="tg-stickers-overlay" style="position: fixed; width: ${HTMLHelper.overlaySize}px; height: ${HTMLHelper.overlaySize}px; z-index: 20; top: 0; background-color: rgba(0,0,0,0.5); display: none;"></div>`) | |
$('<br><hr><br><div id="tg-stickers" class="smiley-box2"></div>').insertAfter($(".smiley-box")) | |
const stickers = $("#tg-stickers") | |
stickers.append("<strong>Стикеры</strong><br>") | |
stickers.append('<input type="text" id="tg-stickers-search" placeholder="Поиск" size="45" maxlength="124" tabindex="2" class="inputbox">') | |
stickers.append(' <select id="tg-stickers-size" class="bbcode-size" title="Размер"><option value="48">Очень маленький (48)</option><option value="64">Маленький (64)</option><option value="128">Нормальный (128)</option><option value="256">Большой (256)</option><option value="512">Огромный (512)</option></select>') | |
stickers.append(` <select id="tg-stickers-style" class="bbcode-size" title="Тип"></select>`) | |
stickers.append('<p> </p><div id="tg-stickers-container" class="smiley-box"></div>') | |
$(this.elements.search).val(Settings.searchText) | |
$(this.elements.search).on("input", () => { | |
Settings.searchText = $(this.elements.search).val() | |
this.search($(this.elements.search).val()) | |
}) | |
$(this.elements.size).val(Settings.size) | |
$(this.elements.size).on("change", () => { | |
Settings.size = $(this.elements.size).val() | |
}) | |
$(this.elements.style).val(Settings.provider) | |
$(this.elements.style).on("change", () => { | |
Settings.provider = $(this.elements.style).val() | |
this.search(Settings.searchText) | |
}) | |
this.loadProviders() | |
.then(() => { | |
this.search(Settings.searchText) | |
}) | |
} | |
async loadProviders() { | |
const response = await fetch(this.api + '/providers') | |
this.providers = await response.json() | |
$(this.elements.style).html(this.providers.map( | |
p => p.styles.map(t => `<option value="${p.id}_${t.id}">${p.title} (${t.title})</option>`).join('') | |
).join('')) | |
$(this.elements.style).val(Settings.provider) | |
} | |
search(input) { | |
if (input.length <= 2) { | |
return | |
} | |
const array = Settings.provider.split("_") | |
const url = this.api + `/search?id=${array[0]}&style=${array[1]}&query=${input.replaceAll(' ', '+')}` | |
if (this.timeout) { | |
clearTimeout(this.timeout) | |
} | |
this.timeout = setTimeout(async () => { | |
HTMLHelper.showActivityIndicator('Loading...') | |
try { | |
const response = await fetch(url) | |
this.render(await response.json()) | |
HTMLHelper.hideActivityIndicator() | |
} catch (error) { | |
HTMLHelper.showError(error) | |
} | |
}, 500) | |
} | |
render(result) { | |
const container = $(this.elements.container) | |
container.empty() | |
if (result.packs.length == 0) { | |
HTMLHelper.hideOverlay() | |
return container.append('<p>Ничего не найдено...</p>') | |
} | |
HTMLHelper.renderStickers(this, container, result) | |
} | |
async click(result, sticker) { | |
if (this.processing) { | |
return | |
} | |
this.processing = true | |
HTMLHelper.showActivityIndicator('Processing...') | |
try { | |
const url = await ContentRenderer.render(result.content, sticker.content, Settings.size) | |
HTMLHelper.insertImage(url) | |
HTMLHelper.hideActivityIndicator() | |
} catch (error) { | |
HTMLHelper.showError(error) | |
} | |
this.processing = false | |
} | |
} | |
const app = new App() | |
app.run() | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
хороший