Merge pull request #141 from aspacca/master

Adding giphy, gfycat and Tautulli bot, splitted handling for image upload and send
This commit is contained in:
Ville Ranki 2021-04-23 12:42:30 +03:00 committed by GitHub
commit b6774f15a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 639 additions and 35 deletions

View File

@ -20,6 +20,10 @@ wolframalpha = "*"
Mastodon-py = "*" Mastodon-py = "*"
pycups = "*" pycups = "*"
pygithub = "*" pygithub = "*"
pillow = "*"
giphypop = "*"
tzlocal = "*"
nest_asyncio = "*"
[dev-packages] [dev-packages]
pylint = "*" pylint = "*"

140
README.md
View File

@ -470,6 +470,77 @@ SVG files are printed as text currently, avoid printing them.
This module is disabled by default. This module is disabled by default.
### Giphy
Can be used to post a picture from giphy given a query string.
API Key:
The module has no API Key set up by default. You have to provide an api key by using the relevant command.
Read the documentation to create one at https://developers.giphy.com/docs/api
Commands:
* !giphy apikey [apikey] - Set api key (Must be done as bot owner)
* !giphy [query] - Post the first image result from giphy for the given [query]
Example:
* !giphy test
### Gfycat
Can be used to post a picture from Gfycat given a query string.
Commands:
* !gfycat [query] - Post the first image result from gfycat for the given [query]
Example:
* !gfycat test
### Tautulli
Can be used to fetch recently added information from Tautulli or receive Tautulli recently added notification webhook
Commands:
* !tautulli apikey [apikey] - Set api key (Must be done as bot owner)
* !tautulli movie - Show the last 10 recently added movies to Plex library monitered by Tautulli
* !tautulli show - Show the last 10 recently added tv series epsiodes to Plex library monitered by Tautulli
* !tautulli artist - Show the last 10 recently added music artists to Plex library monitered by Tautulli
* !tautulli add [room_id] encrypted - Add the provided encrypted [room_id] to the list of rooms to post the recently added notifications received by the webhook
* !tautulli add [room_id] plain - Add the provided unencrypted [room_id] to the list of rooms to post the recently added notifications received by the webhook
* !tautulli remove [room_id] encrypted - Remove the provided encrypted [room_id] to the list of rooms to post the recently added notifications received by the webhook
* !tautulli remove [room_id] plain - Remove the provided unencrypted [room_id] to the list of rooms to post the recently added notifications received by the webhook
Tautulli instance and API Key:
The module work with an existing installed instance of Tautulli accessible on the machine at the path defined by env variable `TAUTULLI_PATH`
You have to provide an api key by using the relevant command.
Environ variables seen by command:
* TAUTULLI_PATH: Path accessible from the machine to the installed instance of Tautulli
* TAUTULLI_URL: Url accessible from the machine to the installed instance of Tautulli
* TAUTULLI_NOTIFIER_ADDR: Listening address for the Tautulli webhook handler target
* TAUTULLI_NOTIFIER_PORT: Listening port for the Tautulli webhook handler target
* BOT_OWNERS: Owner of the rooms in the list for the notification webhook
Docker environment:
Since the module needs access to the source of the running Tautulli instance volumes on both Docker (hemppa and Tautulli) should be defined and being visible each other.
When running on Docker the env variables seen by command should be defined for the bot instance.
Example:
* !tautulli movie
* !tautulli add !OGEhHVWSdvArJzumhm:matrix.org plain
* !tautulli remove !OGEhHVWSdvArJzumhm:matrix.org plain
### Github based asset management ### Github based asset management
This module was written for asset (machines, tasks and todo) management by This module was written for asset (machines, tasks and todo) management by
@ -639,6 +710,75 @@ class MatrixModule(BotModule):
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.
## Bot API
```python
class Bot:
async def send_msg(self, mxid, roomname, message):
"""
:param mxid: A Matrix user id to send the message to
:param roomname: A Matrix room id to send the message to
:param message: Text to be sent as message
:return bool: Success upon sending the message
"""
async def send_text(self, room, body, msgtype="m.notice", bot_ignore=False):
"""
:param room: A MatrixRoom the text should be send to
:param body: Textual content of the message
:param msgtype: The message type for the room https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
:param bot_ignore: Flag to mark the message to be ignored by the bot
:return:
"""
async def send_html(self, room, html, plaintext, msgtype="m.notice", bot_ignore=False):
"""
:param room: A MatrixRoom the html should be send to
:param html: Html content of the message
:param plaintext: Plaintext content of the message
:param msgtype: The message type for the room https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
:param bot_ignore: Flag to mark the message to be ignored by the bot
:return:
"""
async def send_image(self, room, url, body, mimetype=None, width=None, height=None, size=None):
"""
:param room: A MatrixRoom the image should be send to
:param url: A MXC-Uri https://matrix.org/docs/spec/client_server/r0.6.0#mxc-uri
:param body: A textual representation of the image
:param mimetype: The mimetype of the image
:param width: Width in pixel of the image
:param height: Height in pixel of the image
:param size: Size in bytes of the image
:return:
"""
async def upload_image(self, url, blob=False, blob_content_type="image/png"):
"""
:param url: Url of binary content of the image to upload
:param blob: Flag to indicate if the first param is an url or a binary content
:param blob_content_type: Content type of the image in case of binary content
:return: A MXC-Uri https://matrix.org/docs/spec/client_server/r0.6.0#mxc-uri, Content type, Width, Height, Image size in bytes
"""
async def upload_and_send_image(self, room, url, text=None, blob=False, blob_content_type="image/png"):
"""
:param room: A MatrixRoom the image should be send to after uploading
:param url: Url of binary content of the image to upload
:param text: A textual representation of the image
:param blob: Flag to indicate if the second param is an url or a binary content
:param blob_content_type: Content type of the image in case of binary content
:return:
"""
```
### Logging ### 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`. 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`.

