Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add feature to call webhook on publish #109

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ wget = ["wget", "-q", "-O", "--TARGETPATH--", "--", "--DOWNLOADURL--"]
# otherwise it"s disabled.
[voctoweb]
enable_default = false
# e.g. https://exmaple.com/api/
# e.g. https://exmaple.com/api/ - with trailing slash
api_url = "<voctoweb api url>"
api_key = "<voctoweb key>"
# url your frontend is reachable on, used to build urls in tweets / toots
# url your frontend is reachable on, used to build urls in tweets / toots - no trailing slash
frontend_url = "<voctoweb frontend url>"
# cdn url, used for webhook notifications - no trailing slash
cdn_url = "<voctoweb cdn url>"
# instance name is the name you refer to you"re instead with, its used for the tweets / toots
instance_name = "<voctoweb instance name>"
ssh_host = "<host to release files to>"
Expand Down
6 changes: 3 additions & 3 deletions voctopublish/api_client/voctoweb_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ def _connect_ssh(self):
logging.info("SSH connection established to " + str(self.ssh_host))

for dir_type, path in {
'thumbnail': self.t.voctoweb_thumb_path,
'video': self.t.voctoweb_path,
"thumbnail": self.t.voctoweb_thumb_path,
"video": self.t.voctoweb_path,
}.items():
try:
self.sftp.stat(path)
logging.debug(f'{dir_type} directory {path} already exists')
logging.debug(f"{dir_type} directory {path} already exists")
except IOError as e:
if e.errno == errno.ENOENT:
try:
Expand Down
145 changes: 145 additions & 0 deletions voctopublish/api_client/webhook_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
# Copyright (C) 2023 kunsi
# git@kunsmann.eu
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import logging

from requests import RequestException, post
from tools.announcements import EmptyAnnouncementMessage, make_message

LOG = logging.getLogger("Webhook")

"""
Webhook gets POSTed to the specified url, format is JSON:

{
"announcement": "announcement message like it gets posted to social media", # or null
"is_master": true,
"fahrplan": {
"conference": "democon",
"id": 123,
"language": "eng",
"slug": "my-super-cool-talk",
"title": "my super cool talk",
},
"voctoweb": {
"cdn_url": "https://cdn.example.com/my-video.mp4",
"enabled": true,
"format": "h264-hd",
"frontend_url": "https://example.com/my-video",
"title": "media.ccc.de".
},
"youtube": {
"enabled": true,
"urls": [
"https://example.com/asdf",
"https://example.com/uiae",
],
},
"rclone": {
"enabled": true,
"destination": "demo:/my-video.mp4",
},
}

If "enabled" is false, all other fields are missing.
"""


def send(ticket, config, voctoweb_filename, voctoweb_language, rclone):
LOG.info(f"post webhook to {ticket.webhook_url}")

r = None
result = None
try:
content = _get_json(ticket, config, voctoweb_filename, language, rclone)
LOG.debug(f"{content=}")

if ticket.webhook_user and ticket.webhook_pass:
r = post(
ticket.webhook_url,
auth=(ticket.webhook_user, ticket.webhook_pass),
json=content,
)
else:
r = post(
ticket.webhook_url,
json=content,
)
result = r.status_code
except RequestException as e:
pass

if r:
LOG.debug(f"{r.status_code=} {r.text=}")

return result


def _get_json(ticket, config, voctoweb_filename, language, rclone):
try:
message = make_message(ticket)
except EmptyAnnouncementMessage:
message = None

content = {
"announcement": message,
"is_master": ticket.master,
"fahrplan": {
"conference": ticket.acronym,
"id": ticket.fahrplan_id,
"language": language,
"slug": ticket.slug,
"title": ticket.title,
},
}

if ticket.voctoweb_enable:
content["voctoweb"] = {
"cdn_url": "{}/{}/{}/{}".format(
config["voctoweb"]["cdn_url"],
ticket.voctoweb_path,
ticket.folder,
voctoweb_filename,
),
"enabled": True,
"format": self.folder,
"frontend_url": "{}/v/{}".format(
config["voctoweb"]["frontend_url"],
ticket.slug,
),
"title": config["voctoweb"]["instance_name"],
}
else:
content["voctoweb"] = {"enabled": False}

if ticket.youtube_enable:
content["youtube"] = {
"enabled": True,
"urls": list(ticket.youtube_urls.values()),
}
else:
content["youtube"] = {"enabled": False}

if ticket.rclone_enable and rclone:
content["rclone"] = {
"destination": rclone.destination,
"enabled": True,
}
else:
content["rclone"] = {"enabled": False}

return content
22 changes: 17 additions & 5 deletions voctopublish/model/ticket_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,17 +305,29 @@ def __init__(self, ticket, ticket_id, config):
self.voctoweb_event_id = self._validate_("Voctoweb.EventId", True)

