Merge branch 'sync-upstream' into 'master'
Sync upstream See merge request cfdisk/hemppa!11
This commit is contained in:
commit
99e9934567
|
@ -25,6 +25,9 @@ Bot management commands.
|
||||||
* !bot reload - reload all bot modules (Must be done as bot owner)
|
* !bot reload - reload all bot modules (Must be done as bot owner)
|
||||||
* !bot stats - show statistics on matrix users seen by bot
|
* !bot stats - show statistics on matrix users seen by bot
|
||||||
* !bot leave - ask bot to leave this room (Must be done as admin in room)
|
* !bot leave - ask bot to leave this room (Must be done as admin in room)
|
||||||
|
* !bot modules - list all modules including enabled status
|
||||||
|
* !bot enable [module] - enable module (Must be done as admin in room)
|
||||||
|
* !bot disable [module] - disable module (Must be done as admin in room)
|
||||||
|
|
||||||
### Help
|
### Help
|
||||||
|
|
||||||
|
|
9
bot.py
9
bot.py
|
@ -75,6 +75,12 @@ class Bot:
|
||||||
}
|
}
|
||||||
await self.client.room_send(room.room_id, 'm.room.message', msg)
|
await self.client.room_send(room.room_id, 'm.room.message', msg)
|
||||||
|
|
||||||
|
def remove_callback(self, callback):
|
||||||
|
for cb_object in bot.client.event_callbacks:
|
||||||
|
if cb_object.func == callback:
|
||||||
|
self.logger.info("remove callback")
|
||||||
|
bot.client.event_callbacks.remove(cb_object)
|
||||||
|
|
||||||
def get_room_by_id(self, room_id):
|
def get_room_by_id(self, room_id):
|
||||||
return self.client.rooms[room_id]
|
return self.client.rooms[room_id]
|
||||||
|
|
||||||
|
@ -148,7 +154,8 @@ class Bot:
|
||||||
except CommandRequiresOwner:
|
except CommandRequiresOwner:
|
||||||
await self.send_text(room, f'Sorry, only bot owner can run that command.')
|
await self.send_text(room, f'Sorry, only bot owner can run that command.')
|
||||||
except Exception:
|
except Exception:
|
||||||
await self.send_text(room, f'Module {command} experienced difficulty: {sys.exc_info()[0]} - see log for details')
|
await self.send_text(room,
|
||||||
|
f'Module {command} experienced difficulty: {sys.exc_info()[0]} - see log for details')
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Unknown command: {command}")
|
self.logger.error(f"Unknown command: {command}")
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import collections
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from modules.common.module import BotModule
|
from modules.common.module import BotModule
|
||||||
|
@ -7,7 +8,8 @@ class MatrixModule(BotModule):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
super().__init__(name)
|
super().__init__(name)
|
||||||
self.enable()
|
self.starttime = None
|
||||||
|
self.can_be_disabled = False
|
||||||
|
|
||||||
def matrix_start(self, bot):
|
def matrix_start(self, bot):
|
||||||
super().matrix_start(bot)
|
super().matrix_start(bot)
|
||||||
|
@ -16,6 +18,7 @@ class MatrixModule(BotModule):
|
||||||
async def matrix_message(self, bot, room, event):
|
async def matrix_message(self, bot, room, event):
|
||||||
args = event.body.split()
|
args = event.body.split()
|
||||||
if len(args) == 2:
|
if len(args) == 2:
|
||||||
|
|
||||||
if args[1] == 'quit':
|
if args[1] == 'quit':
|
||||||
await self.quit(bot, room, event)
|
await self.quit(bot, room, event)
|
||||||
elif args[1] == 'version':
|
elif args[1] == 'version':
|
||||||
|
@ -89,6 +92,7 @@ class MatrixModule(BotModule):
|
||||||
async def enable_module(self, bot, room, event, module_name):
|
async def enable_module(self, bot, room, event, module_name):
|
||||||
bot.must_be_admin(room, event)
|
bot.must_be_admin(room, event)
|
||||||
self.logger.info(f"asked to enable {module_name}")
|
self.logger.info(f"asked to enable {module_name}")
|
||||||
|
|
||||||
if bot.modules.get(module_name):
|
if bot.modules.get(module_name):
|
||||||
module = bot.modules.get(module_name)
|
module = bot.modules.get(module_name)
|
||||||
module.enable()
|
module.enable()
|
||||||
|
@ -101,20 +105,26 @@ class MatrixModule(BotModule):
|
||||||
async def disable_module(self, bot, room, event, module_name):
|
async def disable_module(self, bot, room, event, module_name):
|
||||||
bot.must_be_admin(room, event)
|
bot.must_be_admin(room, event)
|
||||||
self.logger.info(f"asked to disable {module_name}")
|
self.logger.info(f"asked to disable {module_name}")
|
||||||
|
|
||||||
if bot.modules.get(module_name):
|
if bot.modules.get(module_name):
|
||||||
module = bot.modules.get(module_name)
|
module = bot.modules.get(module_name)
|
||||||
module.disable()
|
if module.can_be_disabled:
|
||||||
module.matrix_stop(bot)
|
module.disable()
|
||||||
bot.save_settings()
|
module.matrix_stop(bot)
|
||||||
await bot.send_text(room, f"module {module_name} disabled")
|
bot.save_settings()
|
||||||
|
await bot.send_text(room, f"module {module_name} disabled")
|
||||||
|
else:
|
||||||
|
await bot.send_text(room, f"module {module_name} cannot be disabled")
|
||||||
else:
|
else:
|
||||||
await bot.send_text(room, f"module with name {module_name} not found. execute !bot modules for a list of available modules")
|
await bot.send_text(room, f"module with name {module_name} not found. execute !bot modules for a list of available modules")
|
||||||
|
|
||||||
|
|
||||||
async def show_modules(self, bot, room):
|
async def show_modules(self, bot, room):
|
||||||
await bot.send_text(room, "Modules:\n")
|
await bot.send_text(room, "Modules:\n")
|
||||||
for modulename, module in bot.modules.items():
|
for modulename, module in collections.OrderedDict(sorted(bot.modules.items())).items():
|
||||||
await bot.send_text(room, f"Name: {modulename:20s} Enabled: {module.enabled}")
|
module_message = f"Name: {modulename}\n"\
|
||||||
|
f"Enabled: {module.enabled} (Can be disabled: {module.can_be_disabled})\n"\
|
||||||
|
f"Description: {module.help()}\n"
|
||||||
|
await bot.send_text(room, module_message)
|
||||||
|
|
||||||
def help(self):
|
def help(self):
|
||||||
return 'Bot management commands. (quit, version, reload, status, stats, leave, modules, enable, disable)'
|
return 'Bot management commands. (quit, version, reload, status, stats, leave, modules, enable, disable)'
|
||||||
|
|
|
@ -28,7 +28,8 @@ class BotModule(ABC):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.enabled = False
|
self.enabled = True
|
||||||
|
self.can_be_disabled = True
|
||||||
self.name = name
|
self.name = name
|
||||||
self.logger = logging.getLogger("module " + self.name)
|
self.logger = logging.getLogger("module " + self.name)
|
||||||
|
|
||||||
|
@ -82,7 +83,7 @@ class BotModule(ABC):
|
||||||
:return: a dict object that can be converted to JSON
|
:return: a dict object that can be converted to JSON
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
return {'enabled': self.enabled}
|
return {'enabled': self.enabled, 'can_be_disabled': self.can_be_disabled}
|
||||||
|
|
||||||
def set_settings(self, data):
|
def set_settings(self, data):
|
||||||
"""Load these settings. It should be the same JSON you returned in previous get_settings
|
"""Load these settings. It should be the same JSON you returned in previous get_settings
|
||||||
|
@ -90,8 +91,10 @@ class BotModule(ABC):
|
||||||
:param data: a dict object containing the settings read from the account
|
:param data: a dict object containing the settings read from the account
|
||||||
:type data: dict
|
:type data: dict
|
||||||
"""
|
"""
|
||||||
if data.get('enabled'):
|
if data.get('enabled') is not None:
|
||||||
self.enabled = data['enabled']
|
self.enabled = data['enabled']
|
||||||
|
if data.get('can_be_disabled') is not None:
|
||||||
|
self.can_be_disabled = data['can_be_disabled']
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
|
|
|
@ -105,9 +105,12 @@ class PollingService(BotModule):
|
||||||
await bot.send_text(room, f'Removed {self.service_name} account from this room')
|
await bot.send_text(room, f'Removed {self.service_name} account from this room')
|
||||||
|
|
||||||
def get_settings(self):
|
def get_settings(self):
|
||||||
return {'account_rooms': self.account_rooms}
|
data = super().get_settings()
|
||||||
|
data['account_rooms'] = self.account_rooms
|
||||||
|
return data
|
||||||
|
|
||||||
def set_settings(self, data):
|
def set_settings(self, data):
|
||||||
|
super().set_settings(data)
|
||||||
if data.get('account_rooms'):
|
if data.get('account_rooms'):
|
||||||
self.account_rooms = data['account_rooms']
|
self.account_rooms = data['account_rooms']
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ from modules.common.module import BotModule
|
||||||
|
|
||||||
class MatrixModule(BotModule):
|
class MatrixModule(BotModule):
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
super().__init__(name)
|
|
||||||
self.enable()
|
|
||||||
|
|
||||||
async def matrix_message(self, bot, room, event):
|
async def matrix_message(self, bot, room, event):
|
||||||
msg = f'This is Hemppa {bot.version}, a generic Matrix bot. Known commands:\n\n'
|
msg = f'This is Hemppa {bot.version}, a generic Matrix bot. Known commands:\n\n'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from geopy.geocoders import Nominatim
|
from geopy.geocoders import Nominatim
|
||||||
from nio import RoomMessageUnknown
|
from nio import RoomMessageUnknown, AsyncClient
|
||||||
|
from modules.common.module import BotModule
|
||||||
|
|
||||||
from modules.common.module import BotModule
|
from modules.common.module import BotModule
|
||||||
|
|
||||||
|
@ -12,6 +13,10 @@ class MatrixModule(BotModule):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
bot.client.add_event_callback(self.unknown_cb, RoomMessageUnknown)
|
bot.client.add_event_callback(self.unknown_cb, RoomMessageUnknown)
|
||||||
|
|
||||||
|
def matrix_stop(self, bot):
|
||||||
|
super().matrix_stop(bot)
|
||||||
|
bot.remove_callback(self.unknown_cb)
|
||||||
|
|
||||||
async def unknown_cb(self, room, event):
|
async def unknown_cb(self, room, event):
|
||||||
if event.msgtype != 'm.location':
|
if event.msgtype != 'm.location':
|
||||||
return
|
return
|
||||||
|
|
|
@ -20,14 +20,17 @@ from modules.common.module import BotModule
|
||||||
|
|
||||||
|
|
||||||
class MatrixModule(BotModule):
|
class MatrixModule(BotModule):
|
||||||
def matrix_start(self, bot):
|
def __init__(self, name):
|
||||||
super().matrix_start(bot)
|
super().__init__(name)
|
||||||
self.bot = bot
|
|
||||||
self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
|
|
||||||
self.credentials_file = "credentials.json"
|
self.credentials_file = "credentials.json"
|
||||||
|
self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
|
||||||
|
self.bot = None
|
||||||
self.service = None
|
self.service = None
|
||||||
self.calendar_rooms = dict() # Contains room_id -> [calid, calid] ..
|
self.calendar_rooms = dict() # Contains room_id -> [calid, calid] ..
|
||||||
|
|
||||||
|
def matrix_start(self, bot):
|
||||||
|
super().matrix_start(bot)
|
||||||
|
self.bot = bot
|
||||||
creds = None
|
creds = None
|
||||||
|
|
||||||
if not os.path.exists(self.credentials_file) or os.path.getsize(self.credentials_file) == 0:
|
if not os.path.exists(self.credentials_file) or os.path.getsize(self.credentials_file) == 0:
|
||||||
|
@ -43,8 +46,7 @@ class MatrixModule(BotModule):
|
||||||
if creds and creds.expired and creds.refresh_token:
|
if creds and creds.expired and creds.refresh_token:
|
||||||
creds.refresh(Request())
|
creds.refresh(Request())
|
||||||
else:
|
else:
|
||||||
flow = InstalledAppFlow.from_client_secrets_file(
|
flow = InstalledAppFlow.from_client_secrets_file(self.credentials_file, self.SCOPES)
|
||||||
self.credentials_file, self.SCOPES)
|
|
||||||
# urn:ietf:wg:oauth:2.0:oob
|
# urn:ietf:wg:oauth:2.0:oob
|
||||||
creds = flow.run_local_server(port=0)
|
creds = flow.run_local_server(port=0)
|
||||||
# Save the credentials for the next run
|
# Save the credentials for the next run
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime
|
||||||
|
|
||||||
from pyteamup import Calendar
|
from pyteamup import Calendar
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# TeamUp calendar notifications
|
# TeamUp calendar notifications
|
||||||
#
|
#
|
||||||
|
|
|
@ -4,7 +4,7 @@ from functools import lru_cache
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from nio import RoomMessageText
|
from nio import RoomMessageText, AsyncClient
|
||||||
|
|
||||||
from modules.common.module import BotModule
|
from modules.common.module import BotModule
|
||||||
|
|
||||||
|
@ -16,15 +16,18 @@ class MatrixModule(BotModule):
|
||||||
Everytime a url is seen in a message we do http request to it and try to get a title tag contents to spit out to the room.
|
Everytime a url is seen in a message we do http request to it and try to get a title tag contents to spit out to the room.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bot = None
|
def __init__(self, name):
|
||||||
status = dict() # room_id -> what to do with urls
|
super().__init__(name)
|
||||||
|
|
||||||
STATUSES = {
|
self.bot = None
|
||||||
"OFF": "Not spamming this channel",
|
self.status = dict() # room_id -> what to do with urls
|
||||||
"TITLE": "Spamming this channel with titles",
|
|
||||||
"DESCRIPTION": "Spamming this channel with descriptions",
|
self.STATUSES = {
|
||||||
"BOTH": "Spamming this channel with both title and description",
|
"OFF": "Not spamming this channel",
|
||||||
}
|
"TITLE": "Spamming this channel with titles",
|
||||||
|
"DESCRIPTION": "Spamming this channel with descriptions",
|
||||||
|
"BOTH": "Spamming this channel with both title and description",
|
||||||
|
}
|
||||||
|
|
||||||
def matrix_start(self, bot):
|
def matrix_start(self, bot):
|
||||||
"""
|
"""
|
||||||
|
@ -34,6 +37,10 @@ class MatrixModule(BotModule):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
bot.client.add_event_callback(self.text_cb, RoomMessageText)
|
bot.client.add_event_callback(self.text_cb, RoomMessageText)
|
||||||
|
|
||||||
|
def matrix_stop(self, bot):
|
||||||
|
super().matrix_stop(bot)
|
||||||
|
bot.remove_callback(self.text_cb)
|
||||||
|
|
||||||
async def text_cb(self, room, event):
|
async def text_cb(self, room, event):
|
||||||
"""
|
"""
|
||||||
Handle client callbacks for all room text events
|
Handle client callbacks for all room text events
|
||||||
|
@ -103,7 +110,7 @@ class MatrixModule(BotModule):
|
||||||
# try parse and get the title
|
# try parse and get the title
|
||||||
try:
|
try:
|
||||||
soup = BeautifulSoup(r.text, "html.parser")
|
soup = BeautifulSoup(r.text, "html.parser")
|
||||||
title = soup.title.string
|
title = soup.title.string.strip()
|
||||||
descr_tag = soup.find("meta", attrs={"name": "description"})
|
descr_tag = soup.find("meta", attrs={"name": "description"})
|
||||||
if descr_tag:
|
if descr_tag:
|
||||||
description = descr_tag.get("content", None)
|
description = descr_tag.get("content", None)
|
||||||
|
|
Loading…
Reference in New Issue