snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

View File

@@ -0,0 +1,33 @@
# Zulip Radarr integration
Receive Radarr notifications in Zulip!
{start_tabs}
1. {!create-an-incoming-webhook.md!}
1. {!generate-webhook-url-basic.md!}
1. Go to your Radarr dashboard. Open **Settings**, and select **Connect**.
Click the plus (**+**) icon.
1. Select **Webhook**, and set the name of the webhook to any name of your
choice, such as `Zulip`. Select the scenarios you would like to receive
notifications for. You may also enter tags if you would like to be
notified about movies with specific tags.
1. Set **URL** to the URL generated above, and set **Method** to
**POST**. Leave the **Username** and **Password** fields blank. Click
**Save**, which will send a test message to Zulip.
{end_tabs}
{!congrats.md!}
![](/static/images/integrations/radarr/001.png)
{!event-filtering-additional-feature.md!}
### Related documentation
{!webhooks-url-specification.md!}

View File

@@ -0,0 +1,6 @@
{
"message": "Radarr updated from 4.2.0.6370 to 4.2.0.6372",
"previousVersion": "4.2.0.6370",
"newVersion": "4.2.0.6372",
"eventType": "ApplicationUpdate"
}

View File

@@ -0,0 +1,7 @@
{
"level": "error",
"message": "Movie Gotham City Sirens (tmdbid 416649) was removed from TMDb",
"type": "RemovedMovieCheck",
"wikiUrl": "https://wiki.servarr.com/Radarr_System#movie_was_removed_from_tmdb",
"eventType": "Health"
}

View File

@@ -0,0 +1,7 @@
{
"level": "warning",
"message": "No download client is available",
"type": "DownloadClientCheck",
"wikiUrl": "https://wiki.servarr.com/Radarr_System#no_download_client_is_available",
"eventType": "Health"
}

View File

@@ -0,0 +1,11 @@
{
"movie": {
"id": 796,
"title": "Batman v Superman: Dawn of Justice",
"releaseDate": "2016-06-24",
"folderPath": "/home36/adbtech/media/Movies/Batman v Superman- Dawn of Justice (2016)",
"tmdbId": 209112,
"imdbId": "tt2975590"
},
"eventType": "MovieAdded"
}

View File

@@ -0,0 +1,12 @@
{
"movie": {
"id": 796,
"title": "Batman v Superman: Dawn of Justice",
"releaseDate": "2016-06-24",
"folderPath": "/home36/adbtech/media/Movies/Batman v Superman- Dawn of Justice (2016)",
"tmdbId": 209112,
"imdbId": "tt2975590"
},
"deletedFiles": true,
"eventType": "MovieDelete"
}

View File

@@ -0,0 +1,23 @@
{
"movie": {
"id": 779,
"title": "Marley & Me",
"releaseDate": "2009-03-25",
"folderPath": "/home36/adbtech/media/Movies/Marley & Me (2008)",
"tmdbId": 14306,
"imdbId": "tt0822832"
},
"movieFile": {
"id": 36664,
"relativePath": "<filename>",
"path": "<fullPath>",
"quality": "Bluray-1080p",
"qualityVersion": 1,
"releaseGroup": "<rlsGroup>",
"sceneName": "The.Movie.Title.2012.1080p.BluRay.x265-<rlsGroup>",
"indexerFlags": "0",
"size": 42
},
"deleteReason": "missingFromDisk",
"eventType": "MovieFileDelete"
}

View File

@@ -0,0 +1,27 @@
{
"movie": {
"id": 771,
"title": "Greenland",
"releaseDate": "2021-02-09",
"folderPath": "/home36/adbtech/media/Movies/Greenland (2020)",
"tmdbId": 524047,
"imdbId": "tt7737786"
},
"remoteMovie": {
"tmdbId": 524047,
"imdbId": "tt7737786",
"title": "Greenland",
"year": 2020
},
"release": {
"quality": "WEBRip-1080p",
"qualityVersion": 1,
"releaseGroup": "EVO",
"releaseTitle": "Greenland 2020 1080p AMZN WEBRip DD5 1 X 264-EVO",
"indexer": "IP Torrents - Jackett",
"size": 2319282432
},
"downloadClient": "Deluge",
"downloadId": "8DAECE475EE53205C6FEDDA67F87CDBAF15BD25A",
"eventType": "Grab"
}

View File