# rclone properties
rclone_enabled = self._validate_("Publishing.Rclone.Enable", True)
if rclone_enabled is None:
rclone_enabled = "yes" if config["rclone"]["enable_default"] else "no"
self.rclone_enabled = rclone_enabled == "yes"
rclone_enable = self._validate_("Publishing.Rclone.Enable", True)
if rclone_enable is None:
rclone_enable = "yes" if config["rclone"]["enable_default"] else "no"
self.rclone_enable = rclone_enable == "yes"

if self.rclone_enabled:
if self.rclone_enable:
self.rclone_destination = self._validate_("Publishing.Rclone.Destination")
self.rclone_only_master = (
self._validate_("Publishing.Rclone.OnlyMaster") == "yes"
)

# generic webhook that gets called on release
self.webhook_url = self._validate_("Publishing.Webhook.Url", True)
if self.webhook_url:
self.webhook_user = self._validate_("Publishing.Webhook.User", True)
self.webhook_pass = self._validate_("Publishing.Webhook.Password", True)
self.webhook_only_master = (
self._validate_("Publishing.Webhook.OnlyMaster", True) == "yes"
)
self.webhook_fail_on_error = (
self._validate_("Publishing.Webhook.FailOnError", True) == "yes"
)

# twitter properties
twitter_enable = self._validate_("Publishing.Twitter.Enable", True) == "yes"
if twitter_enable is None:
Expand Down
7 changes: 6 additions & 1 deletion voctopublish/tools/announcements.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ class EmptyAnnouncementMessage(Exception):
pass


def make_message(ticket, max_length=200, override_url_length=None):
def make_message(ticket, max_length=None, override_url_length=None):
if max_length is None:
# if max_length is not set, set it to something very big here.
# saves us a bunch of isinstance() calls below
max_length = 1_000_000

LOG.info(f"generating announcement message with max length of {max_length} chars")

targets = []
Expand Down
45 changes: 36 additions & 9 deletions voctopublish/voctopublish.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import api_client.googlechat_client as googlechat
import api_client.mastodon_client as mastodon
import api_client.twitter_client as twitter
import api_client.webhook_client as webhook
from api_client.rclone_client import RCloneClient
from api_client.voctoweb_client import VoctowebClient
from api_client.youtube_client import YoutubeAPI
Expand Down Expand Up @@ -203,8 +204,9 @@ def publish(self):
else:
self._publish_to_youtube()

logging.debug(f"#rclone {self.ticket.rclone_enabled}")
if self.ticket.rclone_enabled:
logging.debug(f"#rclone {self.ticket.rclone_enable}")
rclone = None
if self.ticket.rclone_enable:
if self.ticket.master or not self.ticket.rclone_only_master:
rclone = RCloneClient(self.ticket, self.config)
ret = rclone.upload()
Expand All @@ -222,6 +224,29 @@ def publish(self):
"skipping rclone because Publishing.Rclone.OnlyMaster is set to 'yes'"
)

if self.ticket.webhook_url:
if self.ticket.master or not self.ticket.webhook_only_master:
result = webhook.send(
self.ticket,
self.config,
getattr(self, "voctoweb_filename", None),
getattr(self, "voctoweb_language", ticket.language),
rclone,
)
if (
not isinstance(result, int) or result >= 300
) and self.ticket.webhook_fail_on_error:
raise PublisherException(
f"POSTing webhook to {self.ticket.webhook_url} failed with http status code {result}"
)
elif isinstance(result, int):
self.c3tt.set_ticket_properties(
self.ticket_id,
{
"Webhook.StatusCode": result,
},
)

self.c3tt.set_ticket_done(self.ticket_id)

# Twitter
Expand Down Expand Up @@ -360,25 +385,27 @@ def _publish_to_voctoweb(self):
# audio tracks of the master we need to reflect that in the target filename
if self.ticket.language_index:
index = int(self.ticket.language_index)
filename = (
self.voctoweb_filename = (
self.ticket.language_template % self.ticket.languages[index]
+ "_"
+ self.ticket.profile_slug
+ "."
+ self.ticket.profile_extension
)
language = self.ticket.languages[index]
self.voctoweb_language = self.ticket.languages[index]
else:
filename = self.ticket.filename
language = self.ticket.language
self.voctoweb_filename = self.ticket.filename
self.voctoweb_language = self.ticket.language

vw.upload_file(self.ticket.local_filename, filename, self.ticket.folder)
vw.upload_file(
self.ticket.local_filename, self.voctoweb_filename, self.ticket.folder
)

recording_id = vw.create_recording(
self.ticket.local_filename,
filename,
self.voctoweb_filename,
self.ticket.folder,
language,
self.voctoweb_language,
hq,
html5,
)
Expand Down
Loading