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 Sonarr integration
Receive Sonarr notifications in Zulip!
{start_tabs}
1. {!create-an-incoming-webhook.md!}
1. {!generate-webhook-url-basic.md!}
1. Go to your Sonarr 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 series 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/sonarr/001.png)
{!event-filtering-additional-feature.md!}
### Related documentation
{!webhooks-url-specification.md!}

View File

@@ -0,0 +1,760 @@
{
"series": {
"id": 92,
"title": "Breaking Bad",
"path": "/home36/adbtech/media/TV Shows/Breaking Bad",
"tvdbId": 81189,
"tvMazeId": 169,
"imdbId": "tt0903747",
"type": "standard"
},
"episodes": [
{
"id": 8894,
"episodeNumber": 1,
"seasonNumber": 1,
"title": "Pilot",
"airDate": "2008-01-20",
"airDateUtc": "2008-01-21T02:00:00Z"
}
],
"episodeFile": {
"seriesId": 92,
"seasonNumber": 1,
"relativePath": "Breaking Bad - 1x01 - Pilot.mkv",
"path": "/home36/adbtech/media/TV Shows/Breaking Bad/Breaking Bad - 1x01 - Pilot.mkv",
"size": 1172453261,
"dateAdded": "2021-03-26T02:30:13.445127Z",
"quality": {
"quality": {
"id": 9,
"name": "HDTV-1080p",
"source": "television",
"resolution": 1080
},
"revision": {
"version": 1,
"real": 0,
"isRepack": false
}
},
"mediaInfo": {
"containerFormat": "Matroska",
"videoFormat": "HEVC",
"videoCodecID": "V_MPEGH/ISO/HEVC",
"videoProfile": "Main@L4@Main",
"videoCodecLibrary": "x265 - 1.9:[Windows][GCC 4.9.0][64 bit] 8bit",
"videoBitrate": 2252712,
"videoBitDepth": 8,
"videoMultiViewCount": 0,
"videoColourPrimaries": "BT.709",
"videoTransferCharacteristics": "BT.709",
"videoHdrFormat": "",
"videoHdrFormatCompatibility": "",
"width": 1920,
"height": 1080,
"audioFormat": "AC-3",
"audioCodecID": "A_AC3",
"audioCodecLibrary": "",
"audioAdditionalFeatures": "",
"audioBitrate": 384000,
"runTime": "00:58:06.5280000",
"audioStreamCount": 1,
"audioChannelsContainer": 6,
"audioChannelsStream": 0,
"audioChannelPositions": "3/2/0.1",
"audioChannelPositionsTextContainer": "Front: L C R, Side: L R, LFE",
"audioChannelPositionsTextStream": "",
"audioProfile": "",
"videoFps": 23.976,
"audioLanguages": "English",
"subtitles": "English / English / Arabic / Dutch / French / French / German / German / Hindi / Spanish / Spanish / Turkish / English / Dutch / French / German / Spanish",
"scanType": "",
"schemaRevision": 7
},
"episodes": {
"value": [
{
"seriesId": 92,
"episodeFileId": 4843,
"seasonNumber": 1,
"episodeNumber": 1,
"title": "Pilot",
"airDate": "2008-01-20",
"airDateUtc": "2008-01-21T02:00:00Z",
"overview": "When an unassuming high school chemistry teacher discovers he has a rare form of lung cancer, he decides to team up with a former student and create a top of the line crystal meth in a used RV, to provide for his family once he is gone.",
"monitored": true,
"absoluteEpisodeNumber": 1,
"unverifiedSceneNumbering": false,
"ratings": {
"votes": 12684,
"value": 7.7
},
"images": [
{
"coverType": "screenshot",
"url": "https://artworks.thetvdb.com/banners/episodes/81189/349232.jpg"
}
],
"episodeFile": {
"isLoaded": true
},
"hasFile": true,
"id": 8894
}
],
"isLoaded": true
},
"series": {
"value": {
"tvdbId": 81189,
"tvRageId": 18164,
"tvMazeId": 169,
"imdbId": "tt0903747",
"title": "Breaking Bad",
"cleanTitle": "breakingbad",
"sortTitle": "breaking bad",
"status": "ended",
"overview": "When Walter White, a chemistry teacher, is diagnosed with Stage III cancer and given a prognosis of two years left to live, he chooses to enter a dangerous world of drugs and crime with the intent to secure his family's financial security.",
"airTime": "21:00",
"monitored": true,
"qualityProfileId": 3,
"languageProfileId": 1,
"seasonFolder": true,
"lastInfoSync": "2021-03-26T02:30:13.193374Z",
"runtime": 47,
"images": [
{
"coverType": "banner",
"url": "https://artworks.thetvdb.com/banners/graphical/81189-g21.jpg"
},
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/posters/81189-10.jpg"
},
{
"coverType": "fanart",
"url": "https://artworks.thetvdb.com/banners/fanart/original/81189-21.jpg"
}
],
"seriesType": "standard",
"network": "Netflix",
"useSceneNumbering": false,
"titleSlug": "breaking-bad",
"path": "/home36/adbtech/media/TV Shows/Breaking Bad",
"year": 2008,
"ratings": {
"votes": 31714,
"value": 9.4
},
"genres": [
"Crime",
"Drama",
"Suspense",
"Thriller"
],
"actors": [
{
"name": "Bryan Cranston",
"character": "Walter White",
"images": []
},
{
"name": "Anna Gunn",
"character": "Skyler White",
"images": []
},
{
"name": "Aaron Paul",
"character": "Jesse Pinkman",
"images": []
},
{
"name": "RJ Mitte",
"character": "Walter White Jr.",
"images": []
},
{
"name": "Dean Norris",
"character": "Hank Schrader",
"images": []
},
{
"name": "Betsy Brandt",
"character": "Marie Schrader",
"images": []
},
{
"name": "Bob Odenkirk",
"character": "Saul Goodman",
"images": []
},
{
"name": "Giancarlo Esposito",
"character": "Gustavo Fring",
"images": []
},
{
"name": "Jonathan Banks",
"character": "Mike Ehrmantraut",
"images": []
}
],
"certification": "TV-MA",
"added": "2021-03-26T02:29:30.73166Z",
"firstAired": "2008-01-20T00:00:00Z",
"qualityProfile": {
"value": {
"name": "HD-720p",
"upgradeAllowed": false,
"cutoff": 4,
"items": [
{
"quality": {
"id": 0,
"name": "Unknown",
"source": "unknown",
"resolution": 0
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 1,
"name": "SDTV",
"source": "television",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"id": 1000,
"name": "WEB 480p",
"items": [
{
"quality": {
"id": 12,
"name": "WEBRip-480p",
"source": "webRip",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 8,
"name": "WEBDL-480p",
"source": "web",
"resolution": 480
},
"items": [],
"allowed": true
}
],
"allowed": true
},
{
"quality": {
"id": 2,
"name": "DVD",
"source": "dvd",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 13,
"name": "Bluray-480p",
"source": "bluray",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 4,
"name": "HDTV-720p",
"source": "television",
"resolution": 720
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 9,
"name": "HDTV-1080p",
"source": "television",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 10,
"name": "Raw-HD",
"source": "televisionRaw",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"id": 1001,
"name": "WEB 720p",
"items": [
{
"quality": {
"id": 14,
"name": "WEBRip-720p",
"source": "webRip",
"resolution": 720
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 5,
"name": "WEBDL-720p",
"source": "web",
"resolution": 720
},
"items": [],
"allowed": true
}
],
"allowed": true
},
{
"quality": {
"id": 6,
"name": "Bluray-720p",
"source": "bluray",
"resolution": 720
},
"items": [],
"allowed": true
},
{
"id": 1002,
"name": "WEB 1080p",
"items": [
{
"quality": {
"id": 15,
"name": "WEBRip-1080p",
"source": "webRip",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 3,
"name": "WEBDL-1080p",
"source": "web",
"resolution": 1080
},
"items": [],
"allowed": false
}
],
"allowed": false
},
{
"quality": {
"id": 7,
"name": "Bluray-1080p",
"source": "bluray",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 20,
"name": "Bluray-1080p Remux",
"source": "blurayRaw",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 16,
"name": "HDTV-2160p",
"source": "television",
"resolution": 2160
},
"items": [],
"allowed": false
},
{
"id": 1003,
"name": "WEB 2160p",
"items": [
{
"quality": {
"id": 17,
"name": "WEBRip-2160p",
"source": "webRip",
"resolution": 2160
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 18,
"name": "WEBDL-2160p",
"source": "web",
"resolution": 2160
},
"items": [],
"allowed": false
}
],
"allowed": false
},
{
"quality": {
"id": 19,
"name": "Bluray-2160p",
"source": "bluray",
"resolution": 2160
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 21,
"name": "Bluray-2160p Remux",
"source": "blurayRaw",
"resolution": 2160
},
"items": [],
"allowed": false
}
],
"id": 3
},
"isLoaded": true
},
"languageProfile": {
"value": {
"name": "English",
"languages": [
{
"language": {
"id": 27,
"name": "Hindi"
},
"allowed": false
},
{
"language": {
"id": 26,
"name": "Arabic"
},
"allowed": false
},
{
"language": {
"id": 13,
"name": "Vietnamese"
},
"allowed": false
},
{
"language": {
"id": 0,
"name": "Unknown"
},
"allowed": false
},
{
"language": {
"id": 17,
"name": "Turkish"
},
"allowed": false
},
{
"language": {
"id": 14,
"name": "Swedish"
},
"allowed": false
},
{
"language": {
"id": 3,
"name": "Spanish"
},
"allowed": false
},
{
"language": {
"id": 11,
"name": "Russian"
},
"allowed": false
},
{
"language": {
"id": 18,
"name": "Portuguese"
},
"allowed": false
},
{
"language": {
"id": 12,
"name": "Polish"
},
"allowed": false
},
{
"language": {
"id": 15,
"name": "Norwegian"
},
"allowed": false
},
{
"language": {
"id": 24,
"name": "Lithuanian"
},
"allowed": false
},
{
"language": {
"id": 21,
"name": "Korean"
},
"allowed": false
},
{
"language": {
"id": 8,
"name": "Japanese"
},
"allowed": false
},
{
"language": {
"id": 5,
"name": "Italian"
},
"allowed": false
},
{
"language": {
"id": 9,
"name": "Icelandic"
},
"allowed": false
},
{
"language": {
"id": 22,
"name": "Hungarian"
},
"allowed": false
},
{
"language": {
"id": 23,
"name": "Hebrew"
},
"allowed": false
},
{
"language": {
"id": 20,
"name": "Greek"
},
"allowed": false
},
{
"language": {
"id": 4,
"name": "German"
},
"allowed": false
},
{
"language": {
"id": 2,
"name": "French"
},
"allowed": false
},
{
"language": {
"id": 19,
"name": "Flemish"
},
"allowed": false
},
{
"language": {
"id": 16,
"name": "Finnish"
},
"allowed": false
},
{
"language": {
"id": 1,
"name": "English"
},
"allowed": true
},
{
"language": {
"id": 7,
"name": "Dutch"
},
"allowed": false
},
{
"language": {
"id": 6,
"name": "Danish"
},
"allowed": false
},
{
"language": {
"id": 25,
"name": "Czech"
},
"allowed": false
},
{
"language": {
"id": 10,
"name": "Chinese"
},
"allowed": false
}
],
"upgradeAllowed": false,
"cutoff": {
"id": 1,
"name": "English"
},
"id": 1
},
"isLoaded": true
},
"seasons": [
{
"seasonNumber": 0,
"monitored": false,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/81189-0.jpg"
}
]
},
{
"seasonNumber": 1,
"monitored": true,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/81189-1-3.jpg"
},
{
"coverType": "banner",
"url": "https://artworks.thetvdb.com/banners/seasonswide/81189-1.jpg"
}
]
},
{
"seasonNumber": 2,
"monitored": true,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/81189-2-17.jpg"
},
{
"coverType": "banner",
"url": "https://artworks.thetvdb.com/banners/seasonswide/81189-2.jpg"
}
]
},
{
"seasonNumber": 3,
"monitored": true,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/81189-3-16.jpg"
},
{
"coverType": "banner",
"url": "https://artworks.thetvdb.com/banners/seasonswide/81189-3.jpg"
}
]
},
{
"seasonNumber": 4,
"monitored": true,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/81189-4-15.jpg"
},
{
"coverType": "banner",
"url": "https://artworks.thetvdb.com/banners/seasonswide/81189-4.jpg"
}
]
},
{
"seasonNumber": 5,
"monitored": true,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/81189-5-18.jpg"
},
{
"coverType": "banner",
"url": "https://artworks.thetvdb.com/banners/seasonswide/81189-5.jpg"
}
]
}
],
"tags": [],
"id": 92
},
"isLoaded": true
},
"language": {
"id": 1,
"name": "English"
},
"id": 4843
},
"deleteReason": "manual",
"eventType": "EpisodeFileDelete"
}