140
bot.py
View File

@ -17,6 +17,8 @@ import logging
import logging.config import logging.config
import datetime import datetime
from importlib import reload from importlib import reload
from io import BytesIO
from PIL import Image
import requests import requests
from nio import AsyncClient, InviteEvent, JoinError, RoomMessageText, MatrixRoom, LoginError, RoomMemberEvent, RoomVisibility, RoomPreset, RoomCreateError, RoomResolveAliasResponse, UploadError, UploadResponse from nio import AsyncClient, InviteEvent, JoinError, RoomMessageText, MatrixRoom, LoginError, RoomMemberEvent, RoomVisibility, RoomPreset, RoomCreateError, RoomResolveAliasResponse, UploadError, UploadResponse
@ -71,7 +73,80 @@ class Bot:
self.logger.debug("Logger initialized") self.logger.debug("Logger initialized")
async def upload_and_send_image(self, room, url, text=None, blob=False, blob_content_type="image/png"):
"""
:param room: A MatrixRoom the image should be send to after uploading
:param url: Url of binary content of the image to upload
:param text: A textual representation of the image
:param blob: Flag to indicate if the second param is an url or a binary content
:param blob_content_type: Content type of the image in case of binary content
:return:
"""
matrix_uri, mimetype, w, h, size = await self.upload_image(url, blob, blob_content_type)
if not text and not blob:
text = f"{url}"
if matrix_uri is not None:
await self.send_image(room, matrix_uri, text, mimetype, w, h, size)
else:
await self.send_text(room, "sorry. something went wrong uploading the image to matrix server :(")
# Helper function to upload a image from URL to homeserver. Use send_image() to actually send it to room.
async def upload_image(self, url, blob=False, blob_content_type="image/png"):
"""
:param url: Url of binary content of the image to upload
:param blob: Flag to indicate if the first param is an url or a binary content
:param blob_content_type: Content type of the image in case of binary content
:return: A MXC-Uri https://matrix.org/docs/spec/client_server/r0.6.0#mxc-uri, Content type, Width, Height, Image size in bytes
"""
self.client: AsyncClient
response: UploadResponse
if blob:
(response, alist) = await self.client.upload(lambda a, b: url, blob_content_type)
i = Image.open(BytesIO(url))
image_length = len(url)
content_type = blob_content_type
else:
self.logger.debug(f"start downloading image from url {url}")
headers = {'User-Agent': 'Mozilla/5.0'}
url_response = requests.get(url, headers=headers)
self.logger.debug(f"response [status_code={url_response.status_code}, headers={url_response.headers}")
if url_response.status_code == 200:
content_type = url_response.headers.get("content-type")
self.logger.info(f"uploading content to matrix server [size={len(url_response.content)}, content-type: {content_type}]")
(response, alist) = await self.client.upload(lambda a, b: url_response.content, content_type)
self.logger.debug("response: %s", response)
i = Image.open(BytesIO(url_response.content))
image_length = len(url_response.content)
else:
self.logger.error("unable to request url: %s", url_response)
return None, None, None, None
if isinstance(response, UploadResponse):
self.logger.info("uploaded file to %s", response.content_uri)
return response.content_uri, content_type, i.size[0], i.size[1], image_length
else:
response: UploadError
self.logger.error("unable to upload file. msg: %s", response.message)
return None, None, None, None
async def send_text(self, room, body, msgtype="m.notice", bot_ignore=False): async def send_text(self, room, body, msgtype="m.notice", bot_ignore=False):
"""
:param room: A MatrixRoom the text should be send to
:param body: Textual content of the message
:param msgtype: The message type for the room https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
:param bot_ignore: Flag to mark the message to be ignored by the bot
:return:
"""
msg = { msg = {
"body": body, "body": body,
"msgtype": msgtype, "msgtype": msgtype,
@ -82,6 +157,16 @@ 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)
async def send_html(self, room, html, plaintext, msgtype="m.notice", bot_ignore=False): async def send_html(self, room, html, plaintext, msgtype="m.notice", bot_ignore=False):
"""
:param room: A MatrixRoom the html should be send to
:param html: Html content of the message
:param plaintext: Plaintext content of the message
:param msgtype: The message type for the room https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
:param bot_ignore: Flag to mark the message to be ignored by the bot
:return:
"""
msg = { msg = {
"msgtype": msgtype, "msgtype": msgtype,
"format": "org.matrix.custom.html", "format": "org.matrix.custom.html",
@ -92,22 +177,46 @@ class Bot:
msg["org.vranki.hemppa.ignore"] = "true" msg["org.vranki.hemppa.ignore"] = "true"
await self.client.room_send(room.room_id, 'm.room.message', msg) await self.client.room_send(room.room_id, 'm.room.message', msg)
async def send_image(self, room, url, body): async def send_image(self, room, url, body, mimetype=None, width=None, height=None, size=None):
""" """
:param room: A MatrixRoom the image should be send to :param room: A MatrixRoom the image should be send to
:param url: A MXC-Uri https://matrix.org/docs/spec/client_server/r0.6.0#mxc-uri :param url: A MXC-Uri https://matrix.org/docs/spec/client_server/r0.6.0#mxc-uri
:param body: A textual representation of the image :param body: A textual representation of the image
:param mimetype: The mimetype of the image
:param width: Width in pixel of the image
:param height: Height in pixel of the image
:param size: Size in bytes of the image
:return: :return:
""" """
msg = { msg = {
"url": url, "url": url,
"body": body, "body": body,
"msgtype": "m.image" "msgtype": "m.image",
"info": {
"thumbnail_info": None,
"thumbnail_url": None,
},
} }
if mimetype:
msg["info"]["mimetype"] = mimetype
if width:
msg["info"]["w"] = width
if height:
msg["info"]["h"] = height
if size:
msg["info"]["size"] = size
await self.client.room_send(room.room_id, 'm.room.message', msg) await self.client.room_send(room.room_id, 'm.room.message', msg)
async def send_msg(self, mxid, roomname, message): async def send_msg(self, mxid, roomname, message):
"""
:param mxid: A Matrix user id to send the message to
:param roomname: A Matrix room id to send the message to
:param message: Text to be sent as message
:return bool: Success upon sending the message
"""
# Sends private message to user. Returns true on success. # Sends private message to user. Returns true on success.
# Find if we already have a common room with user: # Find if we already have a common room with user:
@ -181,33 +290,6 @@ class Bot:
def should_ignore_event(self, event): def should_ignore_event(self, event):
return "org.vranki.hemppa.ignore" in event.source['content'] return "org.vranki.hemppa.ignore" in event.source['content']
# Helper function to upload a image from URL to homeserver. Use send_image() to actually send it to room.
async def upload_image(self, url):
self.client: AsyncClient
response: UploadResponse
self.logger.debug(f"start downloading image from url {url}")
url_response = requests.get(url)
self.logger.debug(f"response [status_code={url_response.status_code}, headers={url_response.headers}")
if url_response.status_code == 200:
content_type = url_response.headers.get("content-type")
self.logger.info(f"uploading content to matrix server [size={len(url_response.content)}, content-type: {content_type}]")
(response, alist) = await self.client.upload(lambda a, b: url_response.content, content_type)
self.logger.debug("response: %s", response)
if isinstance(response, UploadResponse):
self.logger.info("uploaded file to %s", response.content_uri)
return response.content_uri
else:
response: UploadError
self.logger.error("unable to upload file. msg: %s", response.message)
else:
self.logger.error("unable to request url: %s", url_response)
return None
def save_settings(self): def save_settings(self):
module_settings = dict() module_settings = dict()
for modulename, moduleobject in self.modules.items(): for modulename, moduleobject in self.modules.items():

View File

@ -92,20 +92,20 @@ class MatrixModule(BotModule):
await bot.send_text(room, f"{apod.explanation} || date: {apod.date} || original-url: {apod.url}") await bot.send_text(room, f"{apod.explanation} || date: {apod.date} || original-url: {apod.url}")
async def upload_and_send_image(self, room, bot, apod): async def upload_and_send_image(self, room, bot, apod):
send_again = True
await bot.send_text(room, f"{apod.title} ({apod.date})")
if apod.date in self.matrix_uri_cache: if apod.date in self.matrix_uri_cache:
matrix_uri = self.matrix_uri_cache.get(apod.date) matrix_uri = self.matrix_uri_cache.get(apod.date)
self.logger.debug(f"already uploaded picture {matrix_uri} for date {apod.date}") self.logger.debug(f"already uploaded picture {matrix_uri} for date {apod.date}")
else: else:
matrix_uri = await bot.upload_image(apod.hdurl) matrix_uri = await bot.upload_and_send_image(room, apod.hdurl, f"{apod.title}")
if matrix_uri is None: send_again = False
self.logger.warning("unable to upload hdurl. try url next.")
matrix_uri = await bot.upload_image(apod.url)
await bot.send_text(room, f"{apod.title} ({apod.date})")
if matrix_uri is not None: if matrix_uri is not None:
self.matrix_uri_cache[apod.date] = matrix_uri self.matrix_uri_cache[apod.date] = matrix_uri
bot.save_settings() bot.save_settings()
await bot.send_image(room, matrix_uri, f"{apod.title}") if send_again:
await bot.send_image(room, matrix_uri, f"{apod.title}")
else: else:
await bot.send_text(room, "Sorry. Something went wrong uploading the image to Matrix server :(") await bot.send_text(room, "Sorry. Something went wrong uploading the image to Matrix server :(")
await bot.send_text(room, f"{apod.explanation}") await bot.send_text(room, f"{apod.explanation}")

100
modules/gfycat.py Normal file
View File

@ -0,0 +1,100 @@
import urllib.request
import urllib.parse
import urllib.error
import requests
from nio import AsyncClient, UploadError
from nio import UploadResponse
from collections import namedtuple
from modules.common.module import BotModule
class gfycat(object):
"""
A very simple module that allows you to
1. search a gif on gfycat from a remote location
"""
# Urls
url = "https://api.gfycat.com"
def __init__(self):
super(gfycat, self).__init__()
def __fetch(self, url, param):
import json
try:
# added simple User-Ajent string to avoid CloudFlare block this request
headers = {'User-Agent': 'Mozilla/5.0'}
req = urllib.request.Request(url+param, headers=headers)
connection = urllib.request.urlopen(req).read()
except urllib.error.HTTPError as err:
raise ValueError(err.read())
result = namedtuple("result", "raw json")
return result(raw=connection, json=json.loads(connection))
def search(self, param):
result = self.__fetch(self.url, "/v1/gfycats/search?search_text=%s" % urllib.parse.quote_plus(param))
if "errorMessage" in result.json:
raise ValueError("%s" % self.json["errorMessage"])
return _gfycatSearch(result)
class _gfycatUtils(object):
"""
A utility class that provides the necessary common
for all the other classes
"""
def __init__(self, param, json):
super(_gfycatUtils, self).__init__()
# This can be used for other functions related to this class
self.res = param
self.js = json
def raw(self):
return self.res.raw
def json(self):
return self.js
def __len__(self):
return len(self.js)
def get(self, what):
try:
return self.js[what]
except KeyError as error:
return ("Sorry, can't find %s" % error)
class _gfycatSearch(_gfycatUtils):
"""
This class will provide more information for an existing url
"""
def __init__(self, param):
super(_gfycatSearch, self).__init__(param, param.json["gfycats"])
class MatrixModule(BotModule):
async def matrix_message(self, bot, room, event):
args = event.body.split()
if len(args) > 1:
gif_url = "No image found"
query = event.body[len(args[0])+1:]
try:
gifs = gfycat().search(query)
if len(gifs) < 1:
await bot.send_text(room, gif_url)
return
gif_url = gifs.get(0)["content_urls"]["largeGif"]["url"]
await bot.upload_and_send_image(room, gif_url)
except Exception as exc:
gif_url = str(exc)
await bot.send_text(room, gif_url)
else:
await bot.send_text(room, 'Usage: !gfycat <query>')
def help(self):
return ('Gfycat bot')

60
modules/giphy.py Normal file
View File

@ -0,0 +1,60 @@
import urllib.request
import urllib.parse
import urllib.error
import os
import giphypop
import requests
from nio import AsyncClient, UploadError
from nio import UploadResponse
from collections import namedtuple
from modules.common.module import BotModule
class MatrixModule(BotModule):
api_key = None
async def matrix_message(self, bot, room, event):
args = event.body.split()
if len(args) == 3 and args[1] == 'apikey':
bot.must_be_owner(event)
self.api_key = args[2]
bot.save_settings()
await bot.send_text(room, 'Api key set')
elif len(args) > 1:
gif_url = "No image found"
query = event.body[len(args[0])+1:]
try:
g = giphypop.Giphy(api_key=self.api_key)
gifs = []
try:
for x in g.search(phrase=query, limit=1):
gifs.append(x)
except Exception:
pass
if len(gifs) < 1:
await bot.send_text(room, gif_url)
return
gif_url = gifs[0].media_url
await bot.upload_and_send_image(room, gif_url)
return
except Exception as exc:
gif_url = str(exc)
await bot.send_text(room, gif_url)
else:
await bot.send_text(room, 'Usage: !giphy <query>')
def get_settings(self):
data = super().get_settings()
data["api_key"] = self.api_key
return data
def set_settings(self, data):
super().set_settings(data)
if data.get("api_key"):
self.api_key = data["api_key"]
def help(self):
return ('Giphy bot')

218
modules/tautulli.py Normal file
View File

@ -0,0 +1,218 @@
import urllib.request
import urllib.parse
import urllib.error
import tzlocal
import pytz
import os
import sys
import json
import importlib
from importlib import reload
from nio import MatrixRoom
from aiohttp import web
import asyncio
import nest_asyncio
nest_asyncio.apply()
from modules.common.module import BotModule
tautulli_path = os.getenv("TAUTULLI_PATH")
def load_tautulli():
try:
global tautulli_path
sys.path.append(tautulli_path)
sys.path.append("{}/lib".format(tautulli_path))
module = importlib.import_module("plexpy")
module = reload(module)
return module
except ModuleNotFoundError:
return None
plexpy = load_tautulli()
async def send_entry(bot, room, entry):
if "art" in entry:
global plexpy
if plexpy:
pms = plexpy.pmsconnect.PmsConnect()
(image0, image1) = pms.get_image(entry["art"], 600, 300)
matrix_uri = await bot.upload_and_send_image(room, image0, "", True, image1)
if matrix_uri is not None:
await bot.send_image(room, matrix_uri, "")
fmt_params = {
"title": entry["title"],
"year": entry["year"],
"audience_rating": entry["audience_rating"],
"directors": ", ".join(entry["directors"]),
"actors": ", ".join(entry["actors"]),
"summary": entry["summary"],
"tagline": entry["tagline"],
"genres": ", ".join(entry["genres"])
}
await bot.send_html(room,
msg_template_html.format(**fmt_params),
msg_template_plain.format(**fmt_params))
msg_template_html = """
<b>{title} -({year})- Rating: {audience_rating}</b><br>
Director(s): {directors}<br>
Actors: {actors}<br>
<I>{summary}</I><br>
{tagline}<br>
Genre(s): {genres}<br><br>"""
msg_template_plain = """*{title} -({year})- Rating: {audience_rating}*
Director(s): {directors}
Actors: {actors}
{summary}
{tagline}
Genre(s): {genres}
"""
class WebServer:
bot = None
rooms = dict()
def __init__(self, host, port):
self.app = web.Application()
self.host = host
self.port = port
self.app.router.add_post('/notify', self.notify)
async def run(self):
if not self.host or not self.port:
return
loop = asyncio.get_event_loop()
runner = web.AppRunner(self.app)
loop.run_until_complete(runner.setup())
site = web.TCPSite(runner, host=self.host, port=self.port)
loop.run_until_complete(site.start())
async def notify(self, request: web.Request) -> web.Response:
try:
data = await request.json()
if "genres" in data:
data["genres"] = data["genres"].split(",")
if "actors" in data:
data["actors"] = data["actors"].split(",")
if "directors" in data:
data["directors"] = data["directors"].split(",")
for room_id in self.rooms:
room = MatrixRoom(room_id=room_id, own_user_id=os.getenv("BOT_OWNERS"), encrypted=self.rooms[room_id])
await send_entry(self.bot, room, data)
except Exception as exc:
message = str(exc)
return web.HTTPBadRequest(body=message)
return web.Response()
class MatrixModule(BotModule):
httpd = None
rooms = dict()
api_key = None
def __init__(self, name):
super().__init__(name)
global plexpy
if plexpy:
global tautulli_path
plexpy.FULL_PATH = "{}/Tautulli.py".format(tautulli_path)
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
plexpy.DATA_DIR = tautulli_path
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, plexpy.database.FILENAME)
try:
plexpy.SYS_TIMEZONE = tzlocal.get_localzone()
except (pytz.UnknownTimeZoneError, LookupError, ValueError) as e:
plexpy.SYS_TIMEZONE = pytz.UTC
plexpy.initialize("{}/config.ini".format(tautulli_path))
self.httpd = WebServer(os.getenv("TAUTULLI_NOTIFIER_ADDR"), os.getenv("TAUTULLI_NOTIFIER_PORT"))
loop = asyncio.get_event_loop()
loop.run_until_complete(self.httpd.run())
def matrix_start(self, bot):
super().matrix_start(bot)
self.httpd.bot = bot
async def matrix_message(self, bot, room, event):
args = event.body.split()
if len(args) == 3 and args[1] == 'apikey':
bot.must_be_owner(event)
self.api_key = args[2]
bot.save_settings()
await bot.send_text(room, 'Api key set')
elif len(args) == 2:
media_type = args[1]
if media_type != "movie" and media_type != "show" and media_type != "artist":
await bot.send_text(room, "media type '%s' provided not valid" % media_type)
return
try:
url = "{}/api/v2?apikey={}&cmd=get_recently_added&count=10".format(os.getenv("TAUTULLI_URL"), self.api_key)
req = urllib.request.Request(url+"&media_type="+media_type)
connection = urllib.request.urlopen(req).read()
entries = json.loads(connection)
if "response" not in entries and "data" not in entries["response"] and "recently_added" not in entries["response"]["data"]:
await bot.send_text(room, "no recently added for %s" % media_type)
return
for entry in entries["response"]["data"]["recently_added"]:
await send_entry(bot, room, entry)
except urllib.error.HTTPError as err:
raise ValueError(err.read())
except Exception as exc:
message = str(exc)
await bot.send_text(room, message)
elif len(args) == 4:
if args[1] == "add" or args[1] == "remove":
room_id = args[2]
encrypted = args[3]
if args[1] == "add":
self.rooms[room_id] = encrypted == "encrypted"
await bot.send_text(room, f"Added {room_id} to rooms notification list")
else:
del self.rooms[room_id]
await bot.send_text(room, f"Removed {room_id} to rooms notification list")
bot.save_settings()
self.httpd.rooms = self.rooms
else:
await bot.send_text(room, 'Usage: !tautulli <movie|show|artist>|<add|remove> %room_id% %encrypted%')
else:
await bot.send_text(room, 'Usage: !tautulli <movie|show|artist>|<add|remove> %room_id% %encrypted%')
def get_settings(self):
data = super().get_settings()
data["api_key"] = self.api_key
data["rooms"] = self.rooms
self.httpd.rooms = self.rooms
return data
def set_settings(self, data):
super().set_settings(data)
if data.get("rooms"):
self.rooms = data["rooms"]
self.httpd.rooms = self.rooms
if data.get("api_key"):
self.api_key = data["api_key"]
def help(self):
return ('Tautulli recently added bot')