Skip to content

Instantly share code, notes, and snippets.

Created February 24, 2018 23:50
Show Gist options
  • Save blueset/2afafd1f782bbb137f20daafa33d4e4e to your computer and use it in GitHub Desktop.
Save blueset/2afafd1f782bbb137f20daafa33d4e4e to your computer and use it in GitHub Desktop.
Big Text Bot v2



  • python-telegram-bot
  • node.js
  • libwebp
  • Full Noto fonts
  • Sqlite support of Python 3
  • puppeteer
  • (Headless) Chrome
  • Lighttpd

  1. Setup all dependencies
  2. Launch Chrome in daemon with chrome --disable-background-networking --disable-background-timer-throttling --disable-browser-side-navigation --disable-default-apps --disable-extensions --disable-hang-monitor --disable-prompt-on-repost --disable-sync --disable-translate --metrics-recording-only --no-first-run --remote-debugging-port=34766 --headless --disable-gpu --hide-scrollbars --mute-audio
  3. Setup lighttpd or any other web service to serve the index.html.
  4. Setup a Telegram Bot, set the token in the script
  5. Setup a channel, add the bot to the channel, and set error dump & sticker dump in the script
  6. Run the script.
from PIL import Image, ImageDraw, ImageFont
from uuid import uuid4
import subprocess
import time
import sqlite3
import telegram
import telegram.ext
import html
import os
sticker_dump = 1
error_dump = 2
def draw(text):
# print("trying to draw", text)
fn = str(int(time.time()))['node', 'gen.js', text, fn])['cwebp', fn + ".png", "-o", fn + ".webp"])
os.remove(fn + ".png")
# print("finished", fn + ".webp")
return fn + ".webp"
def get_db(text):
con = sqlite3.connect("./data.sqlite")
cur = con.cursor()
d = cur.execute("select * from cache where `text` = ?", (text, )).fetchone()
if d is None:
return None
return d[1]
def set_db(text, val):
if get_db(text) is None:
con = sqlite3.connect("./data.sqlite")
#cur = con.cursor()
con.execute("insert into cache (`text`, `id`) values (?, ?)", (text, val))
def get_sticker_id(bot, text, dest=sticker_dump):
# print("requesting", text)
db = get_db(text)
# print("db", db)
if db:
# print("retriving from db", db)
return db
p = draw(text)
m = bot.send_sticker(dest, open(p, 'rb'))
# print("msg", m)
set_db(text, m.sticker.file_id)
# print("just sent", m.sticker.file_id)
if dest != sticker_dump:
return m.sticker.file_id, True
return m.sticker.file_id
def start(bot, update):
update.message.reply_text("""Text Sticker.
I'm an inline bot. I make stickers with texts. Quote the text directly, and I will generate stickers for you.
<b>How to use</b>
1. @ me
2. Type your words
3. Wait until the circle stop turning
4. Tap/click the first sticker (or where it supposed to be)
If there's no pop-up showing, try again a few seconds later, or check if it's in the public anon dump. If it's not anywhere, I'm probably down.
You have to make line breaks manually, or everything will be squeezed in one line.
Start a message with <code>serif`</code> to generate stickers in serif fonts.
Major languages are supported by Noto Fonts by Google. Emoji is supported by Noto Color Emoji (before Android O). <em>(Pudding faces Rocks!)</em>
All unique stickers are saved in the <a href="">Public Anonymous Dump of @big_text_bot</a> as a essential component for this bot to work.
""", disable_web_page_preview=True,
parse_mode="HTML", reply_markup=telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton("Try me out", switch_inline_query_current_chat="Hello,\nWorld!")], [telegram.InlineKeyboardButton("Share with friends", switch_inline_query="Hello,\nWorld!")]]))
def inlinequery(bot, update):
# print(update)
query = update.inline_query.query
query = html.unescape(query.replace('<br/>', '\n'))
fid = get_sticker_id(bot, query) if query else ''
# print(fid)
results = [telegram.InlineQueryResultCachedSticker(str(uuid4()), sticker_file_id=fid)] if fid else []
# print("results", results)
u = update.inline_query.answer(results)
# print(update, u)
except Exception as e:
# print(e)
error(bot, update, repr(e) + repr(u))
def gen(bot, update, args):
# update.message.reply_text("generating...")
query = " ".join(args)
# print("generating", query)
fid = get_sticker_id(bot, query,
if type(fid) == tuple:
# print("sticker id", fid)
def error(bot, update, error):
if str(error) == "Bad Request: QUERY_ID_INVALID" or str(error) == "Timed out":
print("update: %s\nerror: %s" % (update, error))
bot.send_message(error_dump, 'Update <pre>%s</pre> caused error <pre>%s</pre>' % (html.escape(str(update)), html.escape(str(error))), parse_mode="HTML")
def main():
u = telegram.ext.Updater(token)
d = u.dispatcher
d.add_handler(telegram.ext.CommandHandler('start', start))
d.add_handler(telegram.ext.CommandHandler('help', start))
d.add_handler(telegram.ext.CommandHandler('gen', gen, pass_args=True))
# u.start_polling()
u.start_webhook(listen='', port=1849, url_path='telegram/' + token)'' + token)
'use strict';
console.log('starting', process.argv);
const puppeteer = require('puppeteer');
const request = require("request-promise");
const fs = require('fs');
(async () => {
// Pupetter API
const info = await request({ uri: "", json: true});
const browser = await puppeteer.connect(
{ browserWSEndpoint: info.webSocketDebuggerUrl}
const page = await browser.newPage();
// Webpage host
await page.goto('');
var argv = process.argv[2];
if (argv.length < 10)
page.setViewport({ width: 460, height: 460 });
page.setViewport({ width: 1000, height: 1000 });
await page.evaluate((text) => {
if (text.startsWith('serif`')) {
text = text.substr(6);
} else {
}, argv);
const fname = process.argv[3];
await page.screenshot({
path: fname + ".png",
omitBackground: true,
fullPage: true
await page.close();
<!DOCTYPE html>
<meta name="robots" content="noindex">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Big text bot</title>
<style id="jsbin-css">
html, body {
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
background-color: #666;
background-color: transparent;
.wrap {
position: relative;
.sans {
font-family: "Noto Symbol", "Roboto", "Noto Sans CJK SC", 'Noto Sans SC Sliced', 'Noto Sans Japanese', sans-serif;
.serif {
font-family: "Noto Serif", "Noto Serif CJK SC", serif;
.text {
text-align: center;
font-weight: 700;
-webkit-text-stroke: 5px white;
color: black;
font-size: 20px;
white-space: pre;
position: absolute;
-webkit-text-stroke: 0;
outline: none;
<div class="wrap sans" id="content">
<div class="text cover" contenteditable="true"></div>
<div class="text"><br>with Noto Fonts
<script id="jsbin-javascript">
function resize(){
var r1 = cover.clientWidth / cover.clientHeight;
var r2 = window.innerWidth / window.innerHeight;
if (r1 < r2){
var ratio = window.innerHeight / (cover.clientHeight + 10);
} else {
var ratio = window.innerWidth / (cover.clientWidth + 10);
} = "scale(" + ratio + ")";
var cover = document.getElementsByClassName("cover")[0];
var text = document.getElementsByClassName("text")[1];
var wrap = document.getElementsByClassName("wrap")[0];
cover.innerHTML = text.innerHTML;
window.onresize = resize;
function update(){
text.innerHTML = cover.innerHTML;
function set(word) {
cover.innerText = word;
function setHtml(word) {
cover.innerHTML = word;
function serif() {
cover.addEventListener("input", update, false);
cover.addEventListener("DOMNodeInserted", update, false);
cover.addEventListener("DOMNodeRemoved", update, false);
cover.addEventListener("DOMCharacterDataModified", update, false);
server.document-root = "/var/www/big_text_bot"
server.bind = ""
server.port = 10001
mimetype.assign = (".html" => "text/html", ".htm" => "text/html", "svg" => "image/svg+xml",
"ttf" => "application/x-font-ttf",
"otf" => "application/x-font-opentype",
"woff" => "application/font-woff",
"woff2" => "application/font-woff2",
"eot" => "application/",
"sfnt" => "application/font-sfnt")
index-file.names = ( "index.html" , "index.htm" )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment