Browse Source
This builds a container image with a simple eavesdrop bot for Matrix. Change-Id: I5304b4ec974b84886ac969b59cfcec8dec2febf9changes/17/800317/6
8 changed files with 248 additions and 1 deletions
@ -0,0 +1,30 @@
|
||||
# Copyright (C) 2021 Acme Gating, LLC |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
# implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
FROM docker.io/opendevorg/python-builder:3.9 as builder |
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list |
||||
# ENV DEBIAN_FRONTEND=noninteractive |
||||
|
||||
COPY src /tmp/src |
||||
RUN assemble |
||||
|
||||
FROM docker.io/opendevorg/python-base:3.9 as eavesdrop |
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list |
||||
|
||||
COPY --from=builder /output/ /output |
||||
RUN /output/install-from-bindep \ |
||||
&& rm -rf /output |
||||
|
||||
CMD ["eavesdrop"] |
@ -0,0 +1,7 @@
|
||||
gcc [compile test] |
||||
libc6-dev [compile test] |
||||
libffi-dev [compile test] |
||||
libolm-dev/buster-backports [compile test] |
||||
make [compile test] |
||||
python3-dev [compile test] |
||||
libolm3/buster-backports |
@ -0,0 +1,155 @@
|
||||
#!/usr/bin/python3 |
||||
|
||||
# Copyright (C) 2021 Acme Gating, LLC |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
# implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import asyncio |
||||
import time |
||||
import json |
||||
import os |
||||
import sys |
||||
import getpass |
||||
import socket |
||||
import yaml |
||||
import logging |
||||
import datetime |
||||
|
||||
logging.basicConfig(level=logging.INFO) |
||||
|
||||
from nio import AsyncClient, AsyncClientConfig, LoginResponse, RoomMessageText |
||||
from nio.store.database import DefaultStore |
||||
|
||||
|
||||
class Bot: |
||||
def __init__(self): |
||||
self.log = logging.getLogger('bot') |
||||
self.config_path = os.environ.get("MATRIX_CONFIG_FILE", |
||||
"/config/config.yaml") |
||||
self.load_config() |
||||
self.cred_path = os.path.join( |
||||
self.config['data_dir'], 'credentials.json') |
||||
self.device_name = socket.gethostname() |
||||
self.room_map = {} |
||||
|
||||
async def login(self): |
||||
config = AsyncClientConfig( |
||||
store=DefaultStore, |
||||
store_sync_tokens=True) |
||||
creds = self.load_creds() |
||||
if creds: |
||||
self.log.info("Restoring previous session") |
||||
self.client = AsyncClient(self.config['homeserver'], |
||||
store_path=self.config['data_dir'], |
||||
config=config) |
||||
self.client.restore_login( |
||||
user_id=self.config['user_id'], |
||||
device_id=creds["device_id"], |
||||
access_token=creds["access_token"], |
||||
) |
||||
else: |
||||
self.log.info("Creating new session") |
||||
self.client = AsyncClient(self.config['homeserver'], |
||||
self.config['user_id'], |
||||
store_path=self.config['data_dir'], |
||||
config=config) |
||||
resp = await self.client.login( |
||||
self.config['password'], device_name=self.device_name) |
||||
if (isinstance(resp, LoginResponse)): |
||||
self.save_creds(resp.device_id, resp.access_token) |
||||
else: |
||||
self.log.error(resp) |
||||
raise Exception("Error logging in") |
||||
# Load the sync tokens |
||||
self.client.load_store() |
||||
|
||||
def load_config(self): |
||||
with open(self.config_path) as f: |
||||
data = yaml.safe_load(f) |
||||
self.rooms = data['rooms'] |
||||
self.config = data['config'] |
||||
|
||||
def save_creds(self, device_id, token): |
||||
data = { |
||||
'device_id': device_id, |
||||
'access_token': token, |
||||
} |
||||
with open(self.cred_path, 'w') as f: |
||||
json.dump(data, f) |
||||
|
||||
def load_creds(self): |
||||
if os.path.exists(self.cred_path): |
||||
with open(self.cred_path) as f: |
||||
data = json.load(f) |
||||
return data |
||||
|
||||
async def join_rooms(self): |
||||
new = set() |
||||
old = set() |
||||
resp = await self.client.joined_rooms() |
||||
for room in resp.rooms: |
||||
old.add(room) |
||||
for room in self.rooms: |
||||
self.log.info("Join room %s", room['id']) |
||||
resp = await self.client.join(room['id']) |
||||
new.add(resp.room_id) |
||||
# Store the canonical room id, since the one in the config |
||||
# file may be an alias |
||||
self.room_map[resp.room_id] = room |
||||
os.makedirs(room['path'], exist_ok=True) |
||||
for room in old-new: |
||||
self.log.info("Leave room %s", room['id']) |
||||
await self.client.room_leave(room) |
||||
|
||||
async def message_callback(self, room, event): |
||||
config_room = self.room_map.get(room.room_id) |
||||
if not config_room: |
||||
return |
||||
room_name = config_room['id'].split(':')[0] |
||||
ts = datetime.datetime.utcfromtimestamp(event.server_timestamp/1000.0) |
||||
event_date = str(ts.date()) |
||||
event_time = str(ts.time())[:8] |
||||
room_path = config_room['path'] |
||||
if not room_path.startswith('/'): |
||||
room_path = os.path.join(self.config['log_dir'], room_path) |
||||
filename = f'{room_name}.{event_date}.log' |
||||
logpath = os.path.join(room_path, filename) |
||||
body = event.body |
||||
line = f'{event_date}T{event_time} <{event.sender}> {body}\n' |
||||
self.log.info('Logging %s %s', room.room_id, line[:-1]) |
||||
with open(logpath, 'a') as f: |
||||
f.write(line) |
||||
|
||||
async def run(self): |
||||
await self.login() |
||||
await self.join_rooms() |
||||
self.client.add_event_callback(self.message_callback, RoomMessageText) |
||||
try: |
||||
await self.client.sync_forever(timeout=30000, full_state=True) |
||||
finally: |
||||
await self.client.close() |
||||
|
||||
|
||||
async def _main(): |
||||
while True: |
||||
try: |
||||
bot = Bot() |
||||
await bot.run() |
||||
except Exception: |
||||
bot.log.exception("Error:") |
||||
time.sleep(10) |
||||
|
||||
|
||||
def main(): |
||||
asyncio.get_event_loop().run_until_complete(_main()) |
@ -0,0 +1,13 @@
|
||||
from setuptools import setup, find_packages |
||||
|
||||
setup( |
||||
name='eavesdrop', |
||||
version='0.0.1', |
||||
packages=find_packages(), |
||||
install_requires=['matrix-nio[e2e]', 'PyYaml'], |
||||
entry_points={ |
||||
'console_scripts': [ |
||||
'eavesdrop = eavesdrop.bot:main', |
||||
] |
||||
} |
||||
) |
@ -1,4 +1,4 @@
|
||||
#!/bin/bash |
||||
|
||||
ROOT=$(readlink -fn $(dirname $0)/.. ) |
||||
find $ROOT -not -wholename \*.tox/\* -and \( -name \*.sh -or -name \*rc -or -name functions\* \) -print0 | xargs -0 bashate -i E006 -v |
||||
find $ROOT -type f -not -wholename \*.tox/\* -and \( -name \*.sh -or -name \*rc -or -name functions\* \) -print0 | xargs -0 bashate -i E006 -v |
||||
|
@ -0,0 +1,31 @@
|
||||
# matrix-eavesdrop jobs |
||||
- job: |
||||
name: system-config-build-image-matrix-eavesdrop |
||||
description: Build a matrix-eavesdrop image. |
||||
parent: system-config-build-image |
||||
requires: &matrix-eavesdrop_requires |
||||
- python-base-3.9-container-image |
||||
- python-builder-3.9-container-image |
||||
provides: matrix-eavesdrop-container-image |
||||
vars: &matrix-eavesdrop_vars |
||||
docker_images: |
||||
- context: docker/matrix-eavesdrop |
||||
repository: opendevorg/matrix-eavesdrop |
||||
files: &matrix-eavesdrop_files |
||||
- docker/matrix-eavesdrop/.* |
||||
|
||||
- job: |
||||
name: system-config-upload-image-matrix-eavesdrop |
||||
description: Build and upload a matrix-eavesdrop image. |
||||
parent: system-config-upload-image |
||||
requires: *matrix-eavesdrop_requires |
||||
provides: matrix-eavesdrop-container-image |
||||
vars: *matrix-eavesdrop_vars |
||||
files: *matrix-eavesdrop_files |
||||
|
||||
- job: |
||||
name: system-config-promote-image-matrix-eavesdrop |
||||
description: Promote a previously published matrix-eavesdrop image to latest. |
||||
parent: system-config-promote-image |
||||
vars: *matrix-eavesdrop_vars |
||||
files: *matrix-eavesdrop_files |
Loading…
Reference in new issue