@@ -0,0 +1,27 @@
{
"movie": {
"id": 796,
"title": "Batman v Superman: Dawn of Justice",
"releaseDate": "2016-06-24",
"folderPath": "/home36/adbtech/media/Movies/Batman v Superman- Dawn of Justice (2016)",
"tmdbId": 209112,
"imdbId": "tt2975590"
},
"remoteMovie": {
"tmdbId": 209112,
"imdbId": "tt2975590",
"title": "Batman v Superman: Dawn of Justice",
"year": 2016
},
"movieFile": {
"id": 32,
"relativePath": "Batman v Superman- Dawn of Justice (2016) WEBDL-720p.mp4",
"path": "/home36/adbtech/Downloads/Batman.v.Superman.Dawn.of.Justice.2016.Extended.Ultimate.Edition.720p.WEB-DL.AAC.BvS.mp4",
"quality": "WEBDL-720p",
"qualityVersion": 1,
"size": 1515669476
},
"isUpgrade": false,
"downloadId": "54DD5B3DC0E345179CFEA3E1CE9CD4C63AF7A641",
"eventType": "Download"
}

View File

@@ -0,0 +1,41 @@
{
"movie": {
"id": 771,
"title": "Greenland",
"releaseDate": "2021-02-09",
"folderPath": "/home36/adbtech/media/Movies/Greenland (2020)",
"tmdbId": 524047,
"imdbId": "tt7737786"
},
"remoteMovie": {
"tmdbId": 524047,
"imdbId": "tt7737786",
"title": "Greenland",
"year": 2020
},
"movieFile": {
"id": 29,
"relativePath": "Greenland (2020) WEBRip-1080p.mkv",
"path": "/home36/adbtech/Downloads/Greenland.2020.1080p.AMZN.WEBRip.DD5.1.X.264-EVO/Greenland.2020.1080p.AMZN.WEBRip.DD5.1.X.264-EVO.mkv",
"quality": "WEBRip-1080p",
"qualityVersion": 1,
"releaseGroup": "EVO",
"sceneName": "Greenland.2020.1080p.AMZN.WEBRip.DD5.1.X.264-EVO",
"size": 2320560747
},
"isUpgrade": true,
"downloadId": "8DAECE475EE53205C6FEDDA67F87CDBAF15BD25A",
"deletedFiles": [
{
"id": 8,
"relativePath": "Greenland (2020) WEBRip-720p.mkv",
"path": "/home36/adbtech/media/Movies/Greenland (2020)/Greenland (2020) WEBRip-720p.mkv",
"quality": "WEBRip-720p",
"qualityVersion": 1,
"releaseGroup": "EVO",
"sceneName": "Greenland.2020.720p.AMZN.WEBRip.AAC2.0.X.264-EVO",
"size": 1104429335
}
],
"eventType": "Download"
}

View File

@@ -0,0 +1,11 @@
{
"movie": {
"id": 779,
"title": "Marley & Me",
"releaseDate": "2009-03-25",
"folderPath": "/home36/adbtech/media/Movies/Marley & Me (2008)",
"tmdbId": 14306,
"imdbId": "tt0822832"
},
"eventType": "Rename"
}

View File

@@ -0,0 +1,24 @@
{
"movie": {
"id": 1,
"title": "Test Title",
"releaseDate": "1970-01-01",
"folderPath": "C:\\testpath",
"tmdbId": 0
},
"remoteMovie": {
"tmdbId": 1234,
"imdbId": "5678",
"title": "Test title",
"year": 1970
},
"release": {
"quality": "Test Quality",
"qualityVersion": 1,
"releaseGroup": "Test Group",
"releaseTitle": "Test Title",
"indexer": "Test Indexer",
"size": 9999999
},
"eventType": "Test"
}

View File

