mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-27 05:09:23 +01:00
Fediverse: Add support for videos
This commit is contained in:
parent
d67fb2a8b2
commit
2df2bc28d0
@ -38,6 +38,7 @@ from supybot.commands import urlSnarfer, wrap
|
||||
from supybot.i18n import PluginInternationalization
|
||||
|
||||
from . import activitypub as ap
|
||||
from .utils import parse_xsd_duration
|
||||
|
||||
|
||||
importlib.reload(ap)
|
||||
@ -222,18 +223,29 @@ class Fediverse(callbacks.PluginRegexp):
|
||||
name = actor.get("name", username)
|
||||
return "\x02%s\x02 (@%s@%s)" % (name, username, hostname)
|
||||
|
||||
def _format_author(self, irc, author):
|
||||
if isinstance(author, str):
|
||||
# it's an URL
|
||||
try:
|
||||
author = self._get_actor(irc, author)
|
||||
except ap.ActivityPubError as e:
|
||||
return _("<error: %s>") % str(e)
|
||||
else:
|
||||
return self._format_actor_fullname(author)
|
||||
elif isinstance(author, dict):
|
||||
if author.get("id"):
|
||||
return self._format_author(irc, author["id"])
|
||||
elif isinstance(author, list):
|
||||
return format("%L", [self._format_author(irc, item) for item in author])
|
||||
else:
|
||||
return "<unknown>"
|
||||
|
||||
def _format_status(self, irc, msg, status):
|
||||
if status["type"] == "Create":
|
||||
return self._format_status(irc, msg, status["object"])
|
||||
elif status["type"] == "Note":
|
||||
author_url = status["attributedTo"]
|
||||
try:
|
||||
author = self._get_actor(irc, author_url)
|
||||
except ap.ActivityPubError as e:
|
||||
author_fullname = _("<error: %s>") % str(e)
|
||||
else:
|
||||
author_fullname = self._format_actor_fullname(author)
|
||||
cw = status.get("summary")
|
||||
author_fullname = self._format_author(irc, status.get("attributedTo"))
|
||||
if cw:
|
||||
if self.registryValue(
|
||||
"format.statuses.showContentWithCW",
|
||||
@ -275,6 +287,15 @@ class Fediverse(callbacks.PluginRegexp):
|
||||
return self._format_status(irc, msg, status)
|
||||
except ap.ActivityPubProtocolError as e:
|
||||
return "<Could not fetch status: %s>" % e.args[0]
|
||||
elif status["type"] == "Video":
|
||||
author_fullname = self._format_author(irc, status.get("attributedTo"))
|
||||
return format(
|
||||
_("\x02%s\x02 (%T) by %s: %s"),
|
||||
status["name"],
|
||||
abs(parse_xsd_duration(status["duration"]).total_seconds()),
|
||||
author_fullname,
|
||||
status["content"],
|
||||
)
|
||||
else:
|
||||
assert False, "Unknown status type %s: %r" % (
|
||||
status["type"],
|
||||
|
@ -60,6 +60,10 @@ from .test_data import (
|
||||
BOOSTED_DATA,
|
||||
BOOSTED_ACTOR_URL,
|
||||
BOOSTED_ACTOR_DATA,
|
||||
PEERTUBE_VIDEO_URL,
|
||||
PEERTUBE_VIDEO_DATA,
|
||||
PEERTUBE_ACTOR_URL,
|
||||
PEERTUBE_ACTOR_DATA,
|
||||
)
|
||||
|
||||
|
||||
@ -430,6 +434,21 @@ class NetworklessFediverseTestCase(BaseFediverseTestCase):
|
||||
+ "<https://example.net/system/media_attachments/image.png>",
|
||||
)
|
||||
|
||||
def testVideo(self):
|
||||
expected_requests = [
|
||||
(PEERTUBE_VIDEO_URL, PEERTUBE_VIDEO_DATA),
|
||||
(PEERTUBE_ACTOR_URL, PEERTUBE_ACTOR_DATA),
|
||||
(ACTOR_URL, ACTOR_DATA),
|
||||
]
|
||||
|
||||
with self.mockRequests(expected_requests):
|
||||
self.assertResponse(
|
||||
"status https://example.org/w/gABde9e210FGHre",
|
||||
"\x02name of video\x02 (1 hour, 26 minutes, and 0 seconds) "
|
||||
"by \x02chocobozzz\x02 (@chocobozzz@peertube.cpy.re) "
|
||||
"and \x02someuser\x02 (@someuser@example.org): description of video"
|
||||
)
|
||||
|
||||
def testStatusUrlSnarferDisabled(self):
|
||||
with self.mockWebfingerSupport("not called"), self.mockRequests([]):
|
||||
self.assertSnarfNoResponse(
|
||||
|
@ -384,3 +384,180 @@ BOOSTED_ACTOR_VALUE = {
|
||||
"endpoints": {"sharedInbox": "https://example.net/inbox"},
|
||||
}
|
||||
BOOSTED_ACTOR_DATA = json.dumps(BOOSTED_ACTOR_VALUE).encode()
|
||||
|
||||
PEERTUBE_ACTOR_VALUE = {
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"RsaSignature2017": "https://w3id.org/security#RsaSignature2017"
|
||||
},
|
||||
{
|
||||
"pt": "https://joinpeertube.org/ns#",
|
||||
"sc": "http://schema.org/",
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "Person",
|
||||
"id": "https://peertube.cpy.re/accounts/chocobozzz",
|
||||
"following": "https://peertube.cpy.re/accounts/chocobozzz/following",
|
||||
"followers": "https://peertube.cpy.re/accounts/chocobozzz/followers",
|
||||
"playlists": "https://peertube.cpy.re/accounts/chocobozzz/playlists",
|
||||
"inbox": "https://peertube.cpy.re/accounts/chocobozzz/inbox",
|
||||
"outbox": "https://peertube.cpy.re/accounts/chocobozzz/outbox",
|
||||
"preferredUsername": "chocobozzz",
|
||||
"url": "https://peertube.cpy.re/accounts/chocobozzz",
|
||||
"name": "chocobozzz",
|
||||
"published": "2017-11-28T08:48:24.271Z",
|
||||
"summary": None
|
||||
}
|
||||
PEERTUBE_ACTOR_DATA = json.dumps(PEERTUBE_ACTOR_VALUE).encode()
|
||||
PEERTUBE_ACTOR_URL = "https://peertube.cpy.re/accounts/chocobozzz"
|
||||
|
||||
|
||||
PEERTUBE_VIDEO_VALUE = {
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"RsaSignature2017": "https://w3id.org/security#RsaSignature2017"
|
||||
},
|
||||
{
|
||||
"pt": "https://joinpeertube.org/ns#",
|
||||
"sc": "http://schema.org/",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"uuid": "sc:identifier",
|
||||
"category": "sc:category",
|
||||
"licence": "sc:license",
|
||||
"subtitleLanguage": "sc:subtitleLanguage",
|
||||
"sensitive": "as:sensitive",
|
||||
"language": "sc:inLanguage",
|
||||
"icons": "as:icon",
|
||||
"isLiveBroadcast": "sc:isLiveBroadcast",
|
||||
"liveSaveReplay": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:liveSaveReplay"
|
||||
},
|
||||
"permanentLive": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:permanentLive"
|
||||
},
|
||||
"latencyMode": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:latencyMode"
|
||||
},
|
||||
"Infohash": "pt:Infohash",
|
||||
"originallyPublishedAt": "sc:datePublished",
|
||||
"views": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:views"
|
||||
},
|
||||
"state": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:state"
|
||||
},
|
||||
"size": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:size"
|
||||
},
|
||||
"fps": {
|
||||
"@type": "sc:Number",
|
||||
"@id": "pt:fps"
|
||||
},
|
||||
"commentsEnabled": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:commentsEnabled"
|
||||
},
|
||||
"downloadEnabled": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:downloadEnabled"
|
||||
},
|
||||
"waitTranscoding": {
|
||||
"@type": "sc:Boolean",
|
||||
"@id": "pt:waitTranscoding"
|
||||
},
|
||||
"support": {
|
||||
"@type": "sc:Text",
|
||||
"@id": "pt:support"
|
||||
},
|
||||
"likes": {
|
||||
"@id": "as:likes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"dislikes": {
|
||||
"@id": "as:dislikes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"shares": {
|
||||
"@id": "as:shares",
|
||||
"@type": "@id"
|
||||
},
|
||||
"comments": {
|
||||
"@id": "as:comments",
|
||||
"@type": "@id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Video",
|
||||
"name": "name of video",
|
||||
"duration": "PT5160S",
|
||||
"tag": [
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"name": "vostfr"
|
||||
}
|
||||
],
|
||||
"category": {
|
||||
"identifier": "2",
|
||||
"name": "Films"
|
||||
},
|
||||
"licence": {
|
||||
"identifier": "4",
|
||||
"name": "Attribution - Non Commercial"
|
||||
},
|
||||
"language": {
|
||||
"identifier": "en",
|
||||
"name": "English"
|
||||
},
|
||||
"views": 13718,
|
||||
"sensitive": False,
|
||||
"waitTranscoding": False,
|
||||
"state": 1,
|
||||
"commentsEnabled": True,
|
||||
"downloadEnabled": True,
|
||||
"published": "2017-10-23T07:54:38.155Z",
|
||||
"originallyPublishedAt": None,
|
||||
"updated": "2022-07-13T07:03:12.373Z",
|
||||
"mediaType": "text/markdown",
|
||||
"content": "description of video",
|
||||
"support": None,
|
||||
"subtitleLanguage": [],
|
||||
"icon": [
|
||||
# redacted
|
||||
],
|
||||
"url": [
|
||||
# redacted
|
||||
],
|
||||
"attributedTo": [
|
||||
{
|
||||
"type": "Person",
|
||||
"id": PEERTUBE_ACTOR_URL
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"id": ACTOR_URL,
|
||||
}
|
||||
],
|
||||
"isLiveBroadcast": False,
|
||||
"liveSaveReplay": None,
|
||||
"permanentLive": None,
|
||||
"latencyMode": None,
|
||||
}
|
||||
PEERTUBE_VIDEO_DATA = json.dumps(PEERTUBE_VIDEO_VALUE).encode()
|
||||
PEERTUBE_VIDEO_URL = "https://example.org/w/gABde9e210FGHre"
|
||||
|
63
plugins/Fediverse/utils.py
Normal file
63
plugins/Fediverse/utils.py
Normal file
@ -0,0 +1,63 @@
|
||||
###
|
||||
# Copyright (c) 2022, Valentin Lorentz
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
###
|
||||
|
||||
import re
|
||||
import datetime
|
||||
|
||||
# Credits for the regexp and function: https://stackoverflow.com/a/2765366/539465
|
||||
_XSD_DURATION_RE = re.compile(
|
||||
"(?P<sign>-?)P"
|
||||
"(?:(?P<years>\d+)Y)?"
|
||||
"(?:(?P<months>\d+)M)?"
|
||||
"(?:(?P<days>\d+)D)?"
|
||||
"(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?"
|
||||
)
|
||||
|
||||
|
||||
def parse_xsd_duration(s):
|
||||
"""Parses this format to a timedelta:
|
||||
https://www.w3.org/TR/xmlschema11-2/#duration"""
|
||||
# Fetch the match groups with default value of 0 (not None)
|
||||
duration = _XSD_DURATION_RE.match(s).groupdict(0)
|
||||
|
||||
# Create the timedelta object from extracted groups
|
||||
delta = datetime.timedelta(
|
||||
days=int(duration["days"])
|
||||
+ (int(duration["months"]) * 30)
|
||||
+ (int(duration["years"]) * 365),
|
||||
hours=int(duration["hours"]),
|
||||
minutes=int(duration["minutes"]),
|
||||
seconds=int(duration["seconds"]),
|
||||
)
|
||||
|
||||
if duration["sign"] == "-":
|
||||
delta *= -1
|
||||
|
||||
return delta
|
Loading…
Reference in New Issue
Block a user