[READ-ONLY] Mirror of https://github.com/FoxxMD/tautulli-notification-digest. Consolidate Tautuilli Notification agent events for discord
digest discord newsletter notification plex tautulli tautulli-api webhook
0

Configure Feed

Select the types of activity you want to include in your feed.

Add more readme instructions and example config

FoxxMD (Aug 14, 2023, 4:44 PM EDT) 23c5783b 98f3b076

+327 -6
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2023 Matt Foxx 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+248 -4
README.md
··· 1 1 # tautuilli-notification-digest 2 2 3 - Create "digest" notifications for discord using [Tautulli's](https://tautulli.com/) discord [notification agent](https://github.com/Tautulli/Tautulli/wiki/Notification-Agents-Guide#discord). 3 + tautuilli-notification-digest (TND) creates "digest" (timed summary) notifications of **Media Added** events for discord using [Tautulli's](https://tautulli.com/) discord [notification agent](https://github.com/Tautulli/Tautulli/wiki/Notification-Agents-Guide#discord). 4 4 5 - ## What Does It Do? 5 + # What Does It Do? 6 6 7 7 Tautulli already provides an email "newsletter" that compiles triggered events (media added) from Plex and then sends it as one email at a set time. 8 8 9 - This same functionality **does not exist** for notifications. This [functionality](tautulli newsletter discord) is often requested for [discord](https://www.reddit.com/r/PleX/comments/tzadtv/guide_for_setting_up_discord_andor_tautulli/) and there are even some [existing guides](https://forums.serverbuilds.net/t/guide-timed-summary-plex-to-discord-notifications-with-tautulli/4505) but they are quite involved. 9 + This same functionality **does not exist** for notifications. This functionality is often requested for [discord](https://www.reddit.com/r/PleX/comments/tzadtv/guide_for_setting_up_discord_andor_tautulli/) and there are even some [existing guides](https://forums.serverbuilds.net/t/guide-timed-summary-plex-to-discord-notifications-with-tautulli/4505) but they are quite involved. 10 10 11 11 **This app provides a drop-in solution for timed notifications that compile all of your "Recently Added" Tautulli events into one notification.** 12 12 13 - <img src="/docs/assets/thumbnail.png" 13 + <img src="/docs/assets/thumbnail-multiple.png" 14 14 alt="thumbnail view" width="400"> 15 + 16 + # Install 17 + 18 + ## Docker 19 + 20 + * [Dockerhub](https://hub.docker.com/r/foxxmd/tautulli-notification-digest) - `docker.io/foxxmd/tautulli-notification-digest` 21 + * [GHCR](https://github.com/foxxmd/context-mod/pkgs/container/tautulli-notification-digest) - `ghcr.io/foxxmd/tautulli-notification-digest` 22 + 23 + ## Local (Node) 24 + 25 + ```shell 26 + clone https://github.com/FoxxMD/tautulli-notification-digest.git . 27 + cd tautulli-notification-digest 28 + yarn install 29 + ``` 30 + 31 + # Setup 32 + 33 + ## Tautulli 34 + 35 + You must first configure a [Tautulli discord notification agent.](https://github.com/Tautulli/Tautulli/wiki/Notification-Agents-Guide#discord) 36 + 37 + In your agent ensure these settings are used: 38 + 39 + * Configuration 40 + * Discord Webhook Url 41 + * **TND location + Slug (see below)** 42 + * ✅ Include Rich Metadata Info 43 + * ✅ Include Summary 44 + * ✅ Include Link to Plex Web (optional) 45 + * Triggers 46 + * ✅ Recently Added 47 + 48 + If you already have an existing agent you will re-use the Webhook url for TND so save it! 49 + 50 + Your **Discord Webhook URL** for Tautuilli will be the **location of the TND server + your configured slug.** 51 + 52 + Example: 53 + 54 + * TND and Tautulli on the same computer, using ENV setup => `http://localhost:8078/my-digest` 55 + * TND on a different machine (192.168.0.180) than Tautulli, using ENV setup => `http://192.168.0.180:8078/my-digest` 56 + * TND on a different machine (192.168.0.180) than Tautulli, using config setup with slug `test` => `http://192.168.0.180:8078/test` 57 + 58 + ## Configuration 59 + 60 + TND can be run using either [environmental variables](#env) or a [configuration file.](#file) If you want to customize how TND behaves you will need to use a configuration file. 61 + 62 + ### ENV 63 + 64 + If you are fine with all default settings then TND can be configured using only environmental variables. 65 + 66 + | Environmental Variable | Required? | Example | Description | 67 + |------------------------|-----------|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 68 + | DISCORD_WEBHOOK | Yes | `https://discord.com/api/webhooks/606873513` | The [discord webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) for a channel you want to post to. This would be the same hook you used when [setting up Tautulli notifications.](https://github.com/Tautulli/Tautulli/wiki/Notification-Agents-Guide#discord) | 69 + | CRON | Yes | `0 17 * * *` | A [cron expression](https://crontab.guru) for when TND should send notifications. The example sends a notification once a day at 5:00pm local time. | 70 + | FORMAT | No | | Always use the specified embed format instead of collapsing for space. Options are: poster, thumbnail, text, list | 71 + | PORT | No | 8078 | The port the web server will listen for incoming events from Tautuilli | 72 + #### Docker 73 + 74 + Add [environmental variables](https://docs.docker.com/engine/reference/commandline/run/#env) using the `-e flag` to your run command: 75 + 76 + ```shell 77 + docker run -e DISCORD_WEBHOOK="https://discord.com/api/webhooks/606873513" -e CRON="0 17 * * *" ... ghcr.io/foxxmd/tautuilli-notification-digest 78 + ``` 79 + 80 + #### Local 81 + 82 + Export your variables before the run command or use a [.env file](https://www.codementor.io/@parthibakumarmurugesan/what-is-env-how-to-set-up-and-run-a-env-file-in-node-1pnyxw9yxj) 83 + 84 + ```shell 85 + DISCORD_WEBHOOK="https://discord.com/api/webhooks/606873513" -e CRON="0 17 * * *" yarn run start 86 + ``` 87 + 88 + ### File 89 + 90 + An example config file with all options [can be found here.](/config/config.yaml.example) 91 + 92 + #### Docker 93 + 94 + Mount a directory containing your `config.yaml` file to the `/config` directory in the container: 95 + 96 + ```shell 97 + docker run -v /host/path/folder:/config ... ghcr.io/foxxmd/tautuilli-notification-digest 98 + ``` 99 + 100 + #### Local 101 + 102 + Add your `config.yaml` to a new folder named `data` in the project directory. 103 + 104 + # Run 105 + 106 + Make sure you have: 107 + 108 + * Setup a [Tautuilli discord notification agent](#tautulli) 109 + * Are using **either** [environmental variables](#env) or a [file configuration](#file) 110 + 111 + The below run examples will send one summary digest notification a day to discord at 5pm local time. 112 + 113 + ## Docker 114 + 115 + **Note:** When using a `bridge` network (docker default) make sure you map the correct server port (8078 by default) from the container to host. 116 + 117 + ```shell 118 + docker -e DISCORD_WEBHOOK="https://discord.com/api/webhooks/606873513" -e CRON="0 17 * * *" -p 8078:8078 ghcr.io/foxxmd/tautuilli-notification-digest 119 + ``` 120 + 121 + ## Local 122 + 123 + ```shell 124 + DISCORD_WEBHOOK="https://discord.com/api/webhooks/606873513" -e CRON="0 17 * * *" yarn run start 125 + ``` 126 + 127 + # Options 128 + 129 + This section will cover major options for the [file configuration](#file) but is not exhaustive. For a more complete example reference the [**example configuration**](/config/config.yaml.example) or [**the entire config schema can be explored here.**](https://json-schema.app/view/%23?url=https%3A%2F%2Fraw.githubusercontent.com%2FFoxxMD%2Ftautulli-notification-digest%2Fmain%2Fsrc%2Fcommon%2Fschema%2Foperator.json) Use the `Example (YAML)` tab to see examples of individual objects. 130 + 131 + ## Embed Formats 132 + 133 + TND can display notifications in several formats with increasing levels of compactness: 134 + 135 + ### Poster 136 + 137 + The default and same way Tautuilli displays notifications. 138 + 139 + <img src="/docs/assets/poster-multiple.png" 140 + alt="thumbnail view" width="400"> 141 + 142 + ### Thumbnail 143 + 144 + Display post image as a thumbnail 145 + 146 + <img src="/docs/assets/thumbnail-multiple.png" 147 + alt="thumbnail view" width="400"> 148 + 149 + ### Text 150 + 151 + Does not include any images but still includes linkable title, summary, and other links. 152 + 153 + <img src="/docs/assets/text-multiple.png" 154 + alt="thumbnail view" width="400"> 155 + 156 + ### List 157 + 158 + Only includes title of the notification (media name) 159 + 160 + <img src="/docs/assets/list-multiple.png" 161 + alt="thumbnail view" width="400"> 162 + 163 + ### Embed Format Collapse 164 + 165 + You can configure what [format](#embed-formats) TND will render notifications in based on the number of notifications that have been collected since the last time it posted a digest. 166 + 167 + These thresholds are configured in the config file like this: 168 + 169 + ```yaml 170 + digests: 171 + - cron: '...' 172 + discord: 173 + webhook: '...' 174 + options: 175 + list: false 176 + text: false 177 + thumbnail: 2 178 + poster: 0 179 + ``` 180 + 181 + TND determines which format to use by checking for format type threshold by increasing compactness: 182 + 183 + List -> Text -> Thumbnail -> Poster 184 + 185 + Example: 186 + 187 + * 10 pending notifications 188 + * `list: 15 | text: 9 | thumbnail: 8 | poster: 1` 189 + * `text` will be chosen because 10 > 9 190 + * Note: TND will not consider any "larger" format sizes if a smaller format (`text`) condition is true, even if larger formats have higher thresholds 191 + 192 + **Note:** Setting a format to `false` disables it from ever being used. 193 + 194 + #### Default Collapse Settings 195 + 196 + The default thresholds are: 197 + 198 + ``` 199 + list: false 200 + text: false 201 + thumbnail: 2 202 + poster: 0 203 + ``` 204 + 205 + IE 206 + 207 + * If any pending notifications exist, `poster` is used 208 + * If 2 or more pending notifications, `thumbnail` is used 209 + 210 + #### Overflow 211 + 212 + Discord only allows [10 embeds per message](https://discordjs.guide/popular-topics/embeds.html#embed-limits). If your digest would render more than 9 embeds then TND will automatically create an **overflow** embed that renders as a [list](#list). 213 + 214 + <img src="/docs/assets/overflow.png" 215 + alt="thumbnail view" width="400"> 216 + 217 + The number of notifications shown in the overflow list is truncated after `overflowTruncate` number of notifications and a remaining count is shown. 218 + 219 + ```yaml 220 + digests: 221 + - cron: '...' 222 + discord: 223 + webhook: '...' 224 + options: 225 + overflowTruncate: 20 # defaults to 20 226 + ``` 227 + 228 + ## Deduplication Behavior 229 + 230 + TND can prevent duplicate, or already seen notifications, from being rendered in a digest. This is useful if Tautulli sends identical notifications after an initial notification which can occur for things like: 231 + 232 + * New metadata is added (summary or iamges are fetched from plex agent) 233 + * Adding multiple episodes to a season at different time periods 234 + * A newer/better quality version of an existing movie/episode is added (replaced) in Plex 235 + 236 + TND detects duplicates by comparing the **title of the message sent by Tautuilli.** EX: `Season 1 of Show x was added to Plex` or `New Movie (2023) was added to Plex`. 237 + 238 + Behavior options are: 239 + 240 + * `'all'` - Prevent **any** notification that has been processed by TND before from being future digests 241 + * `'sessions'` (default) - Prevent duplicate notifications within one session IE only unique pending notifications -- if a duplicate is detected it is used instead of the original b/c we assume metadata may have changed 242 + * `'never'` - Always allow duplicates 243 + 244 + # API 245 + 246 + ## Tautuilli Webhook 247 + 248 + Any `POST` request to a URL NOT starting with `/api` will be treated as a Tautulli Discord Notification request. 249 + 250 + ## Run Pending Notifications 251 + 252 + Using the **slug** defined for your digest (ENV defaults to `my-digest`) make a `POST` request to 253 + 254 + ``` 255 + http://SERVER_IP:8078/api/SLUG 256 + ``` 257 + 258 + and TND will immediately process any pending notifications
+28
config/config.yaml.example
··· 1 1 logging: 2 + # default logging level ot console and file 2 3 level: 'debug' 4 + # specify a different log level for file logging 3 5 file: 'warn' 6 + digests: 7 + # URL ending Tautulli uses for Webhook URL 8 + # Use this if setting up multiple digests 9 + # EX slug: 'my-digest' => Tautulli Webhook Url 'http://localhost:8078/my-digest' 10 + - slug: 'my-digest' 11 + # run at 5pm 12 + # https://crontab.guru 13 + cron: '0 17,0 * * *' 14 + # cron can also be a list of expression to set up different jobs for 15 + # cron: 16 + # - '0 17,0 * * *' 17 + # # every hour 18 + # - '0 */1 * * *' 19 + 20 + # don't allow duplicate notifications for any pending notifications 21 + dedup: 'session' 22 + discord: 23 + webhook: 'https://discord.com/api/webhooks/6068898134543123/sLjg0poVgpzWUpXf3_Cn05wL6i7FvoPL6ihUxrl8oeOelPrO' 24 + options: 25 + # number of items in the overflow embed before truncating and displaying remainder as a count 26 + overflowTruncate: 15 27 + # See README -> Embed Formats 28 + list: 20 29 + text: 15 30 + thumbnail: 6 31 + poster: 0
docs/assets/list-multiple.png