View File

@@ -0,0 +1,720 @@
{
"series": {
"id": 11,
"title": "S.W.A.T. (2017)",
"path": "/home36/adbtech/media/TV Shows/S.W.A.T. (2017)",
"tvdbId": 328687,
"tvMazeId": 21532,
"imdbId": "tt6111130",
"type": "standard"
},
"episodes": [
{
"id": 8198,
"episodeNumber": 10,
"seasonNumber": 4,
"title": "Buried",
"airDate": "2021-03-03",
"airDateUtc": "2021-03-04T03:00:00Z"
}
],
"episodeFile": {
"seriesId": 11,
"seasonNumber": 4,
"relativePath": "Season 4/S.W.A.T. (2017) - S04E10 - Buried SDTV.mkv",
"path": "/home36/adbtech/media/TV Shows/S.W.A.T. (2017)/Season 4/S.W.A.T. (2017) - S04E10 - Buried SDTV.mkv",
"size": 401690753,
"dateAdded": "2021-03-04T05:05:36.86463Z",
"originalFilePath": "S.W.A.T.2017.S04E10.HDTV.x264-PHOENiX[eztv.re].mkv",
"sceneName": "S.W.A.T.2017.S04E10.HDTV.x264-PHOENiX[eztv.re]",
"releaseGroup": "PHOENiX",
"quality": {
"quality": {
"id": 1,
"name": "SDTV",
"source": "television",
"resolution": 480
},
"revision": {
"version": 1,
"real": 0,
"isRepack": false
}
},
"mediaInfo": {
"containerFormat": "Matroska",
"videoFormat": "AVC",
"videoCodecID": "V_MPEG4/ISO/AVC",
"videoProfile": "High@L3",
"videoCodecLibrary": "x264 - core 157",
"videoBitrate": 6572890,
"videoBitDepth": 8,
"videoMultiViewCount": 0,
"videoColourPrimaries": "",
"videoTransferCharacteristics": "",
"width": 720,
"height": 400,
"audioFormat": "AAC",
"audioCodecID": "A_AAC-2",
"audioCodecLibrary": "Lavc58.35.100 aac",
"audioAdditionalFeatures": "LC",
"audioBitrate": 384000,
"runTime": "00:42:14.1760000",
"audioStreamCount": 1,
"audioChannelsContainer": 2,
"audioChannelsStream": 0,
"audioChannelPositions": "2/0/0",
"audioChannelPositionsTextContainer": "Front: L R",
"audioProfile": "",
"videoFps": 23.976,
"audioLanguages": "English",
"subtitles": "",
"scanType": "Progressive",
"schemaRevision": 5
},
"episodes": {
"value": [
{
"seriesId": 11,
"episodeFileId": 4644,
"seasonNumber": 4,
"episodeNumber": 10,
"title": "Buried",
"airDate": "2021-03-03",
"airDateUtc": "2021-03-04T03:00:00Z",
"overview": "As the SWAT team helps the LAPD search for the prime suspect in a woman’s disappearance, they’re joined in the field by a new team member. The personal strain between Chris and Street comes to a head as both await Hicks’ decision on who the winner is of a prestigious leadership competition.",
"monitored": true,
"absoluteEpisodeNumber": 76,
"unverifiedSceneNumbering": false,
"ratings": {
"votes": 0,
"value": 0.0
},
"images": [
{
"coverType": "screenshot",
"url": "https://artworks.thetvdb.com/banners/series/328687/episodes/604212e57b8d2.jpg"
}
],
"lastSearchTime": "2021-03-26T02:33:32.478455Z",
"episodeFile": {
"isLoaded": true
},
"hasFile": true,
"id": 8198
}
],
"isLoaded": true
},
"series": {
"value": {
"tvdbId": 328687,
"tvRageId": 0,
"tvMazeId": 21532,
"imdbId": "tt6111130",
"title": "S.W.A.T. (2017)",
"cleanTitle": "swt2017",
"sortTitle": "s w t 2017",
"status": "continuing",
"overview": "Los Angeles S.W.A.T. Lieutenant Daniel ‘Hondo’ Harrelson is assigned to lead a highly-skilled unit in the community where he grew up. Torn between loyalty to the streets where the cops are sometimes the enemy, and allegiance to his brothers in blue, he strategically straddles these two worlds. Hondo encourages his team to rely on communication and respect over force and aggression, but when a crisis erupts, these unflinching men and women are prepared to put their tactical training to the test.",
"airTime": "22:00",
"monitored": true,
"qualityProfileId": 3,
"languageProfileId": 1,
"seasonFolder": true,
"lastInfoSync": "2021-03-26T02:29:02.009181Z",
"runtime": 45,
"images": [
{
"coverType": "banner",
"url": "https://artworks.thetvdb.com/banners/graphical/328687-g.jpg"
},
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/posters/328687-2.jpg"
},
{
"coverType": "fanart",
"url": "https://artworks.thetvdb.com/banners/fanart/original/5d4d461189894.jpg"
}
],
"seriesType": "standard",
"network": "CBS",
"useSceneNumbering": false,
"titleSlug": "s-w-a-t-2017",
"path": "/home36/adbtech/media/TV Shows/S.W.A.T. (2017)",
"year": 2017,
"ratings": {
"votes": 460,
"value": 7.5
},
"genres": [
"Action",
"Crime",
"Drama"
],
"actors": [
{
"name": "Shemar Moore",
"character": "Daniel “Hondo” Harrelson",
"images": []
},
{
"name": "Jay Harrington",
"character": "David “Deacon” Kay",
"images": []
},
{
"name": "Alex Russell",
"character": "Jim Street",
"images": []
},
{
"name": "Lina Esco",
"character": "Christina “Chris” Alonso",
"images": []
},
{
"name": "Kenny Johnson",
"character": "Dominique Luca",
"images": []
},
{
"name": "David Lim",
"character": "Victor Tan",
"images": []
},
{
"name": "Patrick St. Esprit",
"character": "Robert Hicks",
"images": []
},
{
"name": "Peter Onorati",
"character": "Jeff Mumford",
"images": []
},
{
"name": "Stephanie Sigman",
"character": "Jessica Cortez",
"images": []
},
{
"name": "Amy Farrington",
"character": "Piper Lynch",
"images": []
}
],
"certification": "TV-14",
"added": "2020-05-04T12:01:55.170293Z",
"firstAired": "2017-11-02T00:00:00Z",
"qualityProfile": {
"value": {
"name": "HD-720p",
"upgradeAllowed": false,
"cutoff": 4,
"items": [
{
"quality": {
"id": 0,
"name": "Unknown",
"source": "unknown",
"resolution": 0
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 1,
"name": "SDTV",
"source": "television",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"id": 1000,
"name": "WEB 480p",
"items": [
{
"quality": {
"id": 12,
"name": "WEBRip-480p",
"source": "webRip",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 8,
"name": "WEBDL-480p",
"source": "web",
"resolution": 480
},
"items": [],
"allowed": true
}
],
"allowed": true
},
{
"quality": {
"id": 2,
"name": "DVD",
"source": "dvd",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 13,
"name": "Bluray-480p",
"source": "bluray",
"resolution": 480
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 4,
"name": "HDTV-720p",
"source": "television",
"resolution": 720
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 9,
"name": "HDTV-1080p",
"source": "television",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 10,
"name": "Raw-HD",
"source": "televisionRaw",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"id": 1001,
"name": "WEB 720p",
"items": [
{
"quality": {
"id": 14,
"name": "WEBRip-720p",
"source": "webRip",
"resolution": 720
},
"items": [],
"allowed": true
},
{
"quality": {
"id": 5,
"name": "WEBDL-720p",
"source": "web",
"resolution": 720
},
"items": [],
"allowed": true
}
],
"allowed": true
},
{
"quality": {
"id": 6,
"name": "Bluray-720p",
"source": "bluray",
"resolution": 720
},
"items": [],
"allowed": true
},
{
"id": 1002,
"name": "WEB 1080p",
"items": [
{
"quality": {
"id": 15,
"name": "WEBRip-1080p",
"source": "webRip",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 3,
"name": "WEBDL-1080p",
"source": "web",
"resolution": 1080
},
"items": [],
"allowed": false
}
],
"allowed": false
},
{
"quality": {
"id": 7,
"name": "Bluray-1080p",
"source": "bluray",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 20,
"name": "Bluray-1080p Remux",
"source": "blurayRaw",
"resolution": 1080
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 16,
"name": "HDTV-2160p",
"source": "television",
"resolution": 2160
},
"items": [],
"allowed": false
},
{
"id": 1003,
"name": "WEB 2160p",
"items": [
{
"quality": {
"id": 17,
"name": "WEBRip-2160p",
"source": "webRip",
"resolution": 2160
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 18,
"name": "WEBDL-2160p",
"source": "web",
"resolution": 2160
},
"items": [],
"allowed": false
}
],
"allowed": false
},
{
"quality": {
"id": 19,
"name": "Bluray-2160p",
"source": "bluray",
"resolution": 2160
},
"items": [],
"allowed": false
},
{
"quality": {
"id": 21,
"name": "Bluray-2160p Remux",
"source": "blurayRaw",
"resolution": 2160
},
"items": [],
"allowed": false
}
],
"id": 3
},
"isLoaded": true
},
"languageProfile": {
"value": {
"name": "English",
"languages": [
{
"language": {
"id": 27,
"name": "Hindi"
},
"allowed": false
},
{
"language": {
"id": 26,
"name": "Arabic"
},
"allowed": false
},
{
"language": {
"id": 13,
"name": "Vietnamese"
},
"allowed": false
},
{
"language": {
"id": 0,
"name": "Unknown"
},
"allowed": false
},
{
"language": {
"id": 17,
"name": "Turkish"
},
"allowed": false
},
{
"language": {
"id": 14,
"name": "Swedish"
},
"allowed": false
},
{
"language": {
"id": 3,
"name": "Spanish"
},
"allowed": false
},
{
"language": {
"id": 11,
"name": "Russian"
},
"allowed": false
},
{
"language": {
"id": 18,
"name": "Portuguese"
},
"allowed": false
},
{
"language": {
"id": 12,
"name": "Polish"
},
"allowed": false
},
{
"language": {
"id": 15,
"name": "Norwegian"
},
"allowed": false
},
{
"language": {
"id": 24,
"name": "Lithuanian"
},
"allowed": false
},
{
"language": {
"id": 21,
"name": "Korean"
},
"allowed": false
},
{
"language": {
"id": 8,
"name": "Japanese"
},
"allowed": false
},
{
"language": {
"id": 5,
"name": "Italian"
},
"allowed": false
},
{
"language": {
"id": 9,
"name": "Icelandic"
},
"allowed": false
},
{
"language": {
"id": 22,
"name": "Hungarian"
},
"allowed": false
},
{
"language": {
"id": 23,
"name": "Hebrew"
},
"allowed": false
},
{
"language": {
"id": 20,
"name": "Greek"
},
"allowed": false
},
{
"language": {
"id": 4,
"name": "German"
},
"allowed": false
},
{
"language": {
"id": 2,
"name": "French"
},
"allowed": false
},
{
"language": {
"id": 19,
"name": "Flemish"
},
"allowed": false
},
{
"language": {
"id": 16,
"name": "Finnish"
},
"allowed": false
},
{
"language": {
"id": 1,
"name": "English"
},
"allowed": true
},
{
"language": {
"id": 7,
"name": "Dutch"
},
"allowed": false
},
{
"language": {
"id": 6,
"name": "Danish"
},
"allowed": false
},
{
"language": {
"id": 25,
"name": "Czech"
},
"allowed": false
},
{
"language": {
"id": 10,
"name": "Chinese"
},
"allowed": false
}
],
"upgradeAllowed": false,
"cutoff": {
"id": 1,
"name": "English"
},
"id": 1
},
"isLoaded": true
},
"seasons": [
{
"seasonNumber": 1,
"monitored": false,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/328687-1.jpg"
}
]
},
{
"seasonNumber": 2,
"monitored": false,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/seasons/5baf03e6b83dd.jpg"
}
]
},
{
"seasonNumber": 3,
"monitored": true,
"images": [
{
"coverType": "poster",
"url": "https://artworks.thetvdb.com/banners/series/328687/seasons/814708/posters/62060192.jpg"
}
]
},
{
"seasonNumber": 4,
"monitored": true,
"images": []
}
],
"tags": [],
"id": 11
},
"isLoaded": true
},
"language": {
"id": 1,
"name": "English"
},
"id": 4644
},
"deleteReason": "upgrade",
"eventType": "EpisodeFileDelete"
}

View File

@@ -0,0 +1,32 @@
{
"series": {
"id": 7,
"title": "NCIS",
"path": "/home36/adbtech/media/TV Shows/NCIS",
"tvdbId": 72108,
"tvMazeId": 60,
"imdbId": "tt0364845",
"type": "standard"
},
"episodes": [
{
"id": 8268,
"episodeNumber": 10,
"seasonNumber": 18,
"title": "Watchdog",
"airDate": "2021-03-16",
"airDateUtc": "2021-03-17T00:00:00Z"
}
],
"release": {
"quality": "HDTV-720p",
"qualityVersion": 1,
"releaseGroup": "MeGusta",
"releaseTitle": "NCIS S18E10 720p HEVC x265-MeGusta",
"indexer": "IP Torrents - Jackett",
"size": 415236096
},
"downloadClient": "Deluge",
"downloadId": "2FBD49EF1D9934F560E4806E001C6DE494D23FD6",
"eventType": "Grab"
}

View File

@@ -0,0 +1,35 @@
{
"series": {
"id": 45,
"title": "Grey's Anatomy",
"path": "/home36/adbtech/media/TV Shows/Grey's Anatomy",
"tvdbId": 73762,
"tvMazeId": 67,
"imdbId": "tt0413573",
"type": "standard"
},
"episodes": [
{
"id": 8507,
"episodeNumber": 9,
"seasonNumber": 17,
"title": "In My Life",
"airDate": "2021-03-25",
"airDateUtc": "2021-03-26T01:00:00Z"
}
],
"episodeFile": {
"id": 4834,
"relativePath": "Season 17/Grey's Anatomy - S17E09 - In My Life HDTV-720p.mkv",
"path": "/home36/adbtech/Downloads/Greys.Anatomy.S17E09.720p.HDTV.x264-SYNCOPY/greys.anatomy.s17e09.720p.hdtv.x264-syncopy.mkv",
"quality": "HDTV-720p",
"qualityVersion": 1,
"releaseGroup": "SYNCOPY",
"sceneName": "Greys.Anatomy.S17E09.720p.HDTV.x264-SYNCOPY",
"size": 832160743
},
"isUpgrade": false,
"downloadClient": "Deluge",
"downloadId": "D93392B18D20704D1BE349FA314FACCB7B5DF2C3",
"eventType": "Download"
}

