Compare commits

...

15 Commits

Author SHA1 Message Date
udf ed18b3ccba
Add check for large files and comments 2018-11-25 02:45:20 +02:00
udf b0b2b65ca4
add short descriptions to kbass plugins 2018-11-24 23:22:44 +02:00
udf 4f05e252be
add tdesktop keyboard assistance plugins 2018-11-24 23:14:04 +02:00
udf 599f794525
move get_target_message to utils 2018-11-24 17:19:23 +02:00
udf 4d531be35b
catch exceptions when the title isnt modified 2018-11-22 16:02:48 +02:00
udf dbbd02edc8
Merge branch 'ninja_fix' into kate 2018-11-22 00:40:00 +02:00
udf 74f0cea230
Fix ninja in saved messages
I'm not sure why you'd use ninja in your own chat, but it shouldn't raise anyways.
2018-11-21 23:24:33 +02:00
udf 7f789c23c9
Merge branch 'superblock' into kate 2018-11-21 22:55:49 +02:00
udf cf7c642b18
Merge branch 'master' into kate 2018-11-21 22:55:02 +02:00
udf 35b6e7d1e4
Return immediately in some cases instead of using the result list
I like it better now, but I still dislike it; We need to rewrite the entire function
2018-11-21 22:49:17 +02:00
udf 778a604d69
Prepend list items with "- " 2018-11-21 22:45:01 +02:00
udf e760062218
Minor code cleanup 2018-11-21 22:34:11 +02:00
udf 8c0f04c0cb
Add license header 2018-11-21 22:08:27 +02:00
udf 3e5b6fddb3
Add superblock plugin 2018-11-21 22:08:27 +02:00
udf 5935c19387
Add message info plugin 2018-11-04 14:58:37 +02:00
11 changed files with 248 additions and 45 deletions

View File

@ -10,7 +10,7 @@ from telethon.tl.types import MessageEntityPre
from telethon.tl.tlobject import TLObject from telethon.tl.tlobject import TLObject
import datetime import datetime
PRINTABLE_SET = set(bytes(string.printable, 'ascii')) PRINTABLE_SET = set(string.printable.encode())
STR_LEN_MAX = 256 STR_LEN_MAX = 256
BYTE_LEN_MAX = 64 BYTE_LEN_MAX = 64
@ -19,7 +19,7 @@ def parse_pre(text):
text = text.strip() text = text.strip()
return ( return (
text, text,
[MessageEntityPre(offset=0, length=len(add_surrogate(text)), language='potato')] [MessageEntityPre(offset=0, length=len(add_surrogate(text)), language='')]
) )
@ -33,55 +33,55 @@ def yaml_format(obj, indent=0):
obj = obj.to_dict() obj = obj.to_dict()
if isinstance(obj, dict): if isinstance(obj, dict):
if not obj:
return 'dict:'
result.append(obj.get('_', 'dict') + ':') result.append(obj.get('_', 'dict') + ':')
if obj: items = obj.items()
items = obj.items() has_multiple_items = len(items) > 2
has_multiple_items = len(items) > 2 if has_multiple_items:
if has_multiple_items: result.append('\n')
result.append('\n') indent += 2
indent += 2 for k, v in items:
for k, v in items: if k == '_' or v is None:
if k == '_' or v is None: continue
continue formatted = yaml_format(v, indent)
formatted = yaml_format(v, indent) if not formatted.strip():
if not formatted.strip(): continue
continue result.append(' ' * (indent if has_multiple_items else 1))
result.append(' ' * (indent if has_multiple_items else 1)) result.append(f'{k}: {formatted}')
result.append(f'{k}: {formatted}') result.append('\n')
result.append('\n') result.pop()
result.pop() indent -= 2
indent -= 2 result.append(' ' * indent)
result.append(' ' * indent)
elif isinstance(obj, str): elif isinstance(obj, str):
# truncate long strings and display elipsis # truncate long strings and display elipsis
result.append(repr(obj[:STR_LEN_MAX])) result = repr(obj[:STR_LEN_MAX])
if len(obj) > STR_LEN_MAX: if len(obj) > STR_LEN_MAX:
result.append('') result += ''
return result
elif isinstance(obj, bytes): elif isinstance(obj, bytes):
# repr() bytes if it's printable, hex like "FF EE BB" otherwise # repr() bytes if it's printable, hex like "FF EE BB" otherwise
if all(c in PRINTABLE_SET for c in obj): if all(c in PRINTABLE_SET for c in obj):
result.append(repr(obj)) return repr(obj)
else: else:
if len(obj) > BYTE_LEN_MAX: return ('<…>' if len(obj) > BYTE_LEN_MAX else
result.append('<…>') ' '.join(f'{b:02X}' for b in obj))
else:
result.append(' '.join(f'{b:02X}' for b in obj))
elif isinstance(obj, datetime.datetime): elif isinstance(obj, datetime.datetime):
# ISO-8601 without timezone offset (telethon dates are always UTC) # ISO-8601 without timezone offset (telethon dates are always UTC)
result.append(obj.strftime('%Y-%m-%d %H:%M:%S')) return obj.strftime('%Y-%m-%d %H:%M:%S')
elif hasattr(obj, '__iter__'): elif hasattr(obj, '__iter__'):
# display iterables one after another at the base indentation level # display iterables one after another at the base indentation level
result.append('\n') result.append('\n')
indent += 2 indent += 2
for x in obj: for x in obj:
result.append(' ' * indent)
result.append(yaml_format(x, indent)) result.append(f"{' ' * indent}- {yaml_format(x, indent + 2)}")
result.append('\n') result.append('\n')
result.pop() result.pop()
indent -= 2 indent -= 2
result.append(' ' * indent) result.append(' ' * indent)
else: else:
result.append(repr(obj)) return repr(obj)
return ''.join(result) return ''.join(result)

