Skip to content

Instantly share code, notes, and snippets.

@rock88
Last active February 22, 2024 19:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rock88/187dd08d4cbbe160eff90b5f1aad8564 to your computer and use it in GitHub Desktop.
Save rock88/187dd08d4cbbe160eff90b5f1aad8564 to your computer and use it in GitHub Desktop.
UserScript for Tampermonkey
// ==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()
})
@bravointheua
Copy link

хороший

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment