Mastodon: Per-room accounts

This commit is contained in:
Ville Ranki 2020-09-12 16:48:47 +03:00
parent 28db0fedfe
commit e372aadd73
3 changed files with 100 additions and 24 deletions

View File

@ -364,22 +364,33 @@ sends different messages and has not yet been reverse engineered.
### Mastodon
Send toots to Mastodon. You can login to Mastodon with the bot and toot with it. Login is
personal - it's mapped to your Matrix ID. You can limit usage to bot owners only or make
it public.
Send toots to Mastodon. You can login to Mastodon with the bot and toot with it.
Normal login is personal - it's mapped to your Matrix ID.
Room login overrides personal login and allows room admins to toot to a specified account.
You can limit usage to bot owners only or make it public.
Note: You can subscribe to Mastodon users and hashtags with stock RSS feed integration.
Mastodon generates the feeds automatically!
Commands:
* !md status - print status of mastodon module
* !md login [instanceurl] [e-mail] [password] - log in your Matrix user to mastodon instance (do in private chat!)
* !md toot [toot] - send a toot message
* !md login [instanceurl] [e-mail] [password] - log in your Matrix user to Mastodon instance (do in private chat!)
* !md toot [toot] - send a toot message. If said in a room with per-room login, user must be a admin in the room.
* !md logout - log out the user from the bot
* !md roomlogin [room] [instanceurl] [e-mail] [password] - same as login but assigns a per-room mastodon account (do in private chat!)
* !md roomlogout - Say in a room that has per-room login. This will remove the login. Must be room admin.
* !md setpublic - ANY user can use login command to login and use the Mastodon module (must be done as bot owner)
* !md setprivate - Only bot owners can use Mastodon module (default) (must be done as bot owner)
* !md clear - Clear all logins from the bot
Example of login command:
Example commands:
* !md login https://my.instance/ me@email.invalid r00tm3
* !md roomlogin #myroom:matrix.org https://my.instance/ me@email.invalid r00tm3
## Bot setup

13
bot.py
View File

@ -19,7 +19,7 @@ import datetime
from importlib import reload
import requests
from nio import AsyncClient, InviteEvent, JoinError, RoomMessageText, MatrixRoom, LoginError, RoomMemberEvent, RoomVisibility, RoomPreset, RoomCreateError
from nio import AsyncClient, InviteEvent, JoinError, RoomMessageText, MatrixRoom, LoginError, RoomMemberEvent, RoomVisibility, RoomPreset, RoomCreateError, RoomResolveAliasResponse
# Couple of custom exceptions
@ -37,7 +37,7 @@ class Bot:
def __init__(self):
self.appid = 'org.vranki.hemppa'
self.version = '1.4'
self.version = '1.5'
self.client = None
self.join_on_invite = False
self.modules = dict()
@ -144,6 +144,12 @@ class Bot:
def get_room_by_id(self, room_id):
return self.client.rooms[room_id]
async def get_room_by_alias(self, alias):
rar = await self.client.room_resolve_alias(alias)
if type(rar) is RoomResolveAliasResponse:
return rar.room_id
return None
# Throws exception if event sender is not a room admin
def must_be_admin(self, room, event):
if not self.is_admin(room, event):
@ -197,7 +203,8 @@ class Bot:
async def message_cb(self, room, event):
# Ignore if asked to ignore
if self.should_ignore_event(event):
print('Ignoring this!')
if self.debug:
print('Ignoring event!')
return
body = event.body

View File

@ -6,6 +6,7 @@ from modules.common.module import BotModule
class MatrixModule(BotModule):
apps = dict() # instance url <-> [app_id, app_secret]
logins = dict() # mxid <-> [username, accesstoken, instanceurl]
roomlogins = dict() # roomid <-> [username, accesstoken, instanceurl]
public = False
async def matrix_message(self, bot, room, event):
@ -16,16 +17,22 @@ class MatrixModule(BotModule):
if not self.public:
bot.must_be_owner(event)
toot_body = " ".join(args[1:])
if event.sender in self.logins.keys():
accesstoken = None
if room.room_id in self.roomlogins.keys():
bot.must_be_admin(room, event)
username = self.roomlogins[room.room_id][0]
accesstoken = self.roomlogins[room.room_id][1]
instanceurl = self.roomlogins[room.room_id][2]
elif event.sender in self.logins.keys():
username = self.logins[event.sender][0]
accesstoken = self.logins[event.sender][1]
instanceurl = self.logins[event.sender][2]
if accesstoken:
toottodon = Mastodon(
access_token = accesstoken,
api_base_url = instanceurl
)
tootdict = toottodon.toot(toot_body)
print(tootdict)
await bot.send_text(room, tootdict['url'])
else:
await bot.send_text(room, f'{event.sender} has not logged in yet with the bot. Please do so.')
@ -39,27 +46,55 @@ class MatrixModule(BotModule):
instanceurl = args[1]
username = args[2]
password = args[3]
if not instanceurl in self.apps.keys():
app = Mastodon.create_app(f'Hemppa The Bot - {bot.client.user}', api_base_url = instanceurl)
self.apps[instanceurl] = [app[0], app[1]]
bot.save_settings()
await bot.send_text(room, f'Registered Mastodon app on {instanceurl}')
mastodon = Mastodon(client_id = self.apps[instanceurl][0], client_secret = self.apps[instanceurl][1], api_base_url = instanceurl)
access_token = mastodon.log_in(username, password)
self.logins[mxid] = [username, access_token, instanceurl]
bot.save_settings()
await bot.send_text(room, f'Logged into {instanceurl} as {username}')
await self.register_app_if_necessary(bot, room, instanceurl)
await self.login_to_account(bot, room, mxid, None, instanceurl, username, password)
return
if len(args) == 5:
if args[0] == "roomlogin":
if not self.public:
bot.must_be_owner(event)
roomalias = args[1]
instanceurl = args[2]
username = args[3]
password = args[4]
roomid = await bot.get_room_by_alias(roomalias)
if roomid:
await self.register_app_if_necessary(bot, room, instanceurl)
await self.login_to_account(bot, room, None, roomid, instanceurl, username, password)
else:
await bot.send_text(room, f'Unknown room alias {roomalias} - invite bot to the room first.')
return
if len(args) == 1:
if args[0] == "status":
await bot.send_text(room, f'{len(self.logins)} users logged in, app registered on {len(self.apps)} instances, public use enabled: {self.public}')
out = f'App registered on {len(self.apps)} instances, public use enabled: {self.public}\n'
out = out + f'{len(self.logins)} users logged in:\n'
for login in self.logins.keys():
out = out + f' - {login} as {self.logins[login][0]} on {self.logins[login][2]}\n'
out = out + f'{len(self.roomlogins)} per-room logins:\n'
for roomlogin in self.roomlogins:
out = out + f' - {roomlogin} as {self.roomlogins[roomlogin][0]} on {self.roomlogins[roomlogin][2]}\n'
await bot.send_text(room, out)
if args[0] == "logout":
if event.sender in self.logins.keys():
# TODO: Is there a way to invalidate the access token with API?
del self.logins[event.sender]
bot.save_settings()
await bot.send_text(room, f'{event.sender} login data removed from the bot.')
if args[0] == "roomlogout":
bot.must_be_admin(room, event)
if room.room_id in self.roomlogins.keys():
del self.roomlogins[room.room_id]
bot.save_settings()
await bot.send_text(room, f'Login data for this room removed from the bot.')
else:
await bot.send_text(room, f'No login found for room id {room.room_id}.')
if args[0] == "clear":
bot.must_be_owner(event)
self.logins = dict()
self.roomlogins = dict()
bot.save_settings()
await bot.send_text(room, f'All Mastodon logins cleared')
if args[0] == "setpublic":
bot.must_be_owner(event)
self.public = True
@ -71,10 +106,31 @@ class MatrixModule(BotModule):
bot.save_settings()
await bot.send_text(room, f'Mastodon usage is now restricted to bot owners')
async def register_app_if_necessary(self, bot, room, instanceurl):
if not instanceurl in self.apps.keys():
app = Mastodon.create_app(f'Hemppa The Bot - {bot.client.user}', api_base_url = instanceurl)
self.apps[instanceurl] = [app[0], app[1]]
bot.save_settings()
await bot.send_text(room, f'Registered Mastodon app on {instanceurl}')
async def login_to_account(self, bot, room, mxid, roomid, instanceurl, username, password):
mastodon = Mastodon(client_id = self.apps[instanceurl][0], client_secret = self.apps[instanceurl][1], api_base_url = instanceurl)
access_token = mastodon.log_in(username, password)
print('login_To_account', mxid, roomid)
if mxid:
self.logins[mxid] = [username, access_token, instanceurl]
await bot.send_text(room, f'Logged Matrix user {mxid} into {instanceurl} as {username}')
elif roomid:
self.roomlogins[roomid] = [username, access_token, instanceurl]
await bot.send_text(room, f'Set room {roomid} Mastodon user to {username} on {instanceurl}')
bot.save_settings()
def get_settings(self):
data = super().get_settings()
data['apps'] = self.apps
data['logins'] = self.logins
data['roomlogins'] = self.roomlogins
data['public'] = self.public
return data
@ -84,6 +140,8 @@ class MatrixModule(BotModule):
self.apps = data["apps"]
if data.get("logins"):
self.logins = data["logins"]
if data.get("roomlogins"):
self.roomlogins = data["roomlogins"]
if data.get("public"):
self.public = data["public"]