Logo

iCal Filter Proxy

iCal proxy with support for user-defined filtering rules

## What's this thing? Do you have iCal feeds with a bunch of stuff you *don't* need? Do you want to modify events generated by your rostering system? iCal Filter Proxy is a simple service for proxying multiple iCal feeds while applying a list of filters to remove or modify events to suit your use case. ### Features * Proxy multiple calendars * Define a list of filters per calendar * Match events using basic text and regex conditions * Remove or modify events as they are proxied ### Built With * Go * [golang-ical](https://github.com/arran4/golang-ical) * [yaml.v3](https://github.com/go-yaml/yaml/tree/v3.0.1) * [DALL-E 2](https://openai.com/index/dall-e-2/) (app icon) ## Setup ### Docker Docker images are published to [Docker Hub](https://hub.docker.com/r/yungwood/ical-filter-proxy). You'll need a config file (see below) mounted into the container at `/app/config.yaml`. For example: ```bash docker run -d \ --name=ical-filter-proxy \ -v config.yaml:/app/config.yaml \ -p 8080:8080/tcp \ --restart unless-stopped \ yungwood/ical-filter-proxy:latest ``` You can also adapt the included [`docker-compose.yaml`](./docker-compose.yaml) example. ### Kubernetes You can deploy iCal Filter Proxy using the included helm chart from [`charts/ical-filter-proxy`](charts/ical-filter-proxy). ### Build from source You can also build the app and container from source. ```bash # clone this repo git clone git@github.com:yungwood/ical-filter-proxy.git cd ical-filter-proxy # build the ical-filter-proxy binary go build . # build container image docker build \ --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ --build-arg REVISION=$(git rev-parse HEAD) \ --build-arg VERSION=$(git rev-parse --short HEAD) \ -t ical-filter-proxy:latest . ``` ## Configuration Calendars and filters are defined in a yaml config file. By default this is `config.yaml` (use the `-config` switch to change this). The configuration must define at least one calendar for ical-filter-proxy to start. Example configuration (with comments): ```yaml calendars: # basic example - name: example # used as slug in URL - e.g. ical-filter-proxy:8080/calendars/example/feed?token=changeme token: "changeme" # token used to pull iCal feed - authentication is disabled when blank feed_url: "https://my-upstream-calendar.url/feed.ics" # URL for the upstream iCal feed filters: # optional - if no filters defined the upstream calendar is proxied as parsed - description: "Remove an event based on a regex" remove: true # events matching this filter will be removed match: # optional - all events will match if no rules defined summary: # match on event summary (title) contains: "deleteme" # must contain 'deleteme' - description: "Remove descriptions from all events" transform: # optional description: # modify event description remove: true # replace with a blank string # example: removing noise from an Office 365 calendar - name: outlook token: "changeme" feed_url: "https://outlook.office365.com/owa/calendar/.../reachcalendar.ics" filters: - description: "Remove canceled events" # canceled events remain with a 'Canceled:' prefix until removed remove: true match: summary: prefix: "Canceled: " - description: "Remove events without descriptions" remove: true match: description: empty: true - description: "Remove public holidays" remove: true match: summary: regex: ".*[Pp]ublic [Hh]oliday.*" # example: cleaning up an OpsGenie feed - name: opsgenie token: "changeme" feed_url: "https://company.app.opsgenie.com/webapi/webcal/getRecentSchedule?webcalToken=..." filters: - description: "Keep oncall schedule events and fix names" match: summary: contains: "schedule: oncall" stop: true # stops processing any more filters transform: summary: replace: "On-Call" # replace the event summary (title) - description: "Remove all other events" remove: true unsafe: false # optional - must be enabled if any calendars do not have a token ``` ### Filters Calendar events are filtered using a similar concept to email filtering. A list of filters is defined for each calendar in the config. Each event parsed from `feed_url` is evaluated against the filters in sequence. * All `match` rules for a filter must be true to match an event * A filter with no `match` rules will *always* match * When a match is found: * if `remove` is `true` the event is discarded * `transform` rules are applied to the event * if `stop` is `true` no more filters are processed * If no match is found the event is retained by default #### Match conditions Each filter can specify match conditions against the following event properties: * `summary` (string value) * `location` (string value) * `description` (string value) These match conditions are available for a string value: * `empty` - if `true`, property must be absent or empty * `contains` - property must contain this value * `prefix` - property must start with this value * `suffix` - property must end with this value * `regex` - property must match the given regular expression (an invalid regex will result in no matches) #### Transformations Transformations can be applied to the following event properties: * `summary` - string value * `location` - string value * `description` - string value The following transformations are available for strings: * `replace` - the property is replace with this value * `remove` - if `true` the property is set to a blank string ## Roadmap to 1.0 There are a few more features I would like to add before I call the project "stable" and release version 1.0. - [ ] Time based event conditions - [ ] Caching - [ ] Support for `ical_url_file` and `token_file` in config (vault secrets) - [ ] Prometheus metrics - [ ] Testing ## Contributing If you have a suggestion that would make this better, please feel free to open an issue or send a pull request. ## License This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details. ## Acknowledgments This project was inspired by [darkphnx/ical-filter-proxy](https://github.com/darkphnx/ical-filter-proxy). I needed more flexibility with filtering rules and the ability to modify event descriptions... plus I wanted an excuse to finally write something in Go.