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