๐Ÿ“† Calendarr

May 28, 2025 ยท View on GitHub

Docker Pulls GitHub Release GitHub last commit

๐Ÿ“† Calendarr

A simple Docker container that fetches upcoming airings/releases for TV shows and movies from Sonarr and Radarr calendars and posts them to Discord on a schedule.

Example Discord post

โœจ Features

  • Combines multiple Sonarr and Radarr calendar feeds
  • Groups shows and movies by day of the week
  • Runs on a customizable schedule (daily or weekly)
  • Supports both Discord and Slack notifications
  • Highly customizable configuration

๐Ÿš€ Usage

Images available via either ghcr.io/jordanlambrecht/calendarr:latest or jordyjordyjordy/calendarr:latest

  1. In Discord, right click channel you want to add the script to -> Edit Channel -> Integrations, Webhooks -> New Webhook -> Copy Webhook URL

  2. Create a .env file with your configuration or remove '.example' from .env.example:

DISCORD_WEBHOOK_URL=your_discord_webhook_url
SLACK_WEBHOOK_URL=your_discord_webhook_url
ICS_URL_SONARR_1=your_sonarr_calendar_url
ICS_URL_SONARR_2=your_anime_sonarr_calendar_url
ICS_URL_RADARR_1=your_radarr_calendar_url

...and so on and so on and turtles all the way down

With Docker Run (If you like pain)

docker run -d \
  --name calandarr \
  -e DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/your_webhook" \
  -e CALENDAR_URLS='[{"url":"https://sonarr.example.com/feed/calendar/api.ics","type":"tv"},{"url":"https://radarr.example.com/feed/calendar/api.ics","type":"movie"}]' \
  -e CUSTOM_HEADER="My Media Guide" \
  -e SHOW_DATE_RANGE="true" \
  -e START_WEEK_ON_MONDAY="true" \
  -e RUN_ON_STARTUP="true" \
  jordyjordyjordy/calendarr:latest

To Run Offschedule

  1. Start the container via the compose file with docker compose up -d
  2. Use the command docker exec calandarr python /app/main.py as willy nilly as you wish

๐Ÿ› ๏ธ Configuration

VariableTypeDefaultDescription
ADD_LEADING_ZEROBooleantrueAdd leading zero to single-digit hours (Optional)
CALENDAR_RANGEStringAUTODate range to fetch: DAY, WEEK, AUTO (Optional)
CALENDAR_URLS *String[]JSON array of calendar URLs and types (e.g., [{"url":"http://...","type":"tv"}])
CRON_SCHEDULEStringNoneCustom CRON expression (Overrides SCHEDULE_TYPE, SCHEDULE_DAY, RUN_TIME) (Optional)
CUSTOM_HEADERStringNew ReleasesCustom header text (Optional)
DEBUGBooleanfalseEnable debug logging (Optional)
DEDUPLICATE_EVENTSBooleantrueRemove duplicate events from multiple sources (Optional)
DISCORD_HIDE_MENTION_INSTRUCTIONSBooleanfalseDiscord only Hide the instruction text below the role mention (Optional)
DISCORD_MENTION_ROLE_IDString""Discord only Role ID to mention (Format: 123456789012345678. Numbers only.) (Optional)
DISCORD_WEBHOOK_URL **String""Discord webhook URL
DISPLAY_TIMEBooleantrueDisplay the release time next to events (Optional)
ENABLE_CUSTOM_DISCORD_FOOTERBooleanfalseEnable custom footer for Discord messages (Optional)
ENABLE_CUSTOM_SLACK_FOOTERBooleanfalseEnable custom footer for Slack messages (Optional)
HTTP_TIMEOUTInteger30Timeout in seconds for HTTP requests (Optional)
LOG_BACKUP_COUNTInteger15Number of rotated log files to keep (Optional)
LOG_DIRString/app/logsDirectory to store log files (Optional)
LOG_FILEStringcalendarr.logName of the log file (Optional)
LOG_MAX_SIZE_MBInteger1Maximum size of a single log file in MB before rotation (Optional)
PASSED_EVENT_HANDLINGStringDISPLAYHow to handle past events: DISPLAY, HIDE, STRIKE (Optional)
RUN_ON_STARTUPBooleanfalseRun the job immediately when the container starts (Optional)
RUN_TIMEString09:00Time to run job (HH:MM) (Optional)
SCHEDULE_DAYString1Day of week for weekly schedule (0-6, Sunday-Saturday) (Optional. Default: Monday)
SCHEDULE_TYPEStringWEEKLYDAILY or WEEKLY (Optional)
SHOW_DATE_RANGEBooleantrueShow the date range in the header (Optional)
SHOW_TIMEZONE_IN_SUBHEADERBooleanfalseShow the configured timezone (Optional)
SLACK_WEBHOOK_URL ***String""Slack webhook URL
START_WEEK_ON_MONDAYBooleantrueUse Monday as the start of the week for color rotation (Optional)
TZ *StringUTCTimezone (e.g., America/New_York)
USE_24_HOURBooleantrueUse 24-hour time format (Optional)
USE_DISCORDBooleantrueEnable Discord notifications (Optional)
USE_SLACKBooleanfalseEnable Slack notifications (Optional)

* Required. ** Required if USE_DISCORD is true. *** Required if USE_SLACK is true.

Schedule Configuration

