From c140b8f5f4f8541af68710f3b828bb9f094b088d Mon Sep 17 00:00:00 2001 From: John Newman Date: Fri, 6 Oct 2023 12:41:23 +0100 Subject: [PATCH 1/3] add generic analytics section for segment compatible api --- mautrix_facebook/__main__.py | 10 +++---- .../{segment_analytics.py => analytics.py} | 27 ++++++++++--------- mautrix_facebook/config.py | 12 +++++++-- mautrix_facebook/example-config.yaml | 14 ++++++---- mautrix_facebook/portal.py | 2 +- mautrix_facebook/web/public.py | 2 +- setup.py | 2 +- 7 files changed, 42 insertions(+), 27 deletions(-) rename mautrix_facebook/{segment_analytics.py => analytics.py} (50%) diff --git a/mautrix_facebook/__main__.py b/mautrix_facebook/__main__.py index fd3bec2f..9be78ad0 100644 --- a/mautrix_facebook/__main__.py +++ b/mautrix_facebook/__main__.py @@ -23,13 +23,13 @@ from mautrix.bridge import Bridge from mautrix.types import RoomID, UserID +from .analytics import init as init_analytics from .config import Config from .db import init as init_db, upgrade_table from .matrix import MatrixHandler from .portal import Portal from .presence import PresenceUpdater from .puppet import Puppet -from .segment_analytics import init as init_segment from .user import User from .util.interval import get_interval from .version import linkified_version, version @@ -65,10 +65,6 @@ def prepare_bridge(self) -> None: super().prepare_bridge() if self.config["appservice.public.enabled"]: secret = self.config["appservice.public.shared_secret"] - segment_key = self.config["appservice.public.segment_key"] - segment_user_id = self.config["appservice.public.segment_user_id"] - if segment_key: - init_segment(segment_key, segment_user_id) self.public_website = PublicBridgeWebsite( loop=self.loop, shared_secret=secret, @@ -78,6 +74,10 @@ def prepare_bridge(self) -> None: ) else: self.public_website = None + + if self.config["analytics.token"]: + analytics = self.config["analytics"] + init_analytics(analytics["host"], analytics["token"], analytics["user_id"]) self.periodic_reconnect_task = None self.periodic_presence_task = None diff --git a/mautrix_facebook/segment_analytics.py b/mautrix_facebook/analytics.py similarity index 50% rename from mautrix_facebook/segment_analytics.py rename to mautrix_facebook/analytics.py index 45ef3aeb..f7e814cc 100644 --- a/mautrix_facebook/segment_analytics.py +++ b/mautrix_facebook/analytics.py @@ -1,8 +1,8 @@ from __future__ import annotations +from urllib.parse import urlunparse import logging -from yarl import URL import aiohttp from mautrix.util import background_task @@ -10,32 +10,35 @@ from . import user as u log = logging.getLogger("mau.web.public.analytics") -segment_url: URL = URL("https://api.segment.io/v1/track") http: aiohttp.ClientSession | None = None -segment_key: str | None = None -segment_user_id: str | None = None +analytics_url: str | None = None +analytics_token: str | None = None +analytics_user_id: str | None = None async def _track(user: u.User, event: str, properties: dict) -> None: await http.post( - segment_url, + analytics_url, json={ - "userId": segment_user_id or user.mxid, + "userId": analytics_user_id or user.mxid, "event": event, "properties": {"bridge": "facebook", **properties}, }, - auth=aiohttp.BasicAuth(login=segment_key, encoding="utf-8"), + auth=aiohttp.BasicAuth(login=analytics_token, encoding="utf-8"), ) log.debug(f"Tracked {event}") def track(user: u.User, event: str, properties: dict | None = None): - if segment_key: + if analytics_token: background_task.create(_track(user, event, properties or {})) -def init(key, user_id: str | None = None): - global segment_key, segment_user_id, http - segment_key = key - segment_user_id = user_id +def init(host: str | None, token: str | None, user_id: str | None = None): + if not host or not token: + return + global analytics_url, analytics_token, analytics_user_id, http + analytics_url = urlunparse(("https", host, "/v1/track", "", "", "")) + analytics_token = token + analytics_user_id = user_id http = aiohttp.ClientSession() diff --git a/mautrix_facebook/config.py b/mautrix_facebook/config.py index 95c15856..2640caf6 100644 --- a/mautrix_facebook/config.py +++ b/mautrix_facebook/config.py @@ -53,8 +53,16 @@ def do_update(self, helper: ConfigUpdateHelper) -> None: else: copy("appservice.public.shared_secret") copy("appservice.public.allow_matrix_login") - copy("appservice.public.segment_key") - copy("appservice.public.segment_user_id") + + copy("analytics.host") + if "appservice.provisioning.segment_key" in self: + base["analytics.token"] = self["appservice.provisioning.segment_key"] + else: + copy("analytics.token") + if "appservice.provisioning.segment_user_id" in self: + base["analytics.user_id"] = self["appservice.provisioning.segment_user_id"] + else: + copy("analytics.user_id") copy("metrics.enabled") copy("metrics.listen_port") diff --git a/mautrix_facebook/example-config.yaml b/mautrix_facebook/example-config.yaml index 27250cdf..eaa6be16 100644 --- a/mautrix_facebook/example-config.yaml +++ b/mautrix_facebook/example-config.yaml @@ -64,11 +64,6 @@ appservice: shared_secret: generate # Allow logging in within Matrix. If false, users can only log in using the web interface. allow_matrix_login: true - # Segment API key to enable analytics tracking for web server endpoints. Set to null to disable. - # Currently the only events are login start, success and fail. - segment_key: null - # Optional user_id to use when sending Segment events. If null, defaults to using mxID. - segment_user_id: null # The unique ID of this appservice. id: facebook @@ -88,6 +83,15 @@ appservice: as_token: "This value is generated when generating the registration" hs_token: "This value is generated when generating the registration" +# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors. +analytics: + # Hostname of the tracking server. The path is hardcoded to /v1/track + host: api.segment.io + # API key to send with tracking requests. Tracking is disabled if this is null. + token: null + # Optional user ID for tracking events. If null, defaults to using Matrix user ID. + user_id: null + # Prometheus telemetry config. Requires prometheus-client to be installed. metrics: enabled: false diff --git a/mautrix_facebook/portal.py b/mautrix_facebook/portal.py index acf932dd..09d37f0e 100644 --- a/mautrix_facebook/portal.py +++ b/mautrix_facebook/portal.py @@ -68,6 +68,7 @@ from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus from . import matrix as m, puppet as p, user as u +from .analytics import track from .config import Config from .db import ( Backfill, @@ -78,7 +79,6 @@ UserPortal as UserPortal, ) from .formatter import facebook_to_matrix, matrix_to_facebook -from .segment_analytics import track if TYPE_CHECKING: from .__main__ import MessengerBridge diff --git a/mautrix_facebook/web/public.py b/mautrix_facebook/web/public.py index f23b12ff..1d3442cf 100644 --- a/mautrix_facebook/web/public.py +++ b/mautrix_facebook/web/public.py @@ -31,7 +31,7 @@ from mautrix.util.signed_token import verify_token from .. import puppet as pu, user as u -from ..segment_analytics import track +from ..analytics import track class InvalidTokenError(Exception): diff --git a/setup.py b/setup.py index be6e95e6..2cd7ac30 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import setuptools -from mautrix_facebook.get_version import git_tag, git_revision, version, linkified_version +from mautrix_facebook.get_version import git_revision, git_tag, linkified_version, version try: long_desc = open("README.md").read() From 884ec695f12b053f537e9d01782bfe35d8b3754f Mon Sep 17 00:00:00 2001 From: the-newman Date: Mon, 9 Oct 2023 15:53:51 +0100 Subject: [PATCH 2/3] use yarl to build analytics url Co-authored-by: Tulir Asokan --- mautrix_facebook/analytics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mautrix_facebook/analytics.py b/mautrix_facebook/analytics.py index f7e814cc..7e53fe8a 100644 --- a/mautrix_facebook/analytics.py +++ b/mautrix_facebook/analytics.py @@ -38,7 +38,7 @@ def init(host: str | None, token: str | None, user_id: str | None = None): if not host or not token: return global analytics_url, analytics_token, analytics_user_id, http - analytics_url = urlunparse(("https", host, "/v1/track", "", "", "")) + analytics_url = URL.build(scheme="https", host=host, path="/v1/track") analytics_token = token analytics_user_id = user_id http = aiohttp.ClientSession() From ad29483651319e8ddc9b6b6805b55faadfca1d74 Mon Sep 17 00:00:00 2001 From: John Newman Date: Mon, 9 Oct 2023 16:18:46 +0100 Subject: [PATCH 3/3] add yarl URL import and type hinting --- mautrix_facebook/analytics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mautrix_facebook/analytics.py b/mautrix_facebook/analytics.py index 7e53fe8a..be917ab0 100644 --- a/mautrix_facebook/analytics.py +++ b/mautrix_facebook/analytics.py @@ -1,8 +1,8 @@ from __future__ import annotations -from urllib.parse import urlunparse import logging +from yarl import URL import aiohttp from mautrix.util import background_task @@ -11,7 +11,7 @@ log = logging.getLogger("mau.web.public.analytics") http: aiohttp.ClientSession | None = None -analytics_url: str | None = None +analytics_url: URL | None = None analytics_token: str | None = None analytics_user_id: str | None = None