View File

@@ -0,0 +1,47 @@
{
"series": {
"id": 7,
"title": "NCIS",
"path": "/home36/adbtech/media/TV Shows/NCIS",
"tvdbId": 72108,
"tvMazeId": 60,
"imdbId": "tt0364845",
"type": "standard"
},
"episodes": [
{
"id": 8268,
"episodeNumber": 10,
"seasonNumber": 18,
"title": "Watchdog",
"airDate": "2021-03-16",
"airDateUtc": "2021-03-17T00:00:00Z"
}
],
"episodeFile": {
"id": 4835,
"relativePath": "Season 18/NCIS - S18E10 - Watchdog HDTV-720p.mkv",
"path": "/home36/adbtech/Downloads/NCIS.S18E10.720p.HEVC.x265-MeGusta/NCIS.S18E10.720p.HEVC.x265-MeGusta.mkv",
"quality": "HDTV-720p",
"qualityVersion": 1,
"releaseGroup": "MeGusta",
"sceneName": "NCIS.S18E10.720p.HEVC.x265-MeGusta",
"size": 406824797
},
"isUpgrade": true,
"downloadClient": "Deluge",
"downloadId": "2FBD49EF1D9934F560E4806E001C6DE494D23FD6",
"deletedFiles": [
{
"id": 4772,
"relativePath": "Season 18/NCIS - S18E10 - Watchdog SDTV.mkv",
"path": "/home36/adbtech/media/TV Shows/NCIS/Season 18/NCIS - S18E10 - Watchdog SDTV.mkv",
"quality": "SDTV",
"qualityVersion": 1,
"releaseGroup": "PHOENiX",
"sceneName": "NCIS.S18E10.HDTV.x264-PHOENiX",
"size": 318197105
}
],
"eventType": "Download"
}

