fixed reply handling and added more support for post interactions
All checks were successful
git.dreamfall.space/pnut-matrix/pipeline/head This commit looks good
All checks were successful
git.dreamfall.space/pnut-matrix/pipeline/head This commit looks good
- Replies from clients with quotes are handled properly, issue #75 - Repost and Bookmark via reactions now supported - Bot commands for post interactions added, issue #71
This commit is contained in:
parent
c23889b6a8
commit
3d5abbba8b
3 changed files with 277 additions and 132 deletions
|
@ -23,6 +23,7 @@ dependencies = [
|
|||
"asyncclick",
|
||||
"peewee",
|
||||
"tomlkit",
|
||||
"beautifulsoup4",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
|
|
@ -11,6 +11,7 @@ import time
|
|||
import os
|
||||
import argparse
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from mautrix.client import ClientAPI
|
||||
from mautrix.types import *
|
||||
from pnut_matrix.models import *
|
||||
|
@ -110,12 +111,6 @@ async def on_receive_events(transaction):
|
|||
logging.debug(event)
|
||||
logging.debug('~~~~~~~~~~~~~~~')
|
||||
|
||||
# if (app.config['MATRIX_ADMIN_ROOM'] and
|
||||
# app.config['MATRIX_ADMIN_ROOM'] == event['room_id']):
|
||||
# logging.debug('>----on_admin_event----<')
|
||||
# await on_admin_event(event)
|
||||
# return jsonify({})
|
||||
|
||||
user = PnutUsers.select().where(PnutUsers.matrix_id ==
|
||||
event['sender']).first()
|
||||
|
||||
|
@ -127,6 +122,10 @@ async def on_receive_events(transaction):
|
|||
logging.debug('>----delete_message----<')
|
||||
delete_message(event, user)
|
||||
|
||||
elif event['type'] == 'm.reaction':
|
||||
logging.debug('>----react_to_message----<')
|
||||
await react_message(event, user)
|
||||
|
||||
elif event['type'] == 'm.room.member':
|
||||
if ('is_direct' in event['content'] and
|
||||
'membership' in event['content']):
|
||||
|
@ -166,6 +165,12 @@ async def new_message(event, user):
|
|||
if event['room_id'] == app.config['pnut']['global_room']:
|
||||
room = PnutChannels(pnut_chan=0,
|
||||
room_id=app.config['pnut']['global_room'])
|
||||
elif event['room_id'] == app.config['pnut']['news_room']:
|
||||
room = PnutChannels(pnut_chan=0,
|
||||
room_id=app.config['pnut']['news_room'])
|
||||
elif event['room_id'] == app.config['pnut']['bot_room']:
|
||||
room = PnutChannels(pnut_chan=0,
|
||||
room_id=app.config['pnut']['bot_room'])
|
||||
|
||||
else:
|
||||
logging.debug('-room not mapped-')
|
||||
|
@ -191,69 +196,32 @@ async def new_message(event, user):
|
|||
pnutpy.api.add_authorization_token(token)
|
||||
|
||||
raw = {}
|
||||
raw['io.pnut.core.crosspost'] = [crosspost_raw(event)]
|
||||
text, oembed = await msg_from_event(event, user)
|
||||
# raw['io.pnut.core.crosspost'] = [crosspost_raw(event)]
|
||||
text, oembed, reply_to = await msg_from_event(event, user, room.pnut_chan)
|
||||
if text is None:
|
||||
return
|
||||
|
||||
text = prefix + text
|
||||
if oembed:
|
||||
raw['io.pnut.core.oembed'] = [oembed]
|
||||
logging.debug(oembed)
|
||||
|
||||
reply_to_id = None
|
||||
ev_content = event['content']
|
||||
if 'm.relates_to' in ev_content:
|
||||
m_relates_to = ev_content['m.relates_to']
|
||||
if 'm.in_reply_to' in m_relates_to:
|
||||
reply_event_id = m_relates_to['m.in_reply_to']['event_id']
|
||||
e = Events.select().where(Events.event_id ==
|
||||
reply_event_id).first()
|
||||
if e is not None:
|
||||
reply_to_id = e.pnut_id
|
||||
|
||||
try:
|
||||
|
||||
payload = {'raw': raw}
|
||||
if room.pnut_chan == 0:
|
||||
if reply_to_id is not None:
|
||||
orig, meta = pnutpy.api.get_post(reply_to_id)
|
||||
if orig.user.id != user.pnut_user_id:
|
||||
author = orig.user.username
|
||||
text = f"@{author} {text}"
|
||||
if 'content' in orig:
|
||||
cc_list = []
|
||||
for m in orig.content.entities.mentions:
|
||||
if m.text == author:
|
||||
continue
|
||||
cc_list.append(f"@{m.text}")
|
||||
if len(cc_list) > 0:
|
||||
copy = " ".join(cc_list)
|
||||
text = f"{text} /{copy}"
|
||||
payload['reply_to'] = reply_to_id
|
||||
|
||||
if room.pnut_chan == 0:
|
||||
payload['text'] = text
|
||||
if reply_to:
|
||||
payload['reply_to'] = reply_to
|
||||
data, meta = pnutpy.api.create_post(data=payload)
|
||||
|
||||
else:
|
||||
if reply_to_id is not None:
|
||||
orig, meta = pnutpy.api.get_message(room.pnut_chan, reply_to_id)
|
||||
if orig.user.id != user.pnut_user_id:
|
||||
author = orig.user.username
|
||||
text = f"@{author} {text}"
|
||||
if 'content' in orig:
|
||||
cc_list = []
|
||||
for m in orig.content.entities.mentions:
|
||||
if m.text == author:
|
||||
continue
|
||||
cc_list.append(f"@{m.text}")
|
||||
if len(cc_list) > 0:
|
||||
copy = " ".join(cc_list)
|
||||
text = f"{text} /{copy}"
|
||||
payload['reply_to'] = reply_to_id
|
||||
|
||||
payload['text'] = text
|
||||
data, meta = pnutpy.api.create_message(room.pnut_chan,
|
||||
data=payload)
|
||||
if reply_to:
|
||||
payload['reply_to'] = reply_to
|
||||
data, meta = pnutpy.api.create_message(room.pnut_chan, data=payload)
|
||||
|
||||
bridge_event = Events(
|
||||
event_id=event['event_id'],
|
||||
room_id=event['room_id'],
|
||||
|
@ -276,11 +244,11 @@ async def new_message(event, user):
|
|||
logging.exception('-something bad happened here-')
|
||||
return
|
||||
|
||||
async def msg_from_event(event, user):
|
||||
async def msg_from_event(event, user, pnut_chan):
|
||||
text = None
|
||||
oembed = None
|
||||
if (event['content']['msgtype'] == 'm.text' or
|
||||
event['content']['msgtype'] == 'm.notice'):
|
||||
reply_to = None
|
||||
if event['content']['msgtype'] == 'm.text':
|
||||
text = event['content']['body']
|
||||
|
||||
fregex = re.compile(r'!file\s(\d+)')
|
||||
|
@ -298,6 +266,36 @@ async def msg_from_event(event, user):
|
|||
'file_token': pnut_file.pnut_file_token,
|
||||
'format': 'oembed'}}
|
||||
|
||||
if text.startswith('!') and pnut_chan == 0:
|
||||
logging.debug(">>>> stream command <<<<")
|
||||
text, reply_to = await on_stream_command(event, user, pnut_chan)
|
||||
|
||||
elif 'm.relates_to' in event['content']:
|
||||
m_relates_to = event['content']['m.relates_to']
|
||||
if 'm.in_reply_to' in m_relates_to:
|
||||
reply_event_id = m_relates_to['m.in_reply_to']['event_id']
|
||||
e = Events.select().where(Events.event_id ==
|
||||
reply_event_id).first()
|
||||
if e is not None:
|
||||
reply_to = e.pnut_id
|
||||
if 'formatted_body' in event['content']:
|
||||
formatted_body = event['content']['formatted_body']
|
||||
soup = BeautifulSoup(formatted_body, 'html.parser')
|
||||
mx_reply = soup.findAll('mx-reply')
|
||||
for item in mx_reply:
|
||||
item.decompose()
|
||||
body = soup.decode()
|
||||
|
||||
m = fregex.search(body)
|
||||
if m is not None:
|
||||
file_id = m.group(1)
|
||||
body = fregex.sub('', body)
|
||||
|
||||
else:
|
||||
body = text
|
||||
|
||||
text = cmd_stream_reply(user, reply_to, body, pnut_chan)
|
||||
|
||||
elif event['content']['msgtype'] == 'm.emote':
|
||||
text = "* " + event['content']['body']
|
||||
|
||||
|
@ -306,7 +304,7 @@ async def msg_from_event(event, user):
|
|||
event['content']['msgtype'] == 'm.audio') and
|
||||
user is not None):
|
||||
await media_from_event(event, user)
|
||||
return None, None
|
||||
return None, None, None
|
||||
|
||||
elif (event['content']['msgtype'] == 'm.image' or
|
||||
event['content']['msgtype'] == 'm.video' or
|
||||
|
@ -329,7 +327,7 @@ async def msg_from_event(event, user):
|
|||
logging.debug('-unknown msg type- ' + event['content']['msgtype'])
|
||||
return
|
||||
|
||||
return text, oembed
|
||||
return text, oembed, reply_to
|
||||
|
||||
def crosspost_raw(event):
|
||||
cross_profile = {'username': event['sender']}
|
||||
|
@ -463,6 +461,50 @@ def delete_message(event, user):
|
|||
except pnutpy.errors.PnutPermissionDenied:
|
||||
pass
|
||||
|
||||
async def react_message(event, user):
|
||||
as_id = (f"@{app.config['matrix']['sender_local']}:"
|
||||
f"{app.config['matrix']['domain']}")
|
||||
|
||||
matrix_api = ClientAPI(as_id,
|
||||
base_url=app.config['matrix']['homeserver'],
|
||||
token=app.config['matrix']['as_token'])
|
||||
|
||||
if event['sender'] == as_id:
|
||||
return
|
||||
|
||||
if app.config['matrix']['namespace'] in event['sender']:
|
||||
return
|
||||
|
||||
if user is None:
|
||||
return
|
||||
|
||||
pnutpy.api.add_authorization_token(user.pnut_user_token)
|
||||
|
||||
m_relates_to = event['content']['m.relates_to']
|
||||
react_event_id = m_relates_to['event_id']
|
||||
e = Events.select().where(Events.event_id == react_event_id).first()
|
||||
if e is None:
|
||||
logging.debug("- can't find the event to react to -")
|
||||
return
|
||||
|
||||
if e.pnut_channel == 0:
|
||||
|
||||
# repost
|
||||
if m_relates_to['key'] == '👀':
|
||||
reply = cmd_stream_repost(e.pnut_id)
|
||||
|
||||
# bookmark
|
||||
elif m_relates_to['key'] in ['❤️', '⭐', '👍', '🔖']:
|
||||
reply = cmd_stream_bookmark(e.pnut_id)
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
reply = f'{event['sender']} {reply}'
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.NOTICE,
|
||||
body=reply)
|
||||
await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
|
||||
def get_profile(userid):
|
||||
url = app.config['matrix']['homeserver'] + "/_matrix/client/r0/profile/" + userid
|
||||
r = requests.get(url)
|
||||
|
@ -559,78 +601,6 @@ def new_matrix_user(username):
|
|||
logging.debug(r.text)
|
||||
return
|
||||
|
||||
# async def on_admin_event(event):
|
||||
# matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
|
||||
# base_url=app.config['MATRIX_HOST'],
|
||||
# token=app.config['MATRIX_AS_TOKEN'])
|
||||
#
|
||||
# logging.debug("- admin room event recieved -")
|
||||
#
|
||||
# if event['type'] != 'm.room.message':
|
||||
# return jsonify({})
|
||||
#
|
||||
# msg = event['content']['body'].split(' ')
|
||||
#
|
||||
# try:
|
||||
# if msg[0] == 'help':
|
||||
# if len(msg) > 1:
|
||||
# await matrix_api.send_message(event['room_id'],
|
||||
# cmd_admin_help(msg[1]))
|
||||
# else:
|
||||
# await matrix_api.send_message(event['room_id'],
|
||||
# cmd_admin_help())
|
||||
#
|
||||
# elif msg[0] == 'list':
|
||||
# await matrix_api.send_message(event['room_id'], cmd_admin_list())
|
||||
#
|
||||
# except Exception:
|
||||
# errmsg = "- on_admin_event -"
|
||||
# logging.exception(errmsg)
|
||||
|
||||
# def cmd_admin_help(cmd=None):
|
||||
# help_usage = "help [command]"
|
||||
# help_desc = "Show information about available commands."
|
||||
# list_usage = "list"
|
||||
# list_desc = "List the rooms currently linked with pnut.io."
|
||||
#
|
||||
# if cmd == 'help':
|
||||
# text = "usage: " + help_usage + "\n\n"
|
||||
# text += help_desc
|
||||
# if cmd == 'list':
|
||||
# text = "usage: " + list_usage + "\n\n"
|
||||
# text += list_desc
|
||||
# elif cmd == 'unlink':
|
||||
# text = "usage: " + unlink_usage + "\n\n"
|
||||
# text += unlink_desc
|
||||
# elif cmd == 'link':
|
||||
# text = "usage: " + link_usage + "\n\n"
|
||||
# text += link_desc
|
||||
# else:
|
||||
# text = "The following commands are available:\n\n"
|
||||
# text += help_usage + "\n"
|
||||
# text += list_usage + "\n"
|
||||
# text += unlink_usage + "\n"
|
||||
# text += link_usage + "\n"
|
||||
#
|
||||
# return TextMessageEventContent(msgtype='m.text', body=text)
|
||||
|
||||
# def cmd_admin_list():
|
||||
# text = ""
|
||||
# rooms = PnutChannels.select()
|
||||
#
|
||||
# if len(rooms) > 0:
|
||||
# text = "ID\tMATRIX ID\tPNUT CHANNEL\n"
|
||||
# else:
|
||||
# text = " - no rooms are currently linked - \n"
|
||||
#
|
||||
# for room in rooms:
|
||||
# text += str(room.id) + '\t'
|
||||
# text += room.room_id + '\t\t\t\t\t'
|
||||
# text += str(room.pnut_chan) + '\t'
|
||||
# text += '\n'
|
||||
#
|
||||
# return TextMessageEventContent(msgtype='m.text', body=text)
|
||||
|
||||
async def on_direct_invite(event):
|
||||
as_id = (f"@{app.config['matrix']['sender_local']}:"
|
||||
f"{app.config['matrix']['domain']}")
|
||||
|
@ -730,7 +700,7 @@ async def on_direct_message(event, user, room):
|
|||
|
||||
raw = {}
|
||||
raw['io.pnut.core.crosspost'] = [crosspost_raw(event)]
|
||||
evtext, evraw = await msg_from_event(event, user)
|
||||
evtext, evraw, noop = await msg_from_event(event, user, room.pnut_chan)
|
||||
text = prefix + evtext
|
||||
|
||||
try:
|
||||
|
@ -752,6 +722,180 @@ async def on_direct_message(event, user, room):
|
|||
|
||||
return jsonify({})
|
||||
|
||||
async def on_stream_command(event, user, pnut_chan):
|
||||
as_id = (f"@{app.config['matrix']['sender_local']}:"
|
||||
f"{app.config['matrix']['domain']}")
|
||||
matrix_api = ClientAPI(as_id,
|
||||
base_url=app.config['matrix']['homeserver'],
|
||||
token=app.config['matrix']['as_token'])
|
||||
|
||||
if event['type'] != 'm.room.message':
|
||||
return None, None
|
||||
|
||||
args = event['content']['body'].split(' ')
|
||||
|
||||
stream_commands = {
|
||||
'!help': '\t\t\t\tdisplay help text',
|
||||
'!reply': ' (post_id) (text)\treply to post',
|
||||
'!replyg': ' (post_id) (text)\treply globally to post',
|
||||
'!repost': ' (post_id)\t\trepost a post to your followers',
|
||||
'!bookmark': ' (post_id)\t\tbookmark a post',
|
||||
}
|
||||
|
||||
if args[0] == '!help':
|
||||
reply = f'{event['sender']}\n'
|
||||
reply += '<pre>Commands:\n'
|
||||
for key in stream_commands:
|
||||
reply += f"{key} {stream_commands[key]}\n"
|
||||
reply += '</pre>'
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.TEXT,
|
||||
format=Format.HTML,
|
||||
formatted_body=reply)
|
||||
await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
return None, None
|
||||
|
||||
elif args[0] == '!reply':
|
||||
if len(args) < 3:
|
||||
reply = f'{event['sender']} invalid command!\n'
|
||||
reply += f"<pre>Usage: !reply {stream_commands['!reply']}</pre>"
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.TEXT,
|
||||
format=Format.HTML,
|
||||
formatted_body=reply)
|
||||
await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
return None, None
|
||||
text = cmd_stream_reply(user, args[1], ' '.join(args[2:]), pnut_chan)
|
||||
reply_to = args[1]
|
||||
return text, reply_to
|
||||
|
||||
elif args[0] == '!replyg':
|
||||
if len(args) < 3:
|
||||
reply = f'{event['sender']} invalid command!\n'
|
||||
reply += f"<pre>Usage: !replyg {stream_commands['!replyg']}</pre>"
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.TEXT,
|
||||
format=Format.HTML,
|
||||
formatted_body=reply)
|
||||
await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
return None, None
|
||||
text = ' '.join(args[2:])
|
||||
reply_to = args[1]
|
||||
return text, reply_to
|
||||
|
||||
elif args[0] == '!repost':
|
||||
if len(args) < 2:
|
||||
reply = f'{event['sender']} invalid command!\n'
|
||||
reply += f"<pre>Usage: !repost {stream_commands['!repost']}</pre>"
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.TEXT,
|
||||
format=Format.HTML,
|
||||
formatted_body=reply)
|
||||
reply = await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
return None, None
|
||||
reply = cmd_stream_repost(args[1])
|
||||
reply = f'{event['sender']} {reply}'
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.NOTICE,
|
||||
body=reply)
|
||||
await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
return None, None
|
||||
|
||||
elif args[0] == '!bookmark':
|
||||
if len(args) < 2:
|
||||
reply = f'{event['sender']} invalid command!\n'
|
||||
reply += f"<pre>Usage: !bookmark {stream_commands['!bookmark']}</pre>"
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.TEXT,
|
||||
format=Format.HTML,
|
||||
formatted_body=reply)
|
||||
reply = await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
return None, None
|
||||
reply = cmd_stream_bookmark(args[1])
|
||||
reply = f'{event['sender']} {reply}'
|
||||
reply_msg = TextMessageEventContent(msgtype=MessageType.NOTICE,
|
||||
body=reply)
|
||||
await matrix_api.send_message(event['room_id'], reply_msg)
|
||||
return None, None
|
||||
|
||||
else:
|
||||
logging.debug('>>> unknown command')
|
||||
logging.debug(text)
|
||||
logging.debug(user)
|
||||
return None, None
|
||||
|
||||
def cmd_stream_reply(user, post_id, text, pnut_chan):
|
||||
logging.debug(f'>>> reply to {post_id}')
|
||||
logging.debug(text)
|
||||
|
||||
try:
|
||||
pnut_user, meta = pnutpy.api.get_user(user.pnut_user_id)
|
||||
|
||||
if pnut_chan == 0:
|
||||
orig, meta = pnutpy.api.get_post(post_id)
|
||||
|
||||
else:
|
||||
orig, meta = pnutpy.api.get_message(pnut_chan, post_id)
|
||||
|
||||
if orig.user.id != user.pnut_user_id:
|
||||
author = orig.user.username
|
||||
text = f"@{author} {text}"
|
||||
if 'content' in orig:
|
||||
cc_list = []
|
||||
for m in orig.content.entities.mentions:
|
||||
if m.text == author:
|
||||
continue
|
||||
if m.text == pnut_user.username:
|
||||
continue
|
||||
cc_list.append(f"@{m.text}")
|
||||
if len(cc_list) > 0:
|
||||
copy = " ".join(cc_list)
|
||||
text = f"{text} /{copy}"
|
||||
|
||||
return text
|
||||
|
||||
except pnutpy.errors.PnutAuthAPIException:
|
||||
logging.error('>>> PNUT AUTH ERROR!!!')
|
||||
|
||||
except Exception:
|
||||
logging.exception('!!!!!!!!!!!!!!!!!!!!!')
|
||||
|
||||
def cmd_stream_bookmark(post_id):
|
||||
logging.debug(f'>>> bookmark {post_id}')
|
||||
|
||||
try:
|
||||
orig, meta = pnutpy.api.get_post(post_id)
|
||||
logging.debug(f'>>> {orig.content.text}')
|
||||
|
||||
if orig.you_bookmarked:
|
||||
post, meta = pnutpy.api.unbookmark_post(post_id)
|
||||
return f'unbookmarked post {post.id}'
|
||||
|
||||
else:
|
||||
post, meta = pnutpy.api.bookmark_post(post_id)
|
||||
return f'bookmarked post {post.id}'
|
||||
|
||||
except pnutpy.errors.PnutAuthAPIException:
|
||||
logging.error('>>> PNUT AUTH ERROR!!!')
|
||||
|
||||
except Exception:
|
||||
logging.exception('!!!!!!!!!!!!!!!!!!!!!')
|
||||
|
||||
def cmd_stream_repost(post_id):
|
||||
logging.debug(f'>>> repost {post_id}')
|
||||
|
||||
try:
|
||||
orig, meta = pnutpy.api.get_post(post_id)
|
||||
logging.debug(f'>>> {orig.content.text}')
|
||||
|
||||
if orig.you_reposted:
|
||||
post, meta = pnutpy.api.unrepost_post(post_id)
|
||||
return f'unreposted post {post_id}'
|
||||
|
||||
else:
|
||||
post, meta = pnutpy.api.repost_post(post_id)
|
||||
return f'reposted post {post_id}'
|
||||
|
||||
except pnutpy.errors.PnutAuthAPIException:
|
||||
logging.error('>>> PNUT AUTH ERROR!!!')
|
||||
|
||||
except Exception:
|
||||
logging.exception('!!!!!!!!!!!!!!!!!!!!!')
|
||||
|
||||
async def on_control_message(event, user):
|
||||
as_id = (f"@{app.config['matrix']['sender_local']}:"
|
||||
f"{app.config['matrix']['domain']}")
|
||||
|
|
|
@ -196,7 +196,7 @@ async def new_pnut_post(post, meta):
|
|||
postlink = f"https://posts.pnut.io/{post.id}"
|
||||
plaintext = f"{post.content.text}\n{postlink}"
|
||||
htmltext = (f"{post.content.html}"
|
||||
f" <a href='{postlink}'>[🔗]</a>")
|
||||
f" <a href='{postlink}'>[🔗]</a> - {post.id}")
|
||||
eventtext = TextMessageEventContent(msgtype=MessageType.TEXT,
|
||||
format=Format.HTML,
|
||||
body=plaintext,
|
||||
|
|
Loading…
Add table
Reference in a new issue