New bots (giphy, gfycat, tautulli), changes for upload and send image with url or blob content

This commit is contained in:
Andrea Spacca 2021-04-21 10:06:58 +02:00
parent 4228779748
commit 030ec5d779
6 changed files with 427 additions and 35 deletions

View File

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

93
bot.py
View File

@ -17,6 +17,8 @@ import logging
import logging.config
import datetime
from importlib import reload
from io import BytesIO
from PIL import Image
import requests
from nio import AsyncClient, InviteEvent, JoinError, RoomMessageText, MatrixRoom, LoginError, RoomMemberEvent, RoomVisibility, RoomPreset, RoomCreateError, RoomResolveAliasResponse, UploadError, UploadResponse
@ -71,6 +73,53 @@ class Bot:
self.logger.debug("Logger initialized")
async def upload_and_send_image(self, room, url, text=None, blob=False, blob_content_type="image/png"):
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"):
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):
msg = {
"body": body,
@ -92,7 +141,7 @@ class Bot:
msg["org.vranki.hemppa.ignore"] = "true"
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
@ -103,8 +152,21 @@ class Bot:
msg = {
"url": url,
"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)
async def send_msg(self, mxid, roomname, message):
@ -181,33 +243,6 @@ class Bot:
def should_ignore_event(self, event):
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):
module_settings = dict()
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}")
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:
matrix_uri = self.matrix_uri_cache.get(apod.date)
self.logger.debug(f"already uploaded picture {matrix_uri} for date {apod.date}")
else:
matrix_uri = await bot.upload_image(apod.hdurl)
if matrix_uri is None:
self.logger.warning("unable to upload hdurl. try url next.")
matrix_uri = await bot.upload_image(apod.url)
matrix_uri = await bot.upload_and_send_image(room, apod.hdurl, f"{apod.title}")
send_again = False
await bot.send_text(room, f"{apod.title} ({apod.date})")
if matrix_uri is not None:
self.matrix_uri_cache[apod.date] = matrix_uri
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:
await bot.send_text(room, "Sorry. Something went wrong uploading the image to Matrix server :(")
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')

45
modules/giphy.py Normal file
View File

@ -0,0 +1,45 @@
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):
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:
g = giphypop.Giphy(api_key=os.getenv("GIPHY_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 set_settings(self, data):
super().set_settings(data)
def help(self):
return ('Giphy bot')

208
modules/tautulli.py Normal file
View File

@ -0,0 +1,208 @@
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=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()
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) == 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"), os.getenv("TAUTULLI_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["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
def help(self):
return ('Tautulli recently added bot')