View File

@@ -0,0 +1,12 @@
{
"series": {
"id": 85,
"title": "The L Word",
"path": "/home36/adbtech/media/TV Shows/The L Word",
"tvdbId": 72477,
"tvMazeId": 823,
"imdbId": "tt0330251",
"type": "standard"
},
"eventType": "Rename"
}

View File

@@ -0,0 +1,7 @@
{
"level": "error",
"message": "No indexers available with RSS sync enabled, Sonarr will not grab new releases automatically",
"type": "IndexerRssCheck",
"wikiUrl": "https://wiki.servarr.com/Sonarr_System#no_indexers_available_with_rss_sync_enabled_sonarr_will_not_grab_new_releases_automatically",
"eventType": "Health"
}

View File

@@ -0,0 +1,7 @@
{
"level": "warning",
"message": "Indexers unavailable due to failures for more than 6 hours: Academic Torrents - Jackett, ACG - Jackett, KickAssTorrent - Jackett, EXT Torrents - Jackett, Extra Torrents - Jackett, SkyTorrents - Jackett, iDope - Jackett",
"type": "IndexerLongTermStatusCheck",
"wikiUrl": "https://github.com/Sonarr/Sonarr/wiki/Health-checks#indexers-are-unavailable-due-to-failures",
"eventType": "Health"
}