@@ -0,0 +1,93 @@
from zerver.lib.test_classes import WebhookTestCase
class RadarrHookTests(WebhookTestCase):
def test_radarr_test(self) -> None:
"""
Tests if radarr test payload is handled correctly
"""
expected_topic_name = "Radarr - Test"
expected_message = "Radarr webhook has been successfully configured."
self.check_webhook("radarr_test", expected_topic_name, expected_message)
def test_radarr_application_update(self) -> None:
"""
Tests if radarr application update payload is handled correctly
"""
expected_topic_name = "Radarr - Application update"
expected_message = "Radarr was updated from 4.2.0.6370 to 4.2.0.6372."
self.check_webhook("radarr_application_update", expected_topic_name, expected_message)
def test_radarr_health_check_warning(self) -> None:
"""
Tests if radarr health check warning payload is handled correctly
"""
expected_topic_name = "Health warning"
expected_message = "No download client is available."
self.check_webhook("radarr_health_check_warning", expected_topic_name, expected_message)
def test_radarr_health_check_error(self) -> None:
"""
Tests if radarr health check error payload is handled correctly
"""
expected_topic_name = "Health error"
expected_message = "Movie Gotham City Sirens (tmdbid 416649) was removed from TMDb."
self.check_webhook("radarr_health_check_error", expected_topic_name, expected_message)
def test_radarr_movie_renamed(self) -> None:
"""
Tests if radarr movie renamed payload is handled correctly
"""
expected_topic_name = "Marley & Me"
expected_message = "The movie Marley & Me has been renamed."
self.check_webhook("radarr_movie_renamed", expected_topic_name, expected_message)
def test_radarr_movie_imported(self) -> None:
"""
Tests if radarr movie imported payload is handled correctly
"""
expected_topic_name = "Batman v Superman: Dawn of Justice"
expected_message = "The movie Batman v Superman: Dawn of Justice has been imported."
self.check_webhook("radarr_movie_imported", expected_topic_name, expected_message)
def test_radarr_movie_imported_upgrade(self) -> None:
"""
Tests if radarr movie imported upgrade payload is handled correctly
"""
expected_topic_name = "Greenland"
expected_message = "The movie Greenland has been upgraded from WEBRip-720p to WEBRip-1080p."
self.check_webhook("radarr_movie_imported_upgrade", expected_topic_name, expected_message)
def test_radarr_movie_grabbed(self) -> None:
"""
Tests if radarr movie grabbed payload is handled correctly
"""
expected_topic_name = "Greenland"
expected_message = "The movie Greenland has been grabbed."
self.check_webhook("radarr_movie_grabbed", expected_topic_name, expected_message)
def test_radarr_movie_deleted(self) -> None:
"""
Tests if radarr movie deleted payload is handled correctly
"""
expected_topic_name = "Batman v Superman: Dawn of Justice"
expected_message = (
"The movie Batman v Superman: Dawn of Justice was deleted; its files were also deleted."
)
self.check_webhook("radarr_movie_deleted", expected_topic_name, expected_message)
def test_radarr_movie_file_deleted(self) -> None:
"""
Tests if radarr movie file deleted payload is handled correctly
"""
expected_topic_name = "Marley & Me"
expected_message = "A file with quality Bluray-1080p for the movie Marley & Me was deleted, because it is missing from disk."
self.check_webhook("radarr_movie_file_deleted", expected_topic_name, expected_message)
def test_radarr_movie_added(self) -> None:
"""
Tests if radarr movie added payload is handled correctly
"""
expected_topic_name = "Batman v Superman: Dawn of Justice"
expected_message = "The movie Batman v Superman: Dawn of Justice was added."
self.check_webhook("radarr_movie_added", expected_topic_name, expected_message)

View File

