├── .gitignore ├── Dockerfile ├── LICENSE ├── docker-compose.yml ├── regexbot.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .idea 3 | .env 4 | __pycache__ 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | COPY requirements.txt /src/ 3 | RUN pip install -r /src/requirements.txt 4 | 5 | COPY . /src 6 | CMD python /src/regexbot.py 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sijmen Schoon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | bot: 4 | build: . 5 | environment: 6 | - 'API_KEY' 7 | restart: always 8 | -------------------------------------------------------------------------------- /regexbot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import regex as re 3 | from collections import defaultdict, deque 4 | 5 | from telethon import TelegramClient, events 6 | 7 | SED_PATTERN = r'^s/((?:\\\S|[^/])+)/((?:\\\S|[^/])*)(/.*)?' 8 | GROUP0_RE = re.compile(r'(?', to) 22 | 23 | return from_, to 24 | 25 | 26 | async def doit(message, match): 27 | fr, to = cleanup_pattern(match) 28 | 29 | try: 30 | fl = match.group(3) 31 | if fl is None: 32 | fl = '' 33 | fl = fl[1:] 34 | except IndexError: 35 | fl = '' 36 | 37 | # Build Python regex flags 38 | count = 1 39 | flags = 0 40 | for f in fl.lower(): 41 | if f == 'i': 42 | flags |= re.IGNORECASE 43 | elif f == 'm': 44 | flags |= re.MULTILINE 45 | elif f == 's': 46 | flags |= re.DOTALL 47 | elif f == 'g': 48 | count = 0 49 | elif f == 'x': 50 | flags |= re.VERBOSE 51 | else: 52 | await message.reply('unknown flag: {}'.format(f)) 53 | return 54 | 55 | def substitute(m): 56 | if not m.raw_text: 57 | return None 58 | 59 | s, i = re.subn(fr, to, m.raw_text, count=count, flags=flags) 60 | if i > 0: 61 | return s 62 | 63 | try: 64 | msg = None 65 | substitution = None 66 | if message.is_reply: 67 | msg = await message.get_reply_message() 68 | substitution = substitute(msg) 69 | else: 70 | for msg in reversed(last_msgs[message.chat_id]): 71 | substitution = substitute(msg) 72 | if substitution is not None: 73 | break # msg is also set 74 | 75 | if substitution is not None: 76 | return await msg.reply(substitution) 77 | 78 | except Exception as e: 79 | await message.reply('fuck me\n' + str(e)) 80 | 81 | 82 | @bot.on(events.NewMessage(pattern=SED_PATTERN)) 83 | @bot.on(events.MessageEdited(pattern=SED_PATTERN)) 84 | async def sed(event): 85 | message = await doit(event.message, event.pattern_match) 86 | if message: 87 | last_msgs[event.chat_id].append(message) 88 | 89 | # Don't save sed commands or we would be able to sed those 90 | raise events.StopPropagation 91 | 92 | 93 | @bot.on(events.NewMessage) 94 | async def catch_all(event): 95 | last_msgs[event.chat_id].append(event.message) 96 | 97 | 98 | @bot.on(events.MessageEdited) 99 | async def catch_edit(event): 100 | for i, message in enumerate(last_msgs[event.chat_id]): 101 | if message.id == event.id: 102 | last_msgs[event.chat_id][i] = event.message 103 | 104 | 105 | @bot.on(events.NewMessage(pattern=r"\/privacy")) 106 | async def privacy(event): 107 | await event.reply( 108 | "This bot does not collect or process any user data, apart from a short " 109 | "backlog of messages to perform regex substitutions on. These are not " 110 | "logged or stored anywhere, and can not be accessed by the bot's " 111 | "administrator in any way." 112 | ) 113 | 114 | 115 | if __name__ == '__main__': 116 | with bot.start(bot_token=os.environ['API_KEY']): 117 | bot.run_until_disconnected() 118 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | telethon 2 | regex --------------------------------------------------------------------------------