View File

@@ -0,0 +1,13 @@
{
"series": {
"id": 90,
"title": "Breaking Bad",
"path": "/home36/adbtech/media/TV Shows/Breaking Bad",
"tvdbId": 81189,
"tvMazeId": 169,
"imdbId": "tt0903747",
"type": "standard"
},
"deletedFiles": true,
"eventType": "SeriesDelete"
}

View File

@@ -0,0 +1,19 @@
{
"series": {
"id": 1,
"title": "Test Title",
"path": "C:\\testpath",
"tvdbId": 1234,
"tvMazeId": 0,
"type": "standard"
},
"episodes": [
{
"id": 123,
"episodeNumber": 1,
"seasonNumber": 1,
"title": "Test title"
}
],
"eventType": "Test"
}

View File

@@ -0,0 +1,85 @@
from zerver.lib.test_classes import WebhookTestCase
class SonarrHookTests(WebhookTestCase):
def test_sonarr_test(self) -> None:
"""
Tests if sonarr test payload is handled correctly
"""
expected_topic_name = "Sonarr - Test"
expected_message = "Sonarr webhook has been successfully configured."
self.check_webhook("sonarr_test", expected_topic_name, expected_message)
def test_sonarr_series_deleted(self) -> None:
"""
Tests if sonarr series deleted payload is handled correctly
"""
expected_topic_name = "Breaking Bad"
expected_message = "Breaking Bad has been deleted."
self.check_webhook("sonarr_series_deleted", expected_topic_name, expected_message)
def test_sonarr_health_check_warning(self) -> None:
"""
Tests if sonarr health check warning payload is handled correctly
"""
expected_topic_name = "Health warning"
expected_message = "Indexers unavailable due to failures for more than 6 hours: Academic Torrents - Jackett, ACG - Jackett, KickAssTorrent - Jackett, EXT Torrents - Jackett, Extra Torrents - Jackett, SkyTorrents - Jackett, iDope - Jackett."
self.check_webhook("sonarr_health_check_warning", expected_topic_name, expected_message)
def test_sonarr_health_check_error(self) -> None:
"""
Tests if sonarr health check error payload is handled correctly
"""
expected_topic_name = "Health error"
expected_message = "No indexers available with RSS sync enabled, Sonarr will not grab new releases automatically."
self.check_webhook("sonarr_health_check_error", expected_topic_name, expected_message)
def test_sonarr_episodes_renamed(self) -> None:
"""
Tests if sonarr episodes renamed payload is handled correctly
"""
expected_topic_name = "The L Word"
expected_message = "The L Word episodes have been renamed."
self.check_webhook("sonarr_episodes_renamed", expected_topic_name, expected_message)
def test_sonarr_episode_imported(self) -> None:
"""
Tests if sonarr episode imported payload is handled correctly
"""
expected_topic_name = "Grey's Anatomy"
expected_message = "Grey's Anatomy - 17x9 - In My Life has been imported."
self.check_webhook("sonarr_episode_imported", expected_topic_name, expected_message)
def test_sonarr_episode_imported_upgrade(self) -> None:
"""
Tests if sonarr episode imported upgrade payload is handled correctly
"""
expected_topic_name = "NCIS"
expected_message = "NCIS - 18x10 - Watchdog has been upgraded from SDTV to HDTV-720p."
self.check_webhook("sonarr_episode_imported_upgrade", expected_topic_name, expected_message)
def test_sonarr_episode_grabbed(self) -> None:
"""
Tests if sonarr episode grabbed payload is handled correctly
"""
expected_topic_name = "NCIS"
expected_message = "NCIS - 18x10 - Watchdog has been grabbed."
self.check_webhook("sonarr_episode_grabbed", expected_topic_name, expected_message)
def test_sonarr_episode_deleted(self) -> None:
"""
Tests if sonarr episode deleted payload is handled correctly
"""
expected_topic_name = "Breaking Bad"
expected_message = "Breaking Bad - 1x1 - Pilot has been deleted."
self.check_webhook("sonarr_episode_deleted", expected_topic_name, expected_message)
def test_sonarr_episode_deleted_upgrade(self) -> None:
"""
Tests if sonarr episode deleted upgrade payload is handled correctly
"""
expected_topic_name = "S.W.A.T. (2017)"
expected_message = (
"S.W.A.T. (2017) - 4x10 - Buried has been deleted due to quality upgrade."
)
self.check_webhook("sonarr_episode_deleted_upgrade", expected_topic_name, expected_message)

