diff --git a/README.md b/README.md
index 819309c..c78056f 100644
--- a/README.md
+++ b/README.md
@@ -500,6 +500,18 @@ For example, if a wood lathe is broken, create issue with labels
Repository name must be in format TampereHacklab/Inventaario - you can
use this as a example to see how the labels work.
+### PeerTube search
+
+Searches PeerTube instances for videos. Uses Sepia Serch at https://sepiasearch.org/
+by default, but you can set any single instance to search on.
+
+#### Usage
+
+* !pt [query] - Search for exactly one video matching query
+* !ptall [query] - Search up to 15 videos matching query
+* !pt setinstance [url] - Set instance url, must end with / (example: https://peertube.cpy.re/)
+* !pt showinstance - Print which instance is used
+
## Bot setup
* Create a Matrix user
diff --git a/modules/pt.py b/modules/pt.py
new file mode 100644
index 0000000..1aa06f9
--- /dev/null
+++ b/modules/pt.py
@@ -0,0 +1,78 @@
+from typing import Text
+import urllib
+import urllib.request
+from urllib.parse import urlencode, quote_plus
+import json
+import time
+
+from modules.common.module import BotModule
+
+class PeerTubeClient:
+ def __init__(self):
+ self.instance_url = 'https://sepiasearch.org/'
+
+ def search(self, search_string, count=0):
+ if count == 0:
+ count = 15 # Pt default, could also remove from params..
+ params = urlencode({'search': search_string, 'count': count}, quote_via=quote_plus)
+ search_url = self.instance_url + 'api/v1/search/videos?' + params
+ response = urllib.request.urlopen(search_url)
+ data = json.loads(response.read().decode("utf-8"))
+ return data
+
+class MatrixModule(BotModule):
+ def matrix_start(self, bot):
+ super().matrix_start(bot)
+ self.add_module_aliases(bot, ['ptall'])
+ if not self.instance_url:
+ instance_url = 'https://sepiasearch.org/'
+
+ async def matrix_message(self, bot, room, event):
+ args = event.body.split()
+ if len(args) == 3:
+ if args[1] == "setinstance":
+ bot.must_be_owner(event)
+ self.instance_url = args[2]
+ bot.save_settings()
+ await bot.send_text(room, 'Instance url set to ' + self.instance_url, bot_ignore=True)
+ return
+
+ if len(args) == 2:
+ if args[1] == "showinstance":
+ await bot.send_text(room, 'Using instance at ' + self.instance_url, bot_ignore=True)
+ return
+
+ if len(args) > 1:
+ query = event.body[len(args[0])+1:]
+ p = PeerTubeClient()
+ p.instance_url = self.instance_url
+ count = 1
+ if args[0] == '!ptall':
+ count = 0
+ data = p.search(query, count)
+ if len(data['data']) > 0:
+ for video in data['data']:
+ video_url = video["url"] #self.instance_url + 'videos/watch/' + video["uuid"]
+ duration = time.strftime('%H:%M:%S', time.gmtime(video["duration"]))
+ instancedata = video["account"]["host"]
+ html = f'{video["name"]} {video["description"] or ""} [{duration}] @ {instancedata}'
+ text = f'{video_url} : {video["name"]} {video.get("description") or ""} [{duration}]'
+ await bot.send_html(room, html, text, bot_ignore=True)
+ else:
+ await bot.send_text(room, 'Sorry, no videos found found.', bot_ignore=True)
+
+ else:
+ await bot.send_text(room, 'Usage: !pt or !ptall to return all results')
+
+ def get_settings(self):
+ data = super().get_settings()
+ data['instance_url'] = self.instance_url
+ return data
+
+ def set_settings(self, data):
+ super().set_settings(data)
+ if data.get("instance_url"):
+ self.instance_url = data["instance_url"]
+
+ def help(self):
+ return ('PeerTube search')