diff --git a/.prettierignore b/.prettierignore index 81de172073..a9fdd4d0c4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,4 +8,5 @@ pnpm-lock.yaml /web/third /zerver/tests/fixtures /zerver/webhooks/*/doc.md +/zerver/webhooks/github/githubsponsors.md /zerver/webhooks/*/fixtures diff --git a/static/images/integrations/githubsponsors/001.png b/static/images/integrations/githubsponsors/001.png new file mode 100644 index 0000000000..84412ebeda Binary files /dev/null and b/static/images/integrations/githubsponsors/001.png differ diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index 8c6e8e71d8..15968750d4 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -197,6 +197,7 @@ class WebhookIntegration(Integration): stream_name: Optional[str] = None, legacy: bool = False, config_options: Sequence[Tuple[str, str, OptionValidator]] = [], + dir_name: Optional[str] = None, ) -> None: if client_name is None: client_name = self.DEFAULT_CLIENT_NAME.format(name=name.title()) @@ -229,9 +230,12 @@ class WebhookIntegration(Integration): if doc is None: doc = self.DEFAULT_DOC_PATH.format(name=name, ext="md") - self.doc = doc + if dir_name is None: + dir_name = self.name + self.dir_name = dir_name + @property def url_object(self) -> URLResolver: assert self.function is not None @@ -266,7 +270,7 @@ def get_fixture_and_image_paths( integration: Integration, screenshot_config: BaseScreenshotConfig ) -> Tuple[str, str]: if isinstance(integration, WebhookIntegration): - fixture_dir = os.path.join("zerver", "webhooks", integration.name, "fixtures") + fixture_dir = os.path.join("zerver", "webhooks", integration.dir_name, "fixtures") else: fixture_dir = os.path.join("zerver", "integration_fixtures", integration.name) fixture_path = os.path.join(fixture_dir, screenshot_config.fixture_name) @@ -401,6 +405,16 @@ WEBHOOK_INTEGRATIONS: List[WebhookIntegration] = [ function="zerver.webhooks.github.view.api_github_webhook", stream_name="github", ), + WebhookIntegration( + "githubsponsors", + ["financial"], + display_name="GitHub Sponsors", + logo="images/integrations/logos/github.svg", + dir_name="github", + function="zerver.webhooks.github.view.api_github_webhook", + doc="github/githubsponsors.md", + stream_name="github", + ), WebhookIntegration("gitlab", ["version-control"], display_name="GitLab"), WebhookIntegration("gocd", ["continuous-integration"], display_name="GoCD"), WebhookIntegration("gogs", ["version-control"], stream_name="commits"), @@ -739,6 +753,7 @@ DOC_SCREENSHOT_CONFIG: Dict[str, List[BaseScreenshotConfig]] = { "gci": [ScreenshotConfig("task_abandoned_by_student.json")], "gitea": [ScreenshotConfig("pull_request__merged.json")], "github": [ScreenshotConfig("push__1_commit.json")], + "githubsponsors": [ScreenshotConfig("created.json")], "gitlab": [ScreenshotConfig("push_hook__push_local_branch_without_commits.json")], "gocd": [ScreenshotConfig("pipeline.json")], "gogs": [ScreenshotConfig("pull_request__opened.json")], @@ -836,8 +851,9 @@ DOC_SCREENSHOT_CONFIG: Dict[str, List[BaseScreenshotConfig]] = { def get_all_event_types_for_integration(integration: Integration) -> Optional[List[str]]: integration = INTEGRATIONS[integration.name] - if isinstance(integration, WebhookIntegration) and hasattr( - integration.function, "_all_event_types" - ): - return integration.function._all_event_types + if isinstance(integration, WebhookIntegration): + if integration.name == "githubsponsors": + return import_string("zerver.webhooks.github.view.SPONSORS_EVENT_TYPES") + if hasattr(integration.function, "_all_event_types"): + return integration.function._all_event_types return None diff --git a/zerver/webhooks/github/fixtures/cancelled.json b/zerver/webhooks/github/fixtures/cancelled.json new file mode 100644 index 0000000000..f2c0db266b --- /dev/null +++ b/zerver/webhooks/github/fixtures/cancelled.json @@ -0,0 +1,78 @@ +{ + "action": "cancelled", + "sponsorship": { + "node_id": "MDExOlNwb25zb3JzaGlwMQ==", + "created_at": "2019-12-20T19:24:46+00:00", + "sponsorable": { + "login": "octocat", + "id": 5, + "node_id": "MDQ6VXNlcjU=", + "avatar_url": "https://avatars2.githubusercontent.com/u/5?", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "sponsor": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + }, + "privacy_level": "private", + "tier": { + "node_id": "MDEyOlNwb25zb3JzVGllcjE=", + "created_at": "2019-12-20T19:17:05Z", + "description": "foo", + "monthly_price_in_cents": 500, + "monthly_price_in_dollars": 5, + "name": "$5 a month", + "is_one_time": false, + "is_custom_amount": false + } + }, + "sender": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/zerver/webhooks/github/fixtures/created.json b/zerver/webhooks/github/fixtures/created.json new file mode 100644 index 0000000000..d3fb90bb19 --- /dev/null +++ b/zerver/webhooks/github/fixtures/created.json @@ -0,0 +1,78 @@ +{ + "action": "created", + "sponsorship": { + "node_id": "MDExOlNwb25zb3JzaGlwMQ==", + "created_at": "2019-12-20T19:24:46+00:00", + "sponsorable": { + "login": "octocat", + "id": 5, + "node_id": "MDQ6VXNlcjU=", + "avatar_url": "https://avatars2.githubusercontent.com/u/5?", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "sponsor": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + }, + "privacy_level": "public", + "tier": { + "node_id": "MDEyOlNwb25zb3JzVGllcjE=", + "created_at": "2019-12-20T19:17:05Z", + "description": "foo", + "monthly_price_in_cents": 500, + "monthly_price_in_dollars": 5, + "name": "$5 a month", + "is_one_time": false, + "is_custom_amount": false + } + }, + "sender": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/zerver/webhooks/github/fixtures/edited.json b/zerver/webhooks/github/fixtures/edited.json new file mode 100644 index 0000000000..3c0e247305 --- /dev/null +++ b/zerver/webhooks/github/fixtures/edited.json @@ -0,0 +1,83 @@ +{ + "action": "edited", + "sponsorship": { + "node_id": "MDExOlNwb25zb3JzaGlwMQ==", + "created_at": "2019-12-20T19:24:46+00:00", + "sponsorable": { + "login": "octocat", + "id": 5, + "node_id": "MDQ6VXNlcjU=", + "avatar_url": "https://avatars2.githubusercontent.com/u/5?", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "sponsor": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + }, + "privacy_level": "private", + "tier": { + "node_id": "MDEyOlNwb25zb3JzVGllcjE=", + "created_at": "2019-12-20T19:17:05Z", + "description": "foo", + "monthly_price_in_cents": 500, + "monthly_price_in_dollars": 5, + "name": "$5 a month", + "is_one_time": false, + "is_custom_amount": false + } + }, + "changes": { + "privacy_level": { + "from": "public" + } + }, + "sender": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/zerver/webhooks/github/fixtures/pending_cancellation.json b/zerver/webhooks/github/fixtures/pending_cancellation.json new file mode 100644 index 0000000000..b1186e3005 --- /dev/null +++ b/zerver/webhooks/github/fixtures/pending_cancellation.json @@ -0,0 +1,91 @@ +{ + "action": "pending_cancellation", + "sponsorship": { + "node_id": "MDExOlNwb25zb3JzaGlwMQ==", + "created_at": "2019-12-30T19:24:46+00:00", + "sponsorable": { + "login": "octocat", + "id": 5, + "node_id": "MDQ6VXNlcjU=", + "avatar_url": "https://avatars2.githubusercontent.com/u/5?", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "sponsor": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + }, + "privacy_level": "private", + "tier": { + "node_id": "MDEyOlNwb25zb3JzVGllcjE=", + "created_at": "2019-12-30T19:17:05Z", + "description": "foo", + "monthly_price_in_cents": 500, + "monthly_price_in_dollars": 5, + "name": "$5 a month", + "is_one_time": false, + "is_custom_amount": false + } + }, + "changes": { + "tier": { + "from": { + "node_id": "MDEyOlNwb25zb3JzVGllcjI=", + "created_at": "2019-12-30T19:26:26Z", + "description": "bar", + "monthly_price_in_cents": 1000, + "monthly_price_in_dollars": 10, + "name": "$10 a month" + } + } + }, + "effective_date": "2020-01-05T00:00:00+00:00", + "sender": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + } + } diff --git a/zerver/webhooks/github/fixtures/pending_tier_change.json b/zerver/webhooks/github/fixtures/pending_tier_change.json new file mode 100644 index 0000000000..2edfa305d0 --- /dev/null +++ b/zerver/webhooks/github/fixtures/pending_tier_change.json @@ -0,0 +1,91 @@ +{ + "action": "pending_tier_change", + "sponsorship": { + "node_id": "MDExOlNwb25zb3JzaGlwMQ==", + "created_at": "2019-12-20T19:24:46+00:00", + "sponsorable": { + "login": "octocat", + "id": 5, + "node_id": "MDQ6VXNlcjU=", + "avatar_url": "https://avatars2.githubusercontent.com/u/5?", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "sponsor": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + }, + "privacy_level": "private", + "tier": { + "node_id": "MDEyOlNwb25zb3JzVGllcjE=", + "created_at": "2019-12-20T19:17:05Z", + "description": "foo", + "monthly_price_in_cents": 500, + "monthly_price_in_dollars": 5, + "name": "$5 a month", + "is_one_time": false, + "is_custom_amount": false + } + }, + "changes": { + "tier": { + "from": { + "node_id": "MDEyOlNwb25zb3JzVGllcjI=", + "created_at": "2019-12-20T19:26:26Z", + "description": "bar", + "monthly_price_in_cents": 1000, + "monthly_price_in_dollars": 10, + "name": "$10 a month" + } + } + }, + "effective_date": "2019-12-30T00:00:00+00:00", + "sender": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/zerver/webhooks/github/fixtures/tier_changed.json b/zerver/webhooks/github/fixtures/tier_changed.json new file mode 100644 index 0000000000..bb2cff587e --- /dev/null +++ b/zerver/webhooks/github/fixtures/tier_changed.json @@ -0,0 +1,90 @@ +{ + "action": "tier_changed", + "sponsorship": { + "node_id": "MDExOlNwb25zb3JzaGlwMQ==", + "created_at": "2019-12-30T19:24:46+00:00", + "sponsorable": { + "login": "octocat", + "id": 5, + "node_id": "MDQ6VXNlcjU=", + "avatar_url": "https://avatars2.githubusercontent.com/u/5?", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "sponsor": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + }, + "privacy_level": "private", + "tier": { + "node_id": "MDEyOlNwb25zb3JzVGllcjE=", + "created_at": "2019-12-30T19:17:05Z", + "description": "foo", + "monthly_price_in_cents": 500, + "monthly_price_in_dollars": 5, + "name": "$5 a month", + "is_one_time": false, + "is_custom_amount": false + } + }, + "changes": { + "tier": { + "from": { + "node_id": "MDEyOlNwb25zb3JzVGllcjI=", + "created_at": "2019-12-30T19:26:26Z", + "description": "bar", + "monthly_price_in_cents": 1000, + "monthly_price_in_dollars": 10, + "name": "$10 a month" + } + } + }, + "sender": { + "login": "monalisa", + "id": 2, + "node_id": "MDQ6VXNlcjI=", + "avatar_url": "https://avatars2.githubusercontent.com/u/2?", + "gravatar_id": "", + "url": "https://api.github.com/users/monalisa", + "html_url": "https://github.com/monalisa", + "followers_url": "https://api.github.com/users/monalisa/followers", + "following_url": "https://api.github.com/users/monalisa/following{/other_user}", + "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}", + "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions", + "organizations_url": "https://api.github.com/users/monalisa/orgs", + "repos_url": "https://api.github.com/users/monalisa/repos", + "events_url": "https://api.github.com/users/monalisa/events{/privacy}", + "received_events_url": "https://api.github.com/users/monalisa/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/zerver/webhooks/github/githubsponsors.md b/zerver/webhooks/github/githubsponsors.md new file mode 100644 index 0000000000..7816d4f471 --- /dev/null +++ b/zerver/webhooks/github/githubsponsors.md @@ -0,0 +1,22 @@ +Get GitHub Sponsors notifications in Zulip! + +1. {!create-stream.md!} + +1. {!create-an-incoming-webhook.md!} + +1. {!generate-integration-url.md!} + + You can refer to GitHub's documentation for [webhook events](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#sponsorship). + +1. Go to your profile on GitHub and click on **Sponsors dashboard**. + Select **Webhooks**. Click on **Add webhook**. GitHub may prompt + you for your password. + +1. Set **Payload URL** to the URL constructed above. Set **Content type** + to `application/json` and click **Create webhook**. + +{!congrats.md!} + +![](/static/images/integrations/githubsponsors/001.png) + +See also the [GitHub integration](/integrations/doc/github). diff --git a/zerver/webhooks/github/tests.py b/zerver/webhooks/github/tests.py index 28567a85b0..7326fd59a3 100644 --- a/zerver/webhooks/github/tests.py +++ b/zerver/webhooks/github/tests.py @@ -13,6 +13,7 @@ TOPIC_ORGANIZATION = "baxterandthehackers organization" TOPIC_BRANCH = "public-repo / changes" TOPIC_WIKI = "public-repo / wiki pages" TOPIC_DISCUSSION = "testing-gh discussion #20: Lets discuss" +TOPIC_SPONSORS = "sponsors" class GitHubWebhookTest(WebhookTestCase): @@ -591,3 +592,59 @@ A temporary team so that I can get some webhook fixtures! def test_discussion_comment_edited_msg(self) -> None: expected_message = "sbansal1999 edited a [comment](https://github.com/sbansal1999/testing-gh/discussions/20#discussioncomment-6332416) on [discussion #20](https://github.com/sbansal1999/testing-gh/discussions/20):\n\n~~~ quote\nsome random comment edited\n~~~" self.check_webhook("discussion_comment__edited", TOPIC_DISCUSSION, expected_message) + + +class GitHubSponsorsHookTests(WebhookTestCase): + STREAM_NAME = "github" + URL_TEMPLATE = "/api/v1/external/githubsponsors?stream={stream}&api_key={api_key}" + WEBHOOK_DIR_NAME = "github" + + def test_cancelled_message(self) -> None: + expected_message = "monalisa cancelled their $5 a month subscription." + self.check_webhook( + "cancelled", + TOPIC_SPONSORS, + expected_message, + ) + + def test_created_message(self) -> None: + expected_message = "monalisa subscribed for $5 a month." + self.check_webhook( + "created", + TOPIC_SPONSORS, + expected_message, + ) + + def test_pending_cancellation_message(self) -> None: + expected_message = ( + "monalisa's $5 a month subscription will be cancelled on January 05, 2020." + ) + self.check_webhook( + "pending_cancellation", + TOPIC_SPONSORS, + expected_message, + ) + + def test_pending_tier_change_message(self) -> None: + expected_message = "monalisa's subscription will change from $10 a month to $5 a month on December 30, 2019." + self.check_webhook( + "pending_tier_change", + TOPIC_SPONSORS, + expected_message, + ) + + def test_tier_changed_message(self) -> None: + expected_message = "monalisa changed their subscription from $10 a month to $5 a month." + self.check_webhook( + "tier_changed", + TOPIC_SPONSORS, + expected_message, + ) + + def test_edited_message(self) -> None: + expected_message = "monalisa changed who can see their sponsorship from public to private." + self.check_webhook( + "edited", + TOPIC_SPONSORS, + expected_message, + ) diff --git a/zerver/webhooks/github/view.py b/zerver/webhooks/github/view.py index 3459f232f9..6711ce54a0 100644 --- a/zerver/webhooks/github/view.py +++ b/zerver/webhooks/github/view.py @@ -1,4 +1,5 @@ import re +from datetime import datetime, timezone from typing import Callable, Dict, Optional from django.http import HttpRequest, HttpResponse @@ -637,6 +638,82 @@ def get_ping_body(helper: Helper) -> str: return get_setup_webhook_message("GitHub", get_sender_name(payload)) +def get_cancelled_body(helper: Helper) -> str: + payload = helper.payload + template = "{user_name} cancelled their {subscription} subscription." + return template.format( + user_name=get_sender_name(payload), + subscription=get_subscription(payload), + ).rstrip() + + +def get_created_body(helper: Helper) -> str: + payload = helper.payload + template = "{user_name} subscribed for {subscription}." + return template.format( + user_name=get_sender_name(payload), + subscription=get_subscription(payload), + ).rstrip() + + +def get_edited_body(helper: Helper) -> str: + payload = helper.payload + template = "{user_name} changed who can see their sponsorship from {prior_privacy_level} to {privacy_level}." + return template.format( + user_name=get_sender_name(payload), + prior_privacy_level=payload["changes"]["privacy_level"]["from"].tame(check_string), + privacy_level=payload["sponsorship"]["privacy_level"].tame(check_string), + ).rstrip() + + +def get_pending_cancellation_body(helper: Helper) -> str: + payload = helper.payload + template = "{user_name}'s {subscription} subscription will be cancelled on {effective_date}." + return template.format( + user_name=get_sender_name(payload), + subscription=get_subscription(payload), + effective_date=get_effective_date(payload), + ).rstrip() + + +def get_pending_tier_change_body(helper: Helper) -> str: + payload = helper.payload + template = "{user_name}'s subscription will change from {prior_subscription} to {subscription} on {effective_date}." + return template.format( + user_name=get_sender_name(payload), + prior_subscription=get_prior_subscription(payload), + subscription=get_subscription(payload), + effective_date=get_effective_date(payload), + ).rstrip() + + +def get_tier_changed_body(helper: Helper) -> str: + payload = helper.payload + template = "{user_name} changed their subscription from {prior_subscription} to {subscription}." + return template.format( + user_name=get_sender_name(payload), + prior_subscription=get_prior_subscription(payload), + subscription=get_subscription(payload), + ).rstrip() + + +def get_subscription(payload: WildValue) -> str: + return payload["sponsorship"]["tier"]["name"].tame(check_string) + + +def get_effective_date(payload: WildValue) -> str: + effective_date = payload["effective_date"].tame(check_string)[:10] + return ( + datetime.strptime(effective_date, "%Y-%m-%d") + .replace(tzinfo=timezone.utc) + .strftime("%B %d, %Y") + ) + + +def get_prior_subscription(payload: WildValue) -> str: + return payload["changes"]["tier"]["from"]["name"].tame(check_string) + + def get_repository_name(payload: WildValue) -> str: return payload["repository"]["name"].tame(check_string) @@ -728,6 +805,8 @@ def get_topic_based_on_type(payload: WildValue, event: str) -> str: number=payload["discussion"]["number"].tame(check_int), title=payload["discussion"]["title"].tame(check_string), ) + elif event in SPONSORS_EVENT_TYPES: + return "sponsors" return get_repository_name(payload) @@ -770,8 +849,23 @@ EVENT_FUNCTION_MAPPER: Dict[str, Callable[[Helper], str]] = { "team": get_team_body, "team_add": get_add_team_body, "watch": get_watch_body, + "cancelled": get_cancelled_body, + "created": get_created_body, + "edited": get_edited_body, + "pending_cancellation": get_pending_cancellation_body, + "pending_tier_change": get_pending_tier_change_body, + "tier_changed": get_tier_changed_body, } +SPONSORS_EVENT_TYPES = [ + "cancelled", + "created", + "edited", + "pending_cancellation", + "pending_tier_change", + "tier_changed", +] + IGNORED_EVENTS = [ "check_suite", "label",