This is a binary file and will not be displayed.

docs/assets/overflow.png

This is a binary file and will not be displayed.

docs/assets/poster-multiple.png

This is a binary file and will not be displayed.

docs/assets/text-multiple.png

This is a binary file and will not be displayed.

docs/assets/thumbnail.png docs/assets/thumbnail-multiple.png
+2 -1
src/common/config/ConfigBuilder.ts
··· 123 123 const envWebhook = process.env.DISCORD_WEBHOOK; 124 124 const envCron = process.env.CRON; 125 125 const envEmbedType = process.env.FORMAT; 126 + const envSlug = process.env.SLUG; 126 127 127 128 if (envWebhook !== undefined && envCron !== undefined && mergedConfig.digests.length === 0) { 128 129 const digest: DigestData = { 129 - slug: '', 130 + slug: envSlug ?? 'my-digest', 130 131 cron: envCron, 131 132 discord: { 132 133 webhook: envWebhook
+28 -1
tests/embed.test.ts
··· 70 70 dotenv.config({path: path.join(projectDir, '.env')}); 71 71 }); 72 72 73 - it('Do not collapse when using poster', async function () { 73 + it('Use poster for default config when only one notification', async function () { 74 74 const rand = uniqueRandomNumber(); 75 75 const [event, image] = await getDummyRequest({id: rand()}); 76 76 const images = image !== undefined ? [image] : []; ··· 79 79 80 80 assert.exists((messages[0].embeds[0] as EmbedBuilder).data.image); 81 81 assert.notExists((messages[0].embeds[0] as EmbedBuilder).data.thumbnail); 82 + sendMessages(defaultTestDigest(), messages); 83 + return; 84 + }); 85 + 86 + it('Collapses to poster', async function (done) { 87 + const rand = uniqueRandomNumber(); 88 + const events: TautulliRequestData[] = []; 89 + const images: TautulliRequestFileData[] = []; 90 + for(let i = 0; i < 3; i++) { 91 + const [event, image] = await getDummyRequest({id: rand()}); 92 + events.push(event); 93 + if(image !== undefined) { 94 + images.push(image); 95 + } 96 + } 97 + 98 + const digest = defaultTestDigest(); 99 + digest.discord.options = { 100 + poster: 0, 101 + thumbnail: false, 102 + text: false, 103 + list: false 104 + } 105 + 106 + const [messages, eventCount] = buildMessages(digest, events, images); 107 + 108 + assert.exists((messages[0].embeds[0] as EmbedBuilder).data.image); 82 109 sendMessages(defaultTestDigest(), messages); 83 110 return; 84 111 });