View File

@@ -0,0 +1,173 @@
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_int, check_string
from zerver.lib.webhooks.common import check_send_webhook_message, get_setup_webhook_message
from zerver.models import UserProfile
SONARR_TOPIC_TEMPLATE = "{series_title}".strip()
SONARR_TOPIC_TEMPLATE_TEST = "Sonarr - Test".strip()
SONARR_TOPIC_TEMPLATE_HEALTH_CHECK = "Health {level}".strip()
SONARR_MESSAGE_TEMPLATE_SERIES_DELETED = "{series_title} has been deleted.".strip()
SONARR_MESSAGE_TEMPLATE_HEALTH_CHECK = "{message}.".strip()
SONARR_MESSAGE_TEMPLATE_EPISODES_RENAMED = "{series_title} episodes have been renamed.".strip()
SONARR_MESSAGE_TEMPLATE_EPISODE_IMPORTED = (
"{series_title} - {series_number}x{episode_number} - {episode_name} has been imported.".strip()
)
SONARR_MESSAGE_TEMPLATE_EPISODE_IMPORTED_UPGRADE = "{series_title} - {series_number}x{episode_number} - {episode_name} has been upgraded from {old_quality} to {new_quality}.".strip()
SONARR_MESSAGE_TEMPLATE_EPISODE_GRABBED = (
"{series_title} - {series_number}x{episode_number} - {episode_name} has been grabbed.".strip()
)
SONARR_MESSAGE_TEMPLATE_EPISODE_DELETED = (
"{series_title} - {series_number}x{episode_number} - {episode_name} has been deleted.".strip()
)
SONARR_MESSAGE_TEMPLATE_EPISODE_DELETED_UPGRADE = "{series_title} - {series_number}x{episode_number} - {episode_name} has been deleted due to quality upgrade.".strip()
ALL_EVENT_TYPES = [
"Grab",
"EpisodeFileDelete",
"Test",
"Download",
"SeriesDelete",
"Health",
"Rename",
]
@webhook_view("Sonarr", all_event_types=ALL_EVENT_TYPES)
@typed_endpoint
def api_sonarr_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":
topic_name = SONARR_TOPIC_TEMPLATE_TEST
elif event_type == "Health":
topic_name = SONARR_TOPIC_TEMPLATE_HEALTH_CHECK.format(
level=payload["level"].tame(check_string)
)
else:
topic_name = SONARR_TOPIC_TEMPLATE.format(
series_title=payload["series"]["title"].tame(check_string)
)
return topic_name
def get_body_for_health_check_event(payload: WildValue) -> str:
return SONARR_MESSAGE_TEMPLATE_HEALTH_CHECK.format(
message=payload["message"].tame(check_string)
)
def get_body_for_episodes_renamed_event(payload: WildValue) -> str:
return SONARR_MESSAGE_TEMPLATE_EPISODES_RENAMED.format(
series_title=payload["series"]["title"].tame(check_string)
)
def get_body_for_series_deleted_event(payload: WildValue) -> str:
return SONARR_MESSAGE_TEMPLATE_SERIES_DELETED.format(
series_title=payload["series"]["title"].tame(check_string)
)
def get_body_for_episode_imported_upgrade_event(payload: WildValue) -> str:
data = {
"series_title": payload["series"]["title"].tame(check_string),
"series_number": payload["episodes"][0]["seasonNumber"].tame(check_int),
"episode_number": payload["episodes"][0]["episodeNumber"].tame(check_int),
"episode_name": payload["episodes"][0]["title"].tame(check_string),
"new_quality": payload["episodeFile"]["quality"].tame(check_string),
"old_quality": payload["deletedFiles"][0]["quality"].tame(check_string),
}
return SONARR_MESSAGE_TEMPLATE_EPISODE_IMPORTED_UPGRADE.format(**data)
def get_body_for_episode_imported_event(payload: WildValue) -> str:
data = {
"series_title": payload["series"]["title"].tame(check_string),
"series_number": payload["episodes"][0]["seasonNumber"].tame(check_int),
"episode_number": payload["episodes"][0]["episodeNumber"].tame(check_int),
"episode_name": payload["episodes"][0]["title"].tame(check_string),
}
return SONARR_MESSAGE_TEMPLATE_EPISODE_IMPORTED.format(**data)
def get_body_for_episode_grabbed_event(payload: WildValue) -> str:
data = {
"series_title": payload["series"]["title"].tame(check_string),
"series_number": payload["episodes"][0]["seasonNumber"].tame(check_int),
"episode_number": payload["episodes"][0]["episodeNumber"].tame(check_int),
"episode_name": payload["episodes"][0]["title"].tame(check_string),
}
return SONARR_MESSAGE_TEMPLATE_EPISODE_GRABBED.format(**data)
def get_body_for_episode_deleted_upgrade_event(payload: WildValue) -> str:
data = {
"series_title": payload["series"]["title"].tame(check_string),
"series_number": payload["episodes"][0]["seasonNumber"].tame(check_int),
"episode_number": payload["episodes"][0]["episodeNumber"].tame(check_int),
"episode_name": payload["episodes"][0]["title"].tame(check_string),
}
return SONARR_MESSAGE_TEMPLATE_EPISODE_DELETED_UPGRADE.format(**data)
def get_body_for_episode_deleted_event(payload: WildValue) -> str:
data = {
"series_title": payload["series"]["title"].tame(check_string),
"series_number": payload["episodes"][0]["seasonNumber"].tame(check_int),
"episode_number": payload["episodes"][0]["episodeNumber"].tame(check_int),
"episode_name": payload["episodes"][0]["title"].tame(check_string),
}
return SONARR_MESSAGE_TEMPLATE_EPISODE_DELETED.format(**data)
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("Sonarr")
elif event_type == "Health":
return get_body_for_health_check_event(payload)
elif event_type == "Rename":
return get_body_for_episodes_renamed_event(payload)
elif event_type == "SeriesDelete":
return get_body_for_series_deleted_event(payload)
elif event_type == "Download" and "isUpgrade" in payload:
if payload["isUpgrade"].tame(check_bool):
return get_body_for_episode_imported_upgrade_event(payload)
else:
return get_body_for_episode_imported_event(payload)
elif event_type == "Grab":
return get_body_for_episode_grabbed_event(payload)
elif event_type == "EpisodeFileDelete" and "deleteReason" in payload:
if payload["deleteReason"].tame(check_string) == "upgrade":
return get_body_for_episode_deleted_upgrade_event(payload)
else:
return get_body_for_episode_deleted_event(payload)
else:
raise UnsupportedWebhookEventTypeError(event_type)