Merge pull request #42 from ancho/feature/logging-and-signals
use logging and register signal handlers
This commit is contained in:
commit
3ce0d80ecb
|
@ -10,6 +10,9 @@ RUN pip install pipenv && \
|
|||
rm -r /root/.local/*
|
||||
|
||||
COPY bot.py *.json *.pickle /bot/
|
||||
COPY config config
|
||||
COPY modules modules
|
||||
|
||||
VOLUME /bot/config
|
||||
|
||||
CMD [ "python", "-u", "./bot.py" ]
|
||||
|
|
1
Pipfile
1
Pipfile
|
@ -15,6 +15,7 @@ requests = "*"
|
|||
igramscraper = "*"
|
||||
twitterscraper = "*"
|
||||
httpx = "*"
|
||||
pyyaml = "==5.3"
|
||||
|
||||
[dev-packages]
|
||||
pylint = "*"
|
||||
|
|
46
README.md
46
README.md
|
@ -215,6 +215,7 @@ MATRIX_ACCESS_TOKEN=MDAxOGxvYlotofcharacters53CgYAYFgo
|
|||
MATRIX_SERVER=https://matrix.org
|
||||
JOIN_ON_INVITE=True
|
||||
BOT_OWNERS=@user1:matrix.org,@user2:matrix.org
|
||||
DEBUG=False
|
||||
```
|
||||
|
||||
Note: without quotes!
|
||||
|
@ -227,21 +228,48 @@ docker-compose up
|
|||
|
||||
## Env variables
|
||||
|
||||
User, access token and server should be self-explanatory. Set JOIN_ON_INVITE to anything if you want the bot to
|
||||
join invites automatically (do not set it if you don't want it to join).
|
||||
`MATRIX_USER`, `MATRIX_ACCESS_TOKEN` and `MATRIX_SERVER` should be self-explanatory.
|
||||
Set `JOIN_ON_INVITE` to anything if you want the bot to join invites automatically (do not set it if you don't want it to join).
|
||||
|
||||
You can set MATRIX_PASSWORD if you want to get access token. Normally you can use Riot to get it.
|
||||
You can set `MATRIX_PASSWORD` if you want to get an access token automatically with a login.
|
||||
Normally you can use Riot to get it.
|
||||
|
||||
BOT_OWNERS is a comma-separated list of matrix id's for the owners of the bot. Some commands require
|
||||
sender to be bot owner. Typically set your own id into it. Don't include bot itself in BOT_OWNERS if cron
|
||||
or any other module that can cause bot to send custom commands is used as it could potentially be used to run
|
||||
owner commands as the bot itself.
|
||||
`BOT_OWNERS` is a comma-separated list of matrix id's for the owners of the bot.
|
||||
Some commands require sender to be bot owner.
|
||||
Typically set your own id into it.
|
||||
|
||||
__*ATTENTION:*__ Don't include bot itself in `BOT_OWNERS` if cron or any other module that can cause bot to send custom commands is used, as it could potentially be used to run owner commands as the bot itself.
|
||||
|
||||
To enable debugging for the root logger set `DEBUG=True`.
|
||||
|
||||
## Logging
|
||||
|
||||
Uses [python logging facility](https://docs.python.org/3/library/logging.html) to print information to the console. Customize it to your needs editing `config/logging.yml`.
|
||||
See [logging.config documentation](https://docs.python.org/3/library/logging.config.html) for further information.
|
||||
|
||||
## Module API
|
||||
|
||||
Just write a python file with desired command name and place it in modules. See current modules for
|
||||
examples. No need to register it anywhere else.
|
||||
|
||||
*Simple skeleton for a bot module:*
|
||||
```python
|
||||
|
||||
class MatrixModule(BotModule):
|
||||
|
||||
async def matrix_message(self, bot, room, event):
|
||||
args = event.body.split()
|
||||
args.pop(0)
|
||||
|
||||
# Echo what they said back
|
||||
self.logger.debug(f"room: {room.name} sender: {event.sender} wants an echo")
|
||||
await bot.send_text(room, ' '.join(args))
|
||||
|
||||
def help(self):
|
||||
return 'Echoes back what user has said'
|
||||
|
||||
```
|
||||
|
||||
Functions:
|
||||
|
||||
* matrix_start - Called once on startup
|
||||
|
@ -254,6 +282,10 @@ Functions:
|
|||
|
||||
You only need to implement the ones you need. See existing bots for examples.
|
||||
|
||||
Logging:
|
||||
|
||||
Use `self.logger` in your module to print information to the console.
|
||||
|
||||
Module settings are stored in Matrix account data.
|
||||
|
||||
If you write a new module, please make a PR if it's something useful for others.
|
||||
|
|
152
bot.py
152
bot.py
|
@ -1,14 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import glob
|
||||
import importlib
|
||||
import json
|
||||
import yaml
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
import traceback
|
||||
import urllib.parse
|
||||
import logging
|
||||
import logging.config
|
||||
from importlib import reload
|
||||
|
||||
import requests
|
||||
|
@ -28,14 +32,38 @@ class CommandRequiresOwner(Exception):
|
|||
|
||||
|
||||
class Bot:
|
||||
appid = 'org.vranki.hemppa'
|
||||
version = '1.2'
|
||||
client = None
|
||||
join_on_invite = False
|
||||
modules = dict()
|
||||
pollcount = 0
|
||||
poll_task = None
|
||||
owners = []
|
||||
|
||||
def __init__(self):
|
||||
self.appid = 'org.vranki.hemppa'
|
||||
self.version = '1.2'
|
||||
self.client = None
|
||||
self.join_on_invite = False
|
||||
self.modules = dict()
|
||||
self.pollcount = 0
|
||||
self.poll_task = None
|
||||
self.owners = []
|
||||
self.debug = os.getenv("DEBUG", "false").lower() == "true"
|
||||
self.logger = None
|
||||
|
||||
self.initialize_logger()
|
||||
|
||||
def initialize_logger(self):
|
||||
|
||||
if os.path.exists('config/logging.yml'):
|
||||
with open('config/logging.yml') as f:
|
||||
config = yaml.load(f, Loader=yaml.Loader)
|
||||
logging.config.dictConfig(config)
|
||||
else:
|
||||
log_format = '%(levelname)s - %(name)s - %(message)s'
|
||||
logging.basicConfig(format=log_format)
|
||||
|
||||
self.logger = logging.getLogger("hemppa")
|
||||
|
||||
if self.debug:
|
||||
logging.root.setLevel(logging.DEBUG)
|
||||
self.logger.info("enabled debugging")
|
||||
|
||||
self.logger.debug("Logger initialized")
|
||||
|
||||
async def send_text(self, room, body):
|
||||
msg = {
|
||||
|
@ -54,10 +82,10 @@ class Bot:
|
|||
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:
|
||||
for cb_object in self.client.event_callbacks:
|
||||
if cb_object.func == callback:
|
||||
print("remove callback")
|
||||
bot.client.event_callbacks.remove(cb_object)
|
||||
self.logger.info("remove callback")
|
||||
self.client.event_callbacks.remove(cb_object)
|
||||
|
||||
def get_room_by_id(self, room_id):
|
||||
return self.client.rooms[room_id]
|
||||
|
@ -124,17 +152,16 @@ class Bot:
|
|||
if moduleobject is not None:
|
||||
if moduleobject.enabled:
|
||||
try:
|
||||
await moduleobject.matrix_message(bot, room, event)
|
||||
await moduleobject.matrix_message(self, room, event)
|
||||
except CommandRequiresAdmin:
|
||||
await self.send_text(room, f'Sorry, you need admin power level in this room to run that command.')
|
||||
except CommandRequiresOwner:
|
||||
await self.send_text(room, f'Sorry, only bot owner can run that command.')
|
||||
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)
|
||||
else:
|
||||
print(f"Unknown command: {command}")
|
||||
self.logger.error(f"Unknown command: {command}")
|
||||
# TODO Make this configurable
|
||||
# await self.send_text(room,
|
||||
# f"Sorry. I don't know what to do. Execute !help to get a list of available commands.")
|
||||
|
@ -152,31 +179,28 @@ class Bot:
|
|||
for attempt in range(3):
|
||||
result = await self.client.join(room.room_id)
|
||||
if type(result) == JoinError:
|
||||
print(f"Error joining room {room.room_id} (attempt %d): %s",
|
||||
attempt, result.message,
|
||||
)
|
||||
self.logger.error(f"Error joining room %s (attempt %d): %s", room.room_id, attempt, result.message)
|
||||
else:
|
||||
print(f"joining room '{room.display_name}'({room.room_id}) invited by '{event.sender}'")
|
||||
self.logger.info(f"joining room '{room.display_name}'({room.room_id}) invited by '{event.sender}'")
|
||||
break
|
||||
else:
|
||||
print(
|
||||
f'Received invite event, but not joining as sender is not owner or bot not configured to join on invite. {event}')
|
||||
self.logger.warning(f'Received invite event, but not joining as sender is not owner or bot not configured to join on invite. {event}')
|
||||
|
||||
def load_module(self, modulename):
|
||||
try:
|
||||
print("load module: " + modulename)
|
||||
self.logger.info(f'load module: {modulename}')
|
||||
module = importlib.import_module('modules.' + modulename)
|
||||
module = reload(module)
|
||||
cls = getattr(module, 'MatrixModule')
|
||||
return cls(modulename)
|
||||
except ModuleNotFoundError:
|
||||
print('Module ', modulename, ' failed to load!')
|
||||
self.logger.error(f'Module {modulename} failed to load!')
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
return None
|
||||
|
||||
def reload_modules(self):
|
||||
for modulename in bot.modules:
|
||||
print('Reloading', modulename, '..')
|
||||
self.logger.info(f'Reloading {modulename} ..')
|
||||
self.modules[modulename] = self.load_module(modulename)
|
||||
|
||||
self.load_settings(self.get_account_data())
|
||||
|
@ -199,7 +223,7 @@ class Bot:
|
|||
for modulename, moduleobject in self.modules.items():
|
||||
if moduleobject.enabled:
|
||||
try:
|
||||
await moduleobject.matrix_poll(bot, self.pollcount)
|
||||
await moduleobject.matrix_poll(self, self.pollcount)
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
await asyncio.sleep(10)
|
||||
|
@ -213,7 +237,7 @@ class Bot:
|
|||
self.__handle_error_response(response)
|
||||
|
||||
if response.status_code != 200:
|
||||
print('Setting account data failed:', response, response.json())
|
||||
self.logger.error('Setting account data failed. response: %s json: %s', response, response.json())
|
||||
|
||||
def get_account_data(self):
|
||||
userid = urllib.parse.quote(self.matrix_user)
|
||||
|
@ -225,14 +249,13 @@ class Bot:
|
|||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
print(
|
||||
f'Getting account data failed: {response} {response.json()} - this is normal if you have not saved any settings yet.')
|
||||
self.logger.error(f'Getting account data failed: {response} {response.json()} - this is normal if you have not saved any settings yet.')
|
||||
return None
|
||||
|
||||
def __handle_error_response(self, response):
|
||||
if response.status_code == 401:
|
||||
print("ERROR: access token is invalid or missing")
|
||||
print("NOTE: check MATRIX_ACCESS_TOKEN or set MATRIX_PASSWORD")
|
||||
self.logger.error("access token is invalid or missing")
|
||||
self.logger.info("NOTE: check MATRIX_ACCESS_TOKEN or set MATRIX_PASSWORD")
|
||||
sys.exit(2)
|
||||
|
||||
def init(self):
|
||||
|
@ -250,7 +273,7 @@ class Bot:
|
|||
|
||||
if self.client.access_token is None:
|
||||
if self.matrix_pass is None:
|
||||
print("Either MATRIX_ACCESS_TOKEN or MATRIX_PASSWORD need to be set")
|
||||
self.logger.error("Either MATRIX_ACCESS_TOKEN or MATRIX_PASSWORD need to be set")
|
||||
sys.exit(1)
|
||||
|
||||
self.join_on_invite = join_on_invite is not None
|
||||
|
@ -258,25 +281,25 @@ class Bot:
|
|||
self.get_modules()
|
||||
|
||||
else:
|
||||
print("The environment variables MATRIX_SERVER, MATRIX_USER and BOT_OWNERS are mandatory")
|
||||
self.logger.error("The environment variables MATRIX_SERVER, MATRIX_USER and BOT_OWNERS are mandatory")
|
||||
sys.exit(1)
|
||||
|
||||
def start(self):
|
||||
self.load_settings(self.get_account_data())
|
||||
enabled_modules = [module for module_name, module in self.modules.items() if module.enabled]
|
||||
print(f'Starting {len(enabled_modules)} modules..')
|
||||
self.logger.info(f'Starting {len(enabled_modules)} modules..')
|
||||
for modulename, moduleobject in self.modules.items():
|
||||
if moduleobject.enabled:
|
||||
try:
|
||||
moduleobject.matrix_start(bot)
|
||||
moduleobject.matrix_start(self)
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
def stop(self):
|
||||
print(f'Stopping {len(self.modules)} modules..')
|
||||
self.logger.info(f'Stopping {len(self.modules)} modules..')
|
||||
for modulename, moduleobject in self.modules.items():
|
||||
try:
|
||||
moduleobject.matrix_stop(bot)
|
||||
moduleobject.matrix_stop(self)
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
|
@ -285,18 +308,18 @@ class Bot:
|
|||
login_response = await self.client.login(self.matrix_pass)
|
||||
|
||||
if isinstance(login_response, LoginError):
|
||||
print(f"Failed to login: {login_response.message}")
|
||||
self.logger.error(f"Failed to login: {login_response.message}")
|
||||
return
|
||||
|
||||
last_16 = self.client.access_token[-16:]
|
||||
print(f"Logged in with password, access token: ...{last_16}")
|
||||
self.logger.info(f"Logged in with password, access token: ...{last_16}")
|
||||
|
||||
await self.client.sync()
|
||||
for roomid, room in self.client.rooms.items():
|
||||
print(f"Bot is on '{room.display_name}'({roomid}) with {len(room.users)} users")
|
||||
self.logger.info(f"Bot is on '{room.display_name}'({roomid}) with {len(room.users)} users")
|
||||
if len(room.users) == 1:
|
||||
print(f'Room {roomid} has no other users - leaving it.')
|
||||
print(await self.client.room_leave(roomid))
|
||||
self.logger.info(f'Room {roomid} has no other users - leaving it.')
|
||||
self.logger.info(await self.client.room_leave(roomid))
|
||||
|
||||
self.start()
|
||||
|
||||
|
@ -308,12 +331,12 @@ class Bot:
|
|||
self.client.add_event_callback(self.invite_cb, (InviteEvent,))
|
||||
|
||||
if self.join_on_invite:
|
||||
print('Note: Bot will join rooms if invited')
|
||||
print('Bot running as', self.client.user, ', owners', self.owners)
|
||||
self.logger.info('Note: Bot will join rooms if invited')
|
||||
self.logger.info('Bot running as %s, owners %s', self.client.user, self.owners)
|
||||
self.bot_task = asyncio.create_task(self.client.sync_forever(timeout=30000))
|
||||
await self.bot_task
|
||||
else:
|
||||
print('Client was not able to log in, check env variables!')
|
||||
self.logger.error('Client was not able to log in, check env variables!')
|
||||
|
||||
async def shutdown(self):
|
||||
|
||||
|
@ -321,28 +344,43 @@ class Bot:
|
|||
logout = await self.client.logout()
|
||||
|
||||
if isinstance(logout, LogoutResponse):
|
||||
print("Logout successful")
|
||||
self.logger.info("Logout successful")
|
||||
try:
|
||||
await self.client.close()
|
||||
print("Connection closed")
|
||||
self.logger.info("Connection closed")
|
||||
except Exception as e:
|
||||
print("error while closing client", e)
|
||||
self.logger.error("error while closing client: %s", e)
|
||||
|
||||
else:
|
||||
logout: LogoutError
|
||||
print(f"Logout unsuccessful. msg: {logout.message}")
|
||||
self.logger.error(f"Logout unsuccessful. msg: {logout.message}")
|
||||
else:
|
||||
await self.client.client_session.close()
|
||||
|
||||
def handle_exit(self, signame, loop):
|
||||
self.logger.info(f"Received signal {signame}")
|
||||
if self.poll_task:
|
||||
self.poll_task.cancel()
|
||||
self.bot_task.cancel()
|
||||
self.stop()
|
||||
|
||||
|
||||
async def main():
|
||||
bot = Bot()
|
||||
bot.init()
|
||||
try:
|
||||
asyncio.get_event_loop().run_until_complete(bot.run())
|
||||
except KeyboardInterrupt:
|
||||
if bot.poll_task:
|
||||
bot.poll_task.cancel()
|
||||
bot.bot_task.cancel()
|
||||
|
||||
bot.stop()
|
||||
asyncio.get_event_loop().run_until_complete(bot.shutdown())
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
for signame in {'SIGINT', 'SIGTERM'}:
|
||||
loop.add_signal_handler(
|
||||
getattr(signal, signame),
|
||||
functools.partial(bot.handle_exit, signame, loop))
|
||||
|
||||
await bot.run()
|
||||
await bot.shutdown()
|
||||
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
version: 1
|
||||
formatters:
|
||||
hemppa:
|
||||
format: '%(asctime)s - %(levelname)s - %(name)s - %(message)s'
|
||||
handlers:
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
formatter: hemppa
|
||||
stream: ext://sys.stdout
|
||||
loggers:
|
||||
hemppa:
|
||||
level: NOTSET
|
||||
root:
|
||||
level: INFO
|
||||
handlers: [console]
|
|
@ -13,6 +13,10 @@ services:
|
|||
- MATRIX_SERVER
|
||||
- JOIN_ON_INVITE
|
||||
- BOT_OWNERS
|
||||
- DEBUG
|
||||
volumes:
|
||||
- ${PWD}/config/:/bot/config
|
||||
- ${PWD}/credentials.json:/bot/credentials.json
|
||||
- ${PWD}/token.pickle:/bot/token.pickle
|
||||
|
||||
stop_signal: SIGINT
|
||||
|
|
|
@ -48,7 +48,7 @@ class MatrixModule(BotModule):
|
|||
|
||||
async def leave(self, bot, room, event):
|
||||
bot.must_be_admin(room, event)
|
||||
print(f'{event.sender} asked bot to leave room {room.room_id}')
|
||||
self.logger.info(f'{event.sender} asked bot to leave room {room.room_id}')
|
||||
await bot.send_text(room, f'By your command.')
|
||||
await bot.client.room_leave(room.room_id)
|
||||
|
||||
|
@ -89,13 +89,12 @@ class MatrixModule(BotModule):
|
|||
async def quit(self, bot, room, event):
|
||||
bot.must_be_admin(room, event)
|
||||
await bot.send_text(room, f'Quitting, as requested')
|
||||
print(f'{event.sender} commanded bot to quit, so quitting..')
|
||||
self.logger.info(f'{event.sender} commanded bot to quit, so quitting..')
|
||||
bot.bot_task.cancel()
|
||||
|
||||
async def enable_module(self, bot, room, event, module_name):
|
||||
bot.must_be_admin(room, event)
|
||||
print(f"asked to enable {module_name}")
|
||||
|
||||
self.logger.info(f"asked to enable {module_name}")
|
||||
if bot.modules.get(module_name):
|
||||
module = bot.modules.get(module_name)
|
||||
module.enable()
|
||||
|
@ -107,8 +106,7 @@ class MatrixModule(BotModule):
|
|||
|
||||
async def disable_module(self, bot, room, event, module_name):
|
||||
bot.must_be_admin(room, event)
|
||||
print(f"asked to disable {module_name}")
|
||||
|
||||
self.logger.info(f"asked to disable {module_name}")
|
||||
if bot.modules.get(module_name):
|
||||
module = bot.modules.get(module_name)
|
||||
if module.can_be_disabled:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from nio import RoomMessageText, MatrixRoom
|
||||
|
@ -30,6 +31,7 @@ class BotModule(ABC):
|
|||
self.enabled = True
|
||||
self.can_be_disabled = True
|
||||
self.name = name
|
||||
self.logger = logging.getLogger("module " + self.name)
|
||||
|
||||
def matrix_start(self, bot):
|
||||
"""Called once on startup
|
||||
|
@ -37,7 +39,7 @@ class BotModule(ABC):
|
|||
:param bot: a reference to the bot
|
||||
:type bot: Bot
|
||||
"""
|
||||
print('Starting', self.name, '..')
|
||||
self.logger.info('Starting..')
|
||||
|
||||
@abstractmethod
|
||||
async def matrix_message(self, bot, room, event):
|
||||
|
@ -58,7 +60,7 @@ class BotModule(ABC):
|
|||
:param bot: a reference to the bot
|
||||
:type bot: Bot
|
||||
"""
|
||||
print('Stopping', self.name, '..')
|
||||
self.logger.info('Stopping..')
|
||||
|
||||
async def matrix_poll(self, bot, pollcount):
|
||||
"""Called every 10 seconds
|
||||
|
|
|
@ -33,7 +33,7 @@ class PollingService(BotModule):
|
|||
for account in accounts:
|
||||
await self.poll_account(bot, account, roomid, send_messages)
|
||||
else:
|
||||
print(f'Bot is no longer in room {roomid} - deleting it from {self.service_name} room list')
|
||||
self.logger.warning(f'Bot is no longer in room {roomid} - deleting it from {self.service_name} room list')
|
||||
delete_rooms.append(roomid)
|
||||
for roomid in delete_rooms:
|
||||
self.account_rooms.pop(roomid, None)
|
||||
|
@ -62,7 +62,7 @@ class PollingService(BotModule):
|
|||
f"Next poll in this room at {self.next_poll_time.get(room.room_id)} - in {self.next_poll_time.get(room.room_id) - datetime.now()}")
|
||||
elif args[1] == 'poll':
|
||||
bot.must_be_owner(event)
|
||||
print(f'{self.service_name} force polling requested by {event.sender}')
|
||||
self.logger.info(f'{self.service_name} force polling requested by {event.sender}')
|
||||
# Faking next poll times to force poll
|
||||
for roomid in self.account_rooms:
|
||||
self.next_poll_time[roomid] = datetime.now() - timedelta(hours=1)
|
||||
|
@ -77,7 +77,7 @@ class PollingService(BotModule):
|
|||
bot.must_be_admin(room, event)
|
||||
|
||||
account = args[2]
|
||||
print(f'Adding {self.service_name} account {account} to room id {room.room_id}')
|
||||
self.logger.info(f'Adding {self.service_name} account {account} to room id {room.room_id}')
|
||||
|
||||
if self.account_rooms.get(room.room_id):
|
||||
if account not in self.account_rooms[room.room_id]:
|
||||
|
@ -94,12 +94,12 @@ class PollingService(BotModule):
|
|||
bot.must_be_admin(room, event)
|
||||
|
||||
account = args[2]
|
||||
print(f'Removing {self.service_name} account {account} from room id {room.room_id}')
|
||||
self.logger.info(f'Removing {self.service_name} account {account} from room id {room.room_id}')
|
||||
|
||||
if self.account_rooms.get(room.room_id):
|
||||
self.account_rooms[room.room_id].remove(account)
|
||||
|
||||
print(f'{self.service_name} accounts now for this room {self.account_rooms.get(room.room_id)}')
|
||||
self.logger.info(f'{self.service_name} accounts now for this room {self.account_rooms.get(room.room_id)}')
|
||||
|
||||
bot.save_settings()
|
||||
await bot.send_text(room, f'Removed {self.service_name} account from this room')
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import shlex
|
||||
from datetime import datetime
|
||||
|
||||
from .common.module import BotModule
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ class MatrixModule(BotModule):
|
|||
args.pop(0)
|
||||
|
||||
# Echo what they said back
|
||||
self.logger.debug(f"room: {room.name} sender: {event.sender} wants an echo")
|
||||
await bot.send_text(room, ' '.join(args))
|
||||
|
||||
def help(self):
|
||||
return ('Echoes back what user has said')
|
||||
return 'Echoes back what user has said'
|
||||
|
|
|
@ -9,7 +9,6 @@ from google.auth.transport.requests import Request
|
|||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
|
||||
#
|
||||
# Google calendar notifications
|
||||
#
|
||||
|
@ -40,10 +39,10 @@ class MatrixModule(BotModule):
|
|||
if os.path.exists('token.pickle'):
|
||||
with open('token.pickle', 'rb') as token:
|
||||
creds = pickle.load(token)
|
||||
print('Loaded existing pickle file')
|
||||
self.logger.info('Loaded existing pickle file')
|
||||
# If there are no (valid) credentials available, let the user log in.
|
||||
if not creds or not creds.valid:
|
||||
print('No credentials or credentials not valid!')
|
||||
self.logger.warn('No credentials or credentials not valid!')
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
|
@ -53,19 +52,17 @@ class MatrixModule(BotModule):
|
|||
# Save the credentials for the next run
|
||||
with open('token.pickle', 'wb') as token:
|
||||
pickle.dump(creds, token)
|
||||
print('Pickle saved')
|
||||
self.logger.info('Pickle saved')
|
||||
|
||||
self.service = build('calendar', 'v3', credentials=creds)
|
||||
|
||||
try:
|
||||
calendar_list = self.service.calendarList().list().execute()[
|
||||
'items']
|
||||
print(
|
||||
f'Google calendar set up successfully with access to {len(calendar_list)} calendars:\n')
|
||||
calendar_list = self.service.calendarList().list().execute()['items']
|
||||
self.logger.info(f'Google calendar set up successfully with access to {len(calendar_list)} calendars:\n')
|
||||
for calendar in calendar_list:
|
||||
print(calendar['summary'] + ' - ' + calendar['id'])
|
||||
self.logger.info(f"{calendar['summary']} - + {calendar['id']}")
|
||||
except Exception:
|
||||
print('Getting calendar list failed!')
|
||||
self.logger.error('Getting calendar list failed!')
|
||||
|
||||
async def matrix_message(self, bot, room, event):
|
||||
if not self.service:
|
||||
|
@ -78,7 +75,7 @@ class MatrixModule(BotModule):
|
|||
if len(args) == 2:
|
||||
if args[1] == 'today':
|
||||
for calid in calendars:
|
||||
print('Listing events in cal', calid)
|
||||
self.logger.info(f'Listing events in cal {calid}')
|
||||
events = events + self.list_today(calid)
|
||||
if args[1] == 'list':
|
||||
await bot.send_text(room, 'Calendars in this room: ' + str(self.calendar_rooms.get(room.room_id)))
|
||||
|
@ -89,7 +86,7 @@ class MatrixModule(BotModule):
|
|||
bot.must_be_admin(room, event)
|
||||
|
||||
calid = args[2]
|
||||
print(f'Adding calendar {calid} to room id {room.room_id}')
|
||||
self.logger.info(f'Adding calendar {calid} to room id {room.room_id}')
|
||||
|
||||
if self.calendar_rooms.get(room.room_id):
|
||||
if calid not in self.calendar_rooms[room.room_id]:
|
||||
|
@ -100,8 +97,7 @@ class MatrixModule(BotModule):
|
|||
else:
|
||||
self.calendar_rooms[room.room_id] = [calid]
|
||||
|
||||
print(
|
||||
f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
self.logger.info(f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
|
||||
bot.save_settings()
|
||||
|
||||
|
@ -112,13 +108,12 @@ class MatrixModule(BotModule):
|
|||
bot.must_be_admin(room, event)
|
||||
|
||||
calid = args[2]
|
||||
print(f'Removing calendar {calid} from room id {room.room_id}')
|
||||
self.logger.info(f'Removing calendar {calid} from room id {room.room_id}')
|
||||
|
||||
if self.calendar_rooms.get(room.room_id):
|
||||
self.calendar_rooms[room.room_id].remove(calid)
|
||||
|
||||
print(
|
||||
f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
self.logger.info(f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
|
||||
bot.save_settings()
|
||||
|
||||
|
@ -127,14 +122,14 @@ class MatrixModule(BotModule):
|
|||
|
||||
else:
|
||||
for calid in calendars:
|
||||
print('Listing events in cal', calid)
|
||||
self.logger.info(f'Listing events in cal {calid}')
|
||||
events = events + self.list_upcoming(calid)
|
||||
|
||||
if len(events) > 0:
|
||||
print(f'Found {len(events)} events')
|
||||
self.logger.info(f'Found {len(events)} events')
|
||||
await self.send_events(bot, events, room)
|
||||
else:
|
||||
print(f'No events found')
|
||||
self.logger.info(f'No events found')
|
||||
await bot.send_text(room, 'No events found, try again later :)')
|
||||
|
||||
async def send_events(self, bot, events, room):
|
||||
|
@ -154,19 +149,18 @@ class MatrixModule(BotModule):
|
|||
|
||||
def list_today(self, calid):
|
||||
startTime = datetime.utcnow()
|
||||
startTime = startTime.replace(
|
||||
hour=0, minute=0, second=0, microsecond=0)
|
||||
startTime = startTime.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
endTime = startTime + timedelta(hours=24)
|
||||
now = startTime.isoformat() + 'Z'
|
||||
end = endTime.isoformat() + 'Z'
|
||||
print('Looking for events between', now, end)
|
||||
self.logger.info(f'Looking for events between {now} {end}')
|
||||
events_result = self.service.events().list(calendarId=calid, timeMin=now,
|
||||
timeMax=end, maxResults=10, singleEvents=True,
|
||||
orderBy='startTime').execute()
|
||||
return events_result.get('items', [])
|
||||
|
||||
def help(self):
|
||||
return ('Google calendar. Lists 10 next events by default. today = list today\'s events.')
|
||||
return 'Google calendar. Lists 10 next events by default. today = list today\'s events.'
|
||||
|
||||
def get_settings(self):
|
||||
data = super().get_settings()
|
||||
|
|
|
@ -19,7 +19,7 @@ class MatrixModule(PollingService):
|
|||
async def poll_implementation(self, bot, account, roomid, send_messages):
|
||||
try:
|
||||
medias = self.instagram.get_medias(account, 5)
|
||||
print(f'Polling instagram account {account} for room {roomid} - got {len(medias)} posts.')
|
||||
self.logger.info(f'Polling instagram account {account} for room {roomid} - got {len(medias)} posts.')
|
||||
for media in medias:
|
||||
if send_messages:
|
||||
if media.identifier not in self.known_ids:
|
||||
|
@ -29,12 +29,11 @@ class MatrixModule(PollingService):
|
|||
self.known_ids.add(media.identifier)
|
||||
|
||||
except InstagramNotFoundException:
|
||||
print('ig error: there is ', account,
|
||||
' account that does not exist - deleting from room')
|
||||
self.logger.error(f"{account} does not exist - deleting from room")
|
||||
self.account_rooms[roomid].remove(account)
|
||||
bot.save_settings()
|
||||
except Exception:
|
||||
print('Polling instagram account failed:')
|
||||
self.logger.error('Polling instagram account failed:')
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
polldelay = timedelta(minutes=30 + randrange(30))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from geopy.geocoders import Nominatim
|
||||
from nio import RoomMessageUnknown, AsyncClient
|
||||
from nio import RoomMessageUnknown
|
||||
|
||||
from modules.common.module import BotModule
|
||||
|
||||
|
||||
|
@ -50,9 +51,9 @@ class MatrixModule(BotModule):
|
|||
else:
|
||||
query = event.body[4:]
|
||||
geolocator = Nominatim(user_agent=bot.appid)
|
||||
print('loc: looking up', query, '..')
|
||||
self.logger.info('loc: looking up %s ..', query)
|
||||
location = geolocator.geocode(query)
|
||||
print('loc rx', location)
|
||||
self.logger.info('loc rx %s', location)
|
||||
if location:
|
||||
locationmsg = {
|
||||
"body": str(location.address),
|
||||
|
|
|
@ -3,7 +3,6 @@ from datetime import datetime
|
|||
|
||||
from pyteamup import Calendar
|
||||
|
||||
|
||||
#
|
||||
# TeamUp calendar notifications
|
||||
#
|
||||
|
@ -48,7 +47,7 @@ class MatrixModule(BotModule):
|
|||
bot.must_be_admin(room, event)
|
||||
|
||||
calid = args[2]
|
||||
print(f'Adding calendar {calid} to room id {room.room_id}')
|
||||
self.logger.info(f'Adding calendar {calid} to room id {room.room_id}')
|
||||
|
||||
if self.calendar_rooms.get(room.room_id):
|
||||
if calid not in self.calendar_rooms[room.room_id]:
|
||||
|
@ -59,8 +58,7 @@ class MatrixModule(BotModule):
|
|||
else:
|
||||
self.calendar_rooms[room.room_id] = [calid]
|
||||
|
||||
print(
|
||||
f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
self.logger.info(f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
|
||||
bot.save_settings()
|
||||
self.setup_calendars()
|
||||
|
@ -69,13 +67,12 @@ class MatrixModule(BotModule):
|
|||
bot.must_be_admin(room, event)
|
||||
|
||||
calid = args[2]
|
||||
print(f'Removing calendar {calid} from room id {room.room_id}')
|
||||
self.logger.info(f'Removing calendar {calid} from room id {room.room_id}')
|
||||
|
||||
if self.calendar_rooms.get(room.room_id):
|
||||
self.calendar_rooms[room.room_id].remove(calid)
|
||||
|
||||
print(
|
||||
f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
self.logger.info(f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
|
||||
|
||||
bot.save_settings()
|
||||
self.setup_calendars()
|
||||
|
|
|
@ -16,7 +16,7 @@ class MatrixModule(PollingService):
|
|||
async def poll_implementation(self, bot, account, roomid, send_messages):
|
||||
try:
|
||||
tweets = query_tweets_from_user(account, limit=1)
|
||||
print(f'Polling twitter account {account} - got {len(tweets)} tweets')
|
||||
self.logger.info(f'Polling twitter account {account} - got {len(tweets)} tweets')
|
||||
for tweet in tweets:
|
||||
if tweet.tweet_id not in self.known_ids:
|
||||
if send_messages:
|
||||
|
@ -25,5 +25,5 @@ class MatrixModule(PollingService):
|
|||
f'Twitter {account}: {tweet.text} - https://twitter.com{tweet.tweet_url}')
|
||||
self.known_ids.add(tweet.tweet_id)
|
||||
except Exception:
|
||||
print('Polling twitter account failed:')
|
||||
self.logger.error('Polling twitter account failed:')
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
|
|
@ -100,11 +100,11 @@ class MatrixModule(BotModule):
|
|||
try:
|
||||
r = httpx.get(url)
|
||||
except Exception as e:
|
||||
print(f"Failed fetching url {url}. Error: {e}")
|
||||
self.logger.error(f"Failed fetching url {url}. Error: {e}")
|
||||
return (title, description)
|
||||
|
||||
if r.status_code != 200:
|
||||
print(f"Failed fetching url {url}. Status code: {r.status_code}")
|
||||
self.logger.info(f"Failed fetching url {url}. Status code: {r.status_code}")
|
||||
return (title, description)
|
||||
|
||||
# try parse and get the title
|
||||
|
@ -115,7 +115,7 @@ class MatrixModule(BotModule):
|
|||
if descr_tag:
|
||||
description = descr_tag.get("content", None)
|
||||
except Exception as e:
|
||||
print(f"Failed parsing response from url {url}. Error: {e}")
|
||||
self.logger.error(f"Failed parsing response from url {url}. Error: {e}")
|
||||
return (title, description)
|
||||
|
||||
return (title, description)
|
||||
|
|
Loading…
Reference in New Issue