@@ -0,0 +1,171 @@
from django.http import HttpRequest, HttpResponse
from zerver.decorator import webhook_view
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.validator import WildValue, check_bool, check_string, check_string_in
from zerver.lib.webhooks.common import check_send_webhook_message, get_setup_webhook_message
from zerver.models import UserProfile
RADARR_TOPIC_TEMPLATE = "{movie_title}".strip()
RADARR_TOPIC_TEMPLATE_TEST = "Radarr - Test".strip()
RADARR_TOPIC_TEMPLATE_APPLICATION_UPDATE = "Radarr - Application update"
RADARR_TOPIC_TEMPLATE_HEALTH_CHECK = "Health {level}".strip()
RADARR_MESSAGE_TEMPLATE_HEALTH_CHECK = "{message}.".strip()
RADARR_MESSAGE_TEMPLATE_APPLICATION_UPDATE = (
"Radarr was updated from {previous_version} to {new_version}."
)
RADARR_MESSAGE_TEMPLATE_MOVIE_RENAMED = "The movie {movie_title} has been renamed.".strip()
RADARR_MESSAGE_TEMPLATE_MOVIE_IMPORTED = "The movie {movie_title} has been imported.".strip()
RADARR_MESSAGE_TEMPLATE_MOVIE_IMPORTED_UPGRADE = (
"The movie {movie_title} has been upgraded from {old_quality} to {new_quality}.".strip()
)
RADARR_MESSAGE_TEMPLATE_MOVIE_GRABBED = "The movie {movie_title} has been grabbed.".strip()
RADARR_MESSAGE_TEMPLATE_MOVIE_DELETED = (
"The movie {movie_title} was deleted; its files were {deleted_files} deleted."
)
RADARR_MESSAGE_TEMPLATE_MOVIE_FILE_DELETED = (
"A file with quality {quality} for the movie {movie_title} was deleted, {reason}."
)
RADARR_MESSAGE_TEMPLATE_MOVIE_ADDED = "The movie {movie_title} was added."
ALL_EVENT_TYPES = [
"ApplicationUpdate",
"Test",
"Rename",
"Download",
"Health",
"Grab",
"MovieDelete",
"MovieFileDelete",
"MovieAdded",
]
@webhook_view("Radarr", all_event_types=ALL_EVENT_TYPES)
@typed_endpoint
def api_radarr_webhook(
request: HttpRequest,
user_profile: UserProfile,
*,
payload: JsonBodyPayload[WildValue],
) -> HttpResponse:
body = get_body_for_http_request(payload)
topic_name = get_topic_for_http_request(payload)
check_send_webhook_message(
request, user_profile, topic_name, body, payload["eventType"].tame(check_string)
)
return json_success(request)
def get_topic_for_http_request(payload: WildValue) -> str:
event_type = payload["eventType"].tame(check_string)
if event_type == "Test":
return RADARR_TOPIC_TEMPLATE_TEST
elif event_type == "ApplicationUpdate":
return RADARR_TOPIC_TEMPLATE_APPLICATION_UPDATE
elif event_type == "Health":
return RADARR_TOPIC_TEMPLATE_HEALTH_CHECK.format(level=payload["level"].tame(check_string))
else:
return RADARR_TOPIC_TEMPLATE.format(
movie_title=payload["movie"]["title"].tame(check_string)
)
def get_body_for_health_check_event(payload: WildValue) -> str:
return RADARR_MESSAGE_TEMPLATE_HEALTH_CHECK.format(
message=payload["message"].tame(check_string)
)
def get_body_for_application_update_event(payload: WildValue) -> str:
return RADARR_MESSAGE_TEMPLATE_APPLICATION_UPDATE.format(
previous_version=payload["previousVersion"].tame(check_string),
new_version=payload["newVersion"].tame(check_string),
)
def get_body_for_movie_renamed_event(payload: WildValue) -> str:
return RADARR_MESSAGE_TEMPLATE_MOVIE_RENAMED.format(
movie_title=payload["movie"]["title"].tame(check_string)
)
def get_body_for_movie_imported_upgrade_event(payload: WildValue) -> str:
data = {
"movie_title": payload["movie"]["title"].tame(check_string),
"new_quality": payload["movieFile"]["quality"].tame(check_string),
"old_quality": payload["deletedFiles"][0]["quality"].tame(check_string),
}
return RADARR_MESSAGE_TEMPLATE_MOVIE_IMPORTED_UPGRADE.format(**data)
def get_body_for_movie_imported_event(payload: WildValue) -> str:
return RADARR_MESSAGE_TEMPLATE_MOVIE_IMPORTED.format(
movie_title=payload["movie"]["title"].tame(check_string)
)
def get_body_for_movie_grabbed_event(payload: WildValue) -> str:
return RADARR_MESSAGE_TEMPLATE_MOVIE_GRABBED.format(
movie_title=payload["movie"]["title"].tame(check_string)
)
def get_body_for_movie_deleted_event(payload: WildValue) -> str:
return RADARR_MESSAGE_TEMPLATE_MOVIE_DELETED.format(
movie_title=payload["movie"]["title"].tame(check_string),
deleted_files="also" if payload["deletedFiles"].tame(check_bool) else "not",
)
def get_body_for_movie_file_deleted_event(payload: WildValue) -> str:
reasons = {
"missingFromDisk": "because it is missing from disk",
"manual": "manually",
"upgrade": "because an upgraded version exists",
"noLinkedEpisodes": "because it has no linked episodes",
"manualOverride": "via manual override",
}
return RADARR_MESSAGE_TEMPLATE_MOVIE_FILE_DELETED.format(
movie_title=payload["movie"]["title"].tame(check_string),
quality=payload["movieFile"]["quality"].tame(check_string),
reason=reasons[payload["deleteReason"].tame(check_string_in(reasons.keys()))],
)
def get_body_for_movie_added_event(payload: WildValue) -> str:
return RADARR_MESSAGE_TEMPLATE_MOVIE_ADDED.format(
movie_title=payload["movie"]["title"].tame(check_string)
)
def get_body_for_http_request(payload: WildValue) -> str:
event_type = payload["eventType"].tame(check_string)
if event_type == "Test":
return get_setup_webhook_message("Radarr")
elif event_type == "ApplicationUpdate":
return get_body_for_application_update_event(payload)
elif event_type == "Health":
return get_body_for_health_check_event(payload)
elif event_type == "Rename":
return get_body_for_movie_renamed_event(payload)
elif event_type == "Download" and "isUpgrade" in payload:
if payload["isUpgrade"]:
return get_body_for_movie_imported_upgrade_event(payload)
else:
return get_body_for_movie_imported_event(payload)
elif event_type == "Grab":
return get_body_for_movie_grabbed_event(payload)
elif event_type == "MovieDelete":
return get_body_for_movie_deleted_event(payload)
elif event_type == "MovieFileDelete":
return get_body_for_movie_file_deleted_event(payload)
elif event_type == "MovieAdded":
return get_body_for_movie_added_event(payload)
else:
raise UnsupportedWebhookEventTypeError(event_type)