46
stdplugins/kbass_core.py Normal file
View File

@ -0,0 +1,46 @@
"""
Contains code used by other kbass_* plugins
"""
from uniborg import util
def self_reply_cmd(borg, pattern):
def wrapper(function):
@borg.on(util.admin_cmd(pattern))
async def wrapped(event, *args, **kwargs):
await event.delete()
target = await util.get_target_message(borg, event)
if not target:
return
return await function(event, target, *args, **kwargs)
return wrapped
return wrapper
def self_reply_selector(borg, cmd):
def wrapper(function):
@borg.on(util.admin_cmd(cmd + r"( [+-]?\d+)?$"))
async def wrapped(event, *args, **kwargs):
await event.delete()
reply = await event.get_reply_message()
if not reply:
return
num_offset = int(event.pattern_match.group(1) or 0)
reverse = num_offset > 0
targets = [reply] if reverse else []
targets.extend(await borg.get_messages(
await event.get_input_chat(),
limit=abs(num_offset),
offset_id=reply.id,
reverse=reverse
))
if not reverse:
# reverse the list because we want things to always be in
# history order
targets.reverse()
targets.append(reply)
return await function(event, targets, num_offset, *args, **kwargs)
return wrapped
return wrapper

43
stdplugins/kbass_edit.py Normal file
View File

@ -0,0 +1,43 @@
"""
Reply to a message with .e to make a draft of it (with '\n.e' appended)
Reply to your own message with <text>.e to edit the message to <text>
if <text> is ".", the message is edited to empty/deleted
"""
import asyncio
from telethon.errors import MessageEmptyError
from telethon.tl.functions.messages import SaveDraftRequest
from telethon.tl.functions.messages import EditMessageRequest
from stdplugins.kbass_core import self_reply_cmd
@self_reply_cmd(borg, r"^\.e$")
async def on_edit_start(event, target):
await asyncio.sleep(3) # tdesktop doesn't sync drafts when the window is active
await borg(SaveDraftRequest(
peer=await event.get_input_chat(),
message=(target.message or '.') + '\n.e',
entities=target.entities,
no_webpage=not target.media,
reply_to_msg_id=target.id
))
@self_reply_cmd(borg, r'(?ms)^(.+)\.e$')
async def on_edit_end(event, target):
text = event.pattern_match.group(1)
message = event.message.message[:-2]
if message.strip() == '.':
message = ''
try:
await borg(EditMessageRequest(
peer=await event.get_input_chat(),
id=target.id,
no_webpage=not target.media,
message=message,
entities=event.message.entities
))
except MessageEmptyError:
# Can't make text message empty, so delete it
await borg.delete_messages(chat, target)

View File

@ -0,0 +1,34 @@
"""
Reply to a file with .f to send it as a photo
"""
from io import BytesIO
from stdplugins.kbass_core import self_reply_cmd
from telethon import types
from telethon.errors import PhotoInvalidDimensionsError
@self_reply_cmd(borg, r"^\.f$")
async def on_file_to_photo(event, target):
try:
image = target.media.document
except AttributeError:
return
if not image.mime_type.startswith('image/'):
return # This isn't an image
if image.mime_type == 'image/webp':
return # Telegram doesn't let you directly send stickers as photos
if image.size > 10 * 1024 * 1024:
return # We'd get PhotoSaveFileInvalidError otherwise
file = await borg.download_media(target, file=BytesIO())
file.seek(0)
img = await borg.upload_file(file)
try:
await event.respond(
reply_to=target,
file=types.InputMediaUploadedPhoto(img)
)
except PhotoInvalidDimensionsError:
return

15
stdplugins/kbass_save.py Normal file
View File

@ -0,0 +1,15 @@
"""
Reply to a message with .s <n> to forward <n> messages from that point to your
saved messages (negative values of <n> go backwards in history)
"""
import asyncio
from stdplugins.kbass_core import self_reply_selector
@self_reply_selector(borg, r'\.s')
async def on_save(event, targets, num_offset):
await borg.forward_messages('me', targets)
msg = await event.respond(f'Saved {abs(num_offset) + 1} messages!')
await asyncio.sleep(3)
await borg.delete_messages(msg.to_id, msg)

View File

