{"id":29654775,"url":"https://github.com/learnitall/watchinator","last_synced_at":"2025-07-22T07:35:41.348Z","repository":{"id":161215159,"uuid":"634027110","full_name":"learnitall/watchinator","owner":"learnitall","description":"Subscribe to things on GitHub using custom filters.","archived":false,"fork":false,"pushed_at":"2024-02-29T19:54:19.000Z","size":144,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-20T03:00:21.080Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/learnitall.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-04-28T21:04:53.000Z","updated_at":"2023-05-03T20:09:39.000Z","dependencies_parsed_at":"2024-02-29T20:26:54.162Z","dependency_job_id":"61cc0de6-a89b-4fc4-a2c2-b63e2d1aae3e","html_url":"https://github.com/learnitall/watchinator","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/learnitall/watchinator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learnitall%2Fwatchinator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learnitall%2Fwatchinator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learnitall%2Fwatchinator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learnitall%2Fwatchinator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/learnitall","download_url":"https://codeload.github.com/learnitall/watchinator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/learnitall%2Fwatchinator/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266448762,"owners_count":23930300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-22T07:35:40.458Z","updated_at":"2025-07-22T07:35:41.335Z","avatar_url":"https://github.com/learnitall.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# watchinator\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/learnitall/watchinator.svg)](https://pkg.go.dev/github.com/learnitall/watchinator)\n[![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/learnitall/watchinator/blob/main/LICENSE)\n[![Report Card](https://goreportcard.com/badge/github.com/learnitall/watchinator)](https://goreportcard.com/report/github.com/learnitall/watchinator)\n[![Release](https://img.shields.io/github/release/learnitall/watchinator.svg)](https://github.com/learnitall/watchinator/releases/latest)\n\nSubscribe to things on GitHub using custom filters.\n\n## Quick Start\n\n1. Create a GitHub PAT (classic) to allow watchinator to view and subscribe to things on your behalf.\n   The following scopes are needed:\n     1. For public repositories: **public_repo**\n     2. For private repositories: **repo**\n     3. **notifications**\n\n2. Create a new config:\n\n```yaml\nuser: your_username\npat: your_pat\ninterval: 30m\nwatches:\n- name: \"bug issues on cilium/cilium\"\n  repos:\n    - name: \"cilium\"\n      owner: \"cilium\"\n  states:\n    - OPEN\n  requiredLabels:\n    - \"bug\"\n  searchLabels:\n    - \"bug\"\n  actions:\n    subscribe:\n      enabled: true\n```\n\n3. Validate the config:\n\n```\n$ go run . validate-config --config ./config.yaml\n$ go run . whoami --config ./config.yaml\n```\n\n4. Run:\n\n```\n$ go run . watch --config ./config.yaml\n```\n\nTo build and run using nix:\n\n```\n$ nix run '.#watchinator'\n```\n\nAs a container:\n\n```\n$ nix build '.#watchinator-image'\n$ cat result | docker load\n```\n\n## Overview\n\nWatchinator was created to handle subscribing to select issues on massive repositories with a lot of collaborators.\nGitHub only has support for automatically subscribing to all issues on a repository, which can create notification fatigue and\ngenerate noise in an inbox.\n\nWatchinator will continually poll GitHub for new issues and filter them based on pre-configured criteria. If an issue matches\nthe pre-configured criteria, then watchinator will perform pre-configured actions, such as subscribing the user to the issue or\nsending an email to the user containing the issue.\n\n## Configuration\n\nWatchinator is configured using a yaml-configurtion file. Documentation is provided in the associated go-struct. When watchinator\nstarts up, validation is performed on the configuration to ensure things are set correctly.\n\nEach config file is composed of multiple 'Watches'. A 'Watch' describes a set of match criteria which will be applied to\nthe watch's configured repositories, and a set of actions which will be performed on a match.\n\n### Example\n\nIn this example, we'll configure a watch that uses each of the available criteria. We first need to start by populating our\ntop-level config fields:\n\n* **User**: Your GitHub username. This is required to ensure authentication is working properly.\n* **PAT**: A PAT which can be used to authenticate to GitHub. See the quick start section above for information on the required\n           scopes.\n* **Interval**: The amount of time in-between querying GitHub for issues to subscribe to. This field is parsed using\n                the function [time.ParseDuration](https://pkg.go.dev/time#ParseDuration).\n\nOverall this will look like:\n\n```yaml\nuser: your_username\npat: your_pat\ninterval: some_interval\n```\n\nTo ensure that authentication is working properly, you can use the 'whoami' subcommand:\n\n```\n$ go run . --config ./config.yaml whoami\ntime=2023-05-02T11:52:24.507-06:00 level=INFO source=/home/rydrew/Documents/watchinator/cmd/whoami.go:47 msg=\"Hello learnitall!\"\n```\n\nNow let's create our 'Watch'. Let's start by giving our watch a name. Each 'Watch' is uniquely identified by this field and it\nis referenced in debug logs (toggle-able with `--verbose`).\n\n```yaml\nwatches: \n- name: \"example\" \n```\n\nNext we can specify our repository. In this case, we can use \"learnitall/watchinator\"\n\n```yaml\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n```\n\nLet's add our first filter by using the 'states' option to only target issues that are currently open:\n\n```yaml\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n  states:\n    - OPEN\n```\n\nWatchinator uses the [shurcooL/githubv4](https://github.com/shurcooL/githubv4) library in the background to perform\nqueries against GitHub's GraphQL API. As such, the valid options for the state field can be found\n[here](https://docs.github.com/en/graphql/reference/enums#issuestate).\n\nNow that we have a watch with at least one filter, we can use watchinator's 'list' subcommand to test it out:\n\n```\n$ go run . list example --config ./config.yaml\n```\n\n\u003e The output here is in JSON, so feel free to pipe it to jq.\n\nLet's move on to trying out more filters to target issue #1\n[This is a Test Issue](https://github.com/learnitall/watchinator/issues/1).\n\nFirst, labels. This issue has two labels `bug` and `wontfix`. We can configure watchinator to target issues that have BOTH\nof these labels by adding a 'requiredLabels' field to our config:\n\n```yaml\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n  states:\n    - OPEN\n  requiredLabels:\n    - \"bug\"\n    - \"wontfix\"\n```\n\nFor repositories with lots of issues, we can tone down the number of requests made to GitHub by also configuring the\n'searchLabels' field. This will ask GitHub to only return issues to the watchinator that contain at least one of these\nlabels:\n\n```yaml\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n  states:\n    - OPEN\n  requiredLabels:\n    - \"bug\"\n    - \"wontfix\"\n  searchLabels:\n    - \"bug\"\n```\n\n\u003e Some filters are available within the GitHub API, such as states and search labels. Others are implemented in the\n\u003e watchinator itself.\n\nLet's further target issue #1 by setting a regex filter on the issue's body. The issue body is:\n\n```\nCan the watchinator subscribe to this issue?\n```\n\nso we can make a crude regex filter to target it:\n\n```\n^Can the watchinator subscribe to this issue\\?$\n```\n\nand add it into our config:\n\n```yaml\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n  states:\n    - OPEN\n  requiredLabels:\n    - \"bug\"\n    - \"wontfix\"\n  searchLabels:\n    - \"bug\"\n  bodyRegex:\n    - \"^Can the watchinator subscribe to this issue\\\\?$\"\n```\n\nThis is a pretty specific set of criteria, but we can be incredibly specific by adding a metadata selector. After each\nissue is pulled from GitHub, it is converted into a set of selectable metadata. The 'selectors' field follows the\nKubernetes label selector syntax (defined [here](https://pkg.go.dev/k8s.io/apimachinery@v0.27.1/pkg/labels#Parse)). To find\nselectable metadata, look for the function `GitHubItemAsLabelSet`.\n\nIn this case, we can select the issue's number:\n\n```yaml\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n  states:\n    - OPEN\n  requiredLabels:\n    - \"bug\"\n    - \"wontfix\"\n  searchLabels:\n    - \"bug\"\n  bodyRegex:\n    - \"^Can the watchinator subscribe to this issue\\\\?$\"\n  selectors\n    - \"number==1\"\n```\n\nRunning a 'list' should return only a single issue:\n\n```json\n[\n  {\n    \"author\": {\n      \"login\": \"learnitall\"\n    },\n    \"body\": \"Can the watchinator subscribe to this issue?\",\n    \"labels\": [\n      \"bug\",\n      \"wontfix\"\n    ],\n    \"number\": 1,\n    \"state\": \"OPEN\",\n    \"title\": \"This is a Test Issue\",\n    \"Subscription\": \"IGNORED\",\n    \"type\": \"issue\",\n    \"repo\": {\n      \"owner\": \"learnitall\",\n      \"name\": \"watchinator\"\n    },\n    \"id\": \"I_kwDOJcp8Zs5krjB2\"\n  }\n]\n```\n\nFinally, let's tell watchinator what to do when it finds a new issue. In this example, let's ask watchinator to ensure we are\nsubscribed to matched issues:\n\n```yaml\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n  states:\n    - OPEN\n  requiredLabels:\n    - \"bug\"\n    - \"wontfix\"\n  searchLabels:\n    - \"bug\"\n  bodyRegex:\n    - \"^Can the watchinator subscribe to this issue\\\\?$\"\n  selectors\n    - \"number==1\"\n  actions:\n    subscribe:\n      enabled: true\n```\n\nThis action will be performed when watchinator is kicked off using the 'watch' subcommand, which will continually poll GitHub\nfor issues using the interval we configured earlier.\n\nAnother action we can have the watchinator take is send us an email for each matched issue we aren't subscribed to. This can be\nuseful, as GitHub will not notify us if we subscribe to a new issue, only when a subscribed issue has an update. To\nconfigure the email action, first let's teach watchinator how to send an email from a gmail account.\n\nTo do this, we first need to generate a 16 character [app-specific password](https://support.google.com/accounts/answer/185833?hl=en)\nfor the account. Once we have this, we can add it into our config, along with information on how to connect to gmail's\nsmtp service:\n\n```yaml\nuser: your_username\npat: your_pat\ninterval: some_interval\nemail:\n  username: \"myemail@gmail.com\"\n  password: \"1234567890123456\"\n  host: \"smtp.gmail.com\"\n  port: 587\n```\n\nAfter this, we just need to add the email action into our watch configuration:\n\n```\nwatches:\n- name: \"example\"\n  repos:\n    - name: \"watchinator\"\n      owner: \"learnitall\"\n  states:\n    - OPEN\n  requiredLabels:\n    - \"bug\"\n    - \"wontfix\"\n  searchLabels:\n    - \"bug\"\n  bodyRegex:\n    - \"^Can the watchinator subscribe to this issue\\\\?$\"\n  selectors\n    - \"number==1\"\n  actions:\n    subscribe:\n      enabled: true\n    email:\n      enabled: true\n      sendTo: \"myotheremail@gmail.com\"\n```\n\nThe next time watchinator is started using the 'watch' subcommand, when it finds an issue on GitHub that we haven't subscribed to yet\nthat matches our criteria it will send an email to \"myotheremail@gmail.com\" from \"myemail@gmail.com\" containing the issue formatted\nas JSON.\n\n## Installation\n\n\u003e To be filled out\n\n## Development\n\n### Building\n\n[Nix](https://nixos.org/) is used as the main build system. The provided [flake](https://nixos.wiki/wiki/Flakes) can be used\nto build both the watchinator binary and container image.\n\n#### Build-Time Variables\n\nThe following variables should be set a build-time using ldflags:\n\n* `github.com/learnitall/watchinator/cmd.commit`: The current git commit SHA at the time of build, or 'dirty' if the git\n  working tree is dirty.\n* `github.com/learnitall/watchinator/cmd.tag`: The git tag the build is based off of. This should be the same as the\n  binary's associated container image tag.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearnitall%2Fwatchinator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flearnitall%2Fwatchinator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flearnitall%2Fwatchinator/lists"}