Set when and how often the calendar runs:

  • RUN_TIME: When to run each day (format: HH:MM in 24-hour time, e.g., "09:30")
  • SCHEDULE_TYPE: Either "DAILY" or "WEEKLY"
  • CALENDAR_RANGE: "AUTO", "DAY", or "WEEK" - controls how many days of events to show
    • "AUTO": Uses a day's worth for daily schedules or a week for weekly schedules
    • "DAY": Shows one day of events
    • "WEEK": Shows an entire week of events

You can also use CRON_SCHEDULE for direct cron expressions (overrides all other schedule settings. Don't use this unless you have a good reason and know what you're doing)

โœ๏ธ Custom Footers

You can add custom text to the end of your Discord and Slack announcements using Markdown files.

  1. Enable the Feature: Set ENABLE_CUSTOM_DISCORD_FOOTER: true and/or ENABLE_CUSTOM_SLACK_FOOTER: true in your environment variables.

  2. Create a Volume Mount: Add a volume mount in your docker-compose.yml to map a local directory (e.g., ./custom_footers) to /app/custom_footers inside the container.

    # docker-compose.yml example snippet
    services:
      calendarr:
        # ... other stuff ...
        volumes:
          - ./logs:/app/logs:rw
          - ./custom_footers:/app/custom_footers:rw # Add this line
    
  3. Edit Footer Files:

    • When you first start the container with the volume mount, Calendarr will automatically copy default template files (discord_footer.md and slack_footer.md) into your local ./custom_footers directory (if they don't already exist).
    • Edit these files using standard Markdown (for Discord) or Slack's mrkdwn (for Slack) to customize your footer.

If the footer files are missing or cannot be read, the app will log a warning and omit the footer without failing.

๐Ÿค Obtaining Calendar URLs

Sonarr Calendar Options

Sonarr

  1. Go to Calendar > iCal Link
  2. Leave all three checkboxes blank
  3. Optionally set tags for shows you want to announce
  4. Copy the ical link

Alternatively:

  1. Go to Settings > General
  2. Under "Security" section, look for "API Key"
  3. Copy the API key
  4. Your calendar URL will be: http://your-sonarr-url/feed/v3/calendar/Sonarr.ics?apikey=YOUR_API_KEY

Radarr

  1. Go to Calendar > iCal Link
  2. Leave all three checkboxes blank
  3. Optionally set tags for movies you want to announce
  4. Copy the ical link

Alternatively:

  1. Go to Settings > General
  2. Under "Security" section, look for "API Key"
  3. Copy the API key
  4. Your calendar URL will be: http://your-radarr-url/feed/v3/calendar/Radarr.ics?apikey=YOUR_API_KEY

Slack Webhooks Setup

More info here on how to obtain a slack webhook URL if you get lost.

You can set up the Slack app using the provided manifest file:

  1. Go to https://api.slack.com/apps
  2. Click "Create New App" and select "From an app manifest"
  3. Select your workspace and click "Next"
  4. Copy and paste the contents of the slack-manifest.yaml file from this repository
  5. Click "Next" and then "Create"
  6. Once created, navigate to "Incoming Webhooks" in the sidebar
  7. Toggle "Activate Incoming Webhooks" to On
  8. Click "Add New Webhook to Workspace"
  9. Select the channel where you want to receive updates
  10. Copy the Webhook URL provided and use it as your SLACK_WEBHOOK_URL environment variable

๐ŸŒŸ First Timers

If you're new to Docker, it's fairly easy to get this going. I won't post an in-depth guide- there's Plenty on the internet. The general gist is:

  1. Install Docker Desktop for your platform (Windows, Mac, or Linux)
  2. Create a new folder for your Calendarr setup via Terminal: mkdir calendarr && cd calendarr
  3. Create these two files:
  • A .env file with your configuration (see example above)
  • A docker-compose.yml file with, at a minimum:
---
name: calendarr
services:
  calendarr:
    image: ghcr.io/jordanlambrecht/calendarr:latest
    restart: "unless-stopped"
    container_name: calendarr
    environment:
      USE_DISCORD: "true"
      DISCORD_WEBHOOK_URL: ${DISCORD_WEBHOOK_URL} # Reference the .env.example for more info
      CALENDAR_URLS: >
        [{
          "url":"${ICS_URL_SONARR_1}",
          "type":"tv"
        },
        {
          "url":"${ICS_URL_RADARR_1}",
          "type":"movie"
        }]
      CUSTOM_HEADER: "TV Guide - What's Up This Week"
      TZ: "America/Chicago"  # Change to your timezone
      SCHEDULE_TYPE: "WEEKLY" # Or "DAILY"
      RUN_TIME: "09:00"       # Time to run the job (HH:MM)
    volumes:
      - ./logs:/app/logs:rw
  1. Open a terminal in that folder and run: docker compose up -d
  2. Check if it's working: docker logs calendarr -f

That's it! The container will immediately run once (if RUN_ON_STARTUP is true) and then according to the schedule you've set.

๐Ÿง‘โ€๐Ÿ’ป Contributing

The two biggest things I need help with right now are:

  • Adding friendly timezone names to the TIMEZONE_NAME_MAP in the constants.py file
  • Translations. There is no localization structure implemented yet, but it would be great to get a head start in things like spanish, etc

๐Ÿ›’ ToDo

Features I'd like to maybe implement:

  • Localization
  • More platform integrations
  • Potentially a web ui

๐Ÿšง Development

Roadmap: https://github.com/users/jordanlambrecht/projects/3

If you want to build the container yourself:

git clone https://github.com/jordanlambrecht/calendarr.git
cd calendarr
docker build -t calendarr .

๐Ÿง‘โ€โš–๏ธ License

GNU GENERAL PUBLIC LICENSE