@ -0,0 +1,25 @@
"""
Reply to a message with .p <y/n> to toggle the webpage preview of a message
"""
from telethon.errors import MessageNotModifiedError
from telethon.tl.functions.messages import EditMessageRequest
from stdplugins.kbass_core import self_reply_cmd
@self_reply_cmd(borg, r"^\.p(?: ?)([yn])?$")
async def on_edit_preview(event, target):
preview = event.pattern_match.group(1) == 'y'
if not event.pattern_match.group(1):
preview = not bool(target.media)
try:
await borg(EditMessageRequest(
peer=await event.get_input_chat(),
id=target.id,
no_webpage=not preview,
message=target.message,
entities=target.entities
))
except MessageNotModifiedError:
# There was no preview to modify
pass

28
stdplugins/kbass_yank.py Normal file
View File

@ -0,0 +1,28 @@
"""
Like save but makes a draft in your chat of all the messages concatenated with
two newlines
"""
import html
from telethon.tl.functions.messages import SaveDraftRequest
from telethon.extensions import html as thtml
from stdplugins.kbass_core import self_reply_selector
def get_message_html(message):
if message.action:
return html.escape(str(message.action))
return thtml.unparse(message.message, message.entities)
@self_reply_selector(borg, r'\.y')
async def on_yank(event, targets, num_offset):
message = '\n\n'.join(get_message_html(target) for target in targets)
message, entities = thtml.parse(message)
await borg(SaveDraftRequest(
peer='me',
message=message,
entities=entities,
no_webpage=True,
))

View File

@ -5,21 +5,15 @@
import asyncio import asyncio
from telethon import events from telethon import events
from telethon.tl.types import InputPeerSelf
import telethon.utils import telethon.utils
from uniborg import util from uniborg import util
async def get_target_message(event):
if event.is_reply and (await event.get_reply_message()).from_id == borg.uid:
return await event.get_reply_message()
async for message in borg.iter_messages(
await event.get_input_chat(), limit=20):
if message.out:
return message
async def await_read(chat, message): async def await_read(chat, message):
if isinstance(chat, InputPeerSelf):
return
chat = telethon.utils.get_peer_id(chat) chat = telethon.utils.get_peer_id(chat)
async def read_filter(read_event): async def read_filter(read_event):
@ -43,7 +37,7 @@ async def delete(event):
text = event.pattern_match.group(2) text = event.pattern_match.group(2)
if not text: if not text:
return return
target = await get_target_message(event) target = await util.get_target_message(borg, event)
if target: if target:
chat = await event.get_input_chat() chat = await event.get_input_chat()
await await_read(chat, target) await await_read(chat, target)

View File

@ -3,6 +3,7 @@ import re
from telethon import events from telethon import events
from telethon.tl.functions.channels import EditTitleRequest from telethon.tl.functions.channels import EditTitleRequest
from telethon.errors.rpcerrorlist import ChatNotModifiedError
MULTI_EDIT_TIMEOUT = 80 MULTI_EDIT_TIMEOUT = 80
REVERT_TIMEOUT = 2 * 60 * 60 REVERT_TIMEOUT = 2 * 60 * 60
@ -26,9 +27,12 @@ async def edit_title(title):
global prog_tech_channel global prog_tech_channel
if prog_tech_channel is None: if prog_tech_channel is None:
prog_tech_channel = await borg.get_entity(CHANNEL_ID) prog_tech_channel = await borg.get_entity(CHANNEL_ID)
await borg(EditTitleRequest( try:
channel=prog_tech_channel, title=title await borg(EditTitleRequest(
)) channel=prog_tech_channel, title=title
))
except ChatNotModifiedError:
pass # Everything is ok
async def wait_for_delete(deleted_fut, timeout): async def wait_for_delete(deleted_fut, timeout):

View File

@ -1,5 +1,9 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import asyncio import asyncio
import time
from telethon import events from telethon import events
import telethon.tl.functions as tlf import telethon.tl.functions as tlf
from telethon.tl.types import InputPeerChannel, UpdateUserBlocked from telethon.tl.types import InputPeerChannel, UpdateUserBlocked
@ -8,6 +12,7 @@ from telethon.tl.functions.contacts import GetBlockedRequest
# How often to fetch the full list of blocked users # How often to fetch the full list of blocked users
REFETCH_TIME = 60 REFETCH_TIME = 60
blocked_user_ids = set() blocked_user_ids = set()

View File

@ -28,3 +28,12 @@ async def is_read(borg, entity, message, is_out=None):
dialog = (await borg(GetPeerDialogsRequest([entity]))).dialogs[0] dialog = (await borg(GetPeerDialogsRequest([entity]))).dialogs[0]
max_id = dialog.read_outbox_max_id if is_out else dialog.read_inbox_max_id max_id = dialog.read_outbox_max_id if is_out else dialog.read_inbox_max_id
return message_id <= max_id return message_id <= max_id
async def get_target_message(borg, event):
if event.is_reply and (await event.get_reply_message()).from_id == borg.uid:
return await event.get_reply_message()
async for message in borg.iter_messages(
await event.get_input_chat(), limit=20):
if message.out:
return message