commit bfceff8f10c60f7ff91f065ec13390fd1f7d96f8 Author: Ed Date: Tue Aug 1 23:10:14 2023 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..01a26ea --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# Telegram Printer Bot + +This Python script implements a Telegram bot that can print images and stickers sent by users. The bot supports resizing images, converting them to grayscale, and applying gamma correction before printing to ensure maximum quality. + +Currently, you can set a command to print your sticker (by default we used brother_ql to print to a Brother printer). You can use any external program you want to print to other brands and models of printers. + +## Requirements + +* Python 3.6+ +* telethon +* PIL (Python Imaging Library) library (Pillow) +* brother_ql + +You can install the requirements by running this command: + +`python3 -m pip install -r requirements.txt` + +If this is the first time running the script and your printer uses the `lp` protocol, remember to add your user to the `lp` group using the following command: + +`sudo usermod -a -G lp ${USER}` + +## Configuration + +Before running the script, you need to set up the configuration parameters. You can use "config.example.py" as a guide (rename it to config.py). The following parameters must be defined: + +* API_ID: Your Telegram API ID. You can obtain it by creating a Telegram application +* API_HASH: Your Telegram API hash. You can obtain it from the same page where you got the API ID. +* BOT_TOKEN: The token for your Telegram bot. You can create a new bot and obtain the token by following the instructions here. +* ADMIN_ID: Your user id. This is the user that will receive administrative rights and error reports. +* PRINT_COMMAND: Adjust with your printer model and path. + +## Usage + +After setting up the configuration file, you can just run the bot by using the command + +`python bot.py` + +Once the bot is running, it will respond to specific commands: + +* `/id`: Returns your Telegram user ID, which you need to add to the ADMIN_ID list in the config.py file to grant yourself privileges. +* `/start`: Displays a welcome message and requests a password if set in the config.py file. + When the user sends the correct password in a private message, the printer functionality will be unlocked for that user. + +## Features + +* Printer password (pin code) protection +* Cooldown period for users +* Caching of images and stickers +* Resizing images to the correct printer resolution for maximum crispness +* Conversion to greyscale with gamma adjustment (improves images a lot!) +* Ratio limit to prevent excessively long stickers from being printed + +## Important Notes + +1. Make sure to set proper permissions for the cache directory to ensure the bot can write to it. +2. The PRINT_COMMAND and PRINT_SUCCESS_COMMAND in the config.py file should be customized to match the print command on your system. +3. Ensure you have a functioning printer setup before running the bot. You will find the output from the command in the console! + +## License + +This project is licensed under the BEER-WARE License. + +```/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + */``` + +**This script is provided as-is, without any warranty or support. Use it at your own risk. The authors are not responsible for any misuse or damage caused by this script.** diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..05e9751 --- /dev/null +++ b/bot.py @@ -0,0 +1,111 @@ +from telethon import TelegramClient +import logging, asyncio +from telethon.tl.types import MessageMediaDocument, DocumentAttributeSticker, DocumentAttributeAnimated, MessageMediaPhoto +from telethon import events +from telethon.tl.custom import Button +from os.path import isfile, join +from os import system +from time import time +from PIL import Image +from config import * +from os import makedirs + +logging.basicConfig(level=logging.INFO) + +client = TelegramClient('bot', API_ID, API_HASH).start(bot_token=BOT_TOKEN) +client.flood_sleep_threshold = 120 + +print_log = {} + +@client.on(events.NewMessage(pattern='^/id')) +async def debug_id(ev): + await ev.respond(f"Hello! Your id is `{ev.peer_id.user_id}` please add it to the ADMIN_ID to give youself privileges :)") + +@client.on(events.NewMessage(pattern='^/start')) +async def welcome(ev): + await ev.respond(WELCOME_MSG) + if (ev.peer_id.user_id not in print_log) and PASSWORD: + await ev.respond(UNLOCK_MSG) + +# This one triggers on a single message with the pin code written +@client.on(events.NewMessage(pattern=PASSWORD, func=lambda e: e.is_private)) +async def unlock_printer(ev): + if ev.peer_id.user_id not in print_log: + print_log[ev.peer_id.user_id] = 0 + if PASSWORD: + await ev.respond(UNLOCKED_MSG) + +@client.on(events.NewMessage(incoming=True, func=lambda e: e.is_private and e.message.media)) +async def handler(ev): + + msg = ev.message + if ev.peer_id.user_id not in print_log: + await ev.respond(UNLOCK_MSG) + + # Check if the file is valid + if msg.photo: + fn = join(CACHE_DIR, f"{msg.photo.id}.jpg") + elif msg.sticker: + fn = join(CACHE_DIR, f"{msg.sticker.id}.webp") + for att in msg.sticker.attributes: + if isinstance(att, DocumentAttributeAnimated): + fn = None + break + else: + fn = None + + if not fn: + await ev.respond(FORMAT_ERR_MSG) + return + + # Check if the user is still in the cooldown period + time_left = int((print_log[ev.peer_id.user_id] + BASE_COOLDOWN) - time()) + if time_left > 0: + await ev.respond(RATELIMIT_MSG.format(time_left=time_left)) + return + + # Download the file unless it's in the cache! + if not isfile(fn): + await client.download_media(msg, file=fn) + + # Try opening the image, at least + try: + img = Image.open(fn) + except: + await ev.respond(FORMAT_ERR_MSG) + return + + # Limit stickers ratio (so people don't print incredibly long stickers) + if img.size[1]/img.size[0] > MAX_ASPECT_RATIO: + return ev.respond(RATIO_ERR_MSG) + + # Remove transparency + if img.mode == 'RGBA': + bg_img = Image.new(img.mode, img.size, BACKGROUND_COLOR) + img = Image.alpha_composite(bg_img, img) + + # Resize the image + img.thumbnail([WIDTH_PX, HEIGHT_PX], resample=Image.LANCZOS, reducing_gap=None) + + # Convert to grayscale and apply a gamma of 1.8 + img = img.convert('L') + + if GAMMA_CORRECTION != 1: + img = Image.eval(img, lambda x: int(255*pow((x/255),(1/GAMMA_CORRECTION)))) + + img.save(IMAGE_PATH, 'PNG') + + status_code = system(PRINT_COMMAND) + if status_code == 0: + print_log[ev.peer_id.user_id] = time() + await ev.respond(PRINT_SUCCESS_MSG) + else: + await ev.respond(PRINT_FAIL_MSG) + await client.send_message(ADMIN_ID, f'Printer is not working. Process returned status code {status_code}') + + if PRINT_SUCCESS_COMMAND: + system(PRINT_SUCCESS_COMMAND) + +if __name__ == '__main__': + makedirs(CACHE_DIR, exist_ok=True) + client.run_until_disconnected() diff --git a/config.example.py b/config.example.py new file mode 100644 index 0000000..dd310ee --- /dev/null +++ b/config.example.py @@ -0,0 +1,37 @@ + +# To get an API_ID and API_HASH, you need to sign up to be a Telegram Dev. +# Do it here: https://core.telegram.org/api/obtaining_api_id +API_ID = 11111 +API_HASH = '5d41402abc4b2a76b9719d911017c592' + +# Get a bot token from Telegram by creating a bot with @BotFather +BOT_TOKEN = '1222222222:b2YgZXdvaWZld29maHdlb2lmaA' + +WELCOME_MSG = 'Hello!\nWelcome to **Foxo\'s label printer**! Send me any sticker or other media to print it!' +UNLOCK_MSG = 'The printer is currently locked for you. Please enter the password!' +PRINT_FAIL_MSG = 'I wasn\'t able to print your sticker.' +RATIO_ERR_MSG = 'That image is too tall. It would waste a lot of paper. Please give me a shorter sticker.' +PRINT_SUCCESS_MSG = 'Your sticker has finished printing now! Enjoy it :3' +FORMAT_ERR_MSG = 'Cannot print this. Try with a (static) sticker or a picture!' +RATELIMIT_MSG = 'Woo calm down fam!\n\nSend the sticker again in {time_left} seconds!' +UNLOCKED_MSG = 'Printer has been unlocked. Have fun!' + +# Limits to prevent abuse +PASSWORD = '12345' # Set to None if no password required +BASE_COOLDOWN = 10 # Seconds between stickers printing +MAX_ASPECT_RATIO = 1.5 # Maximum ratio between height/width of sticker +ADMIN_ID = 111111 # Find your own id with the /id command + +# Folder settings +IMAGE_PATH = '/tmp/image.png' +CACHE_DIR = '/tmp/printercache' + +# Remember to add your user to the "lp" group or this won't work! +PRINT_COMMAND = f"brother_ql -m QL-700 -b linux_kernel -p file:///dev/usb/lp0 print -l 62 {IMAGE_PATH} -d" +PRINT_SUCCESS_COMMAND = None # "mpv --no-video success.wav" - this was used to play audio + +# Resize and process settings +WIDTH_PX = 696 +HEIGHT_PX = 9999 # This means "do not care about height" +GAMMA_CORRECTION = 1.8 +BACKGROUND_COLOR = 'white' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..05ddc68 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +telethon +Pillow +brother_ql