Skip to content

Commit 2007bf4

Browse files
committed
Replace old universal analytics tracking code with updated GA4 tracking - for basic anonymous usage tracking and exception reporting
1 parent 29fbdab commit 2007bf4

File tree

1 file changed

+95
-69
lines changed

1 file changed

+95
-69
lines changed

src/classes/metrics.py

Lines changed: 95 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@
2727

2828
# idna encoding import required to prevent bug (unknown encoding: idna)
2929
import encodings.idna
30-
import requests
30+
import base64
3131
import platform
32-
import threading
32+
import requests
3333
import time
34-
import urllib.parse
35-
import json
3634

3735
from classes import info
3836
from classes import language
@@ -79,95 +77,123 @@
7977
# Build user-agent
8078
user_agent = "Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36" % os_version
8179

82-
params = {
83-
"cid": s.get("unique_install_id"), # Unique install ID
84-
"v": 1, # Google Measurement API version
85-
"tid": "UA-4381101-5", # Google Analytic Tracking ID
86-
"an": info.PRODUCT_NAME, # App Name
87-
"aip": 1, # Anonymize IP
88-
"aid": "org.openshot.%s" % info.NAME, # App ID
89-
"av": info.VERSION, # App Version
90-
"ul": language.get_current_locale().replace('_', '-').lower(), # Current Locale
91-
"ua": user_agent, # Custom User Agent (for OS, Processor, and OS version)
92-
"cd1": openshot.OPENSHOT_VERSION_FULL, # Dimension 1: libopenshot version
93-
"cd2": platform.python_version(), # Dimension 2: python version (i.e. 3.4.3)
94-
"cd3": QT_VERSION_STR, # Dimension 3: qt5 version (i.e. 5.2.1)
95-
"cd4": PYQT_VERSION_STR, # Dimension 4: pyqt5 version (i.e. 5.2.1)
96-
"cd5": os_distro
97-
}
80+
GA4_ENDPOINT = "https://www.google-analytics.com/mp/collect"
81+
GA4_MID = "G-YGF4YGWPE2"
82+
GA4_MPS = "aG9qVmV0MllURXVPMlVoQm11RWRDQQ=="
83+
GA4_SESSION_ID = int(time.time())
84+
METRIC_QUEUE_MAX = 100
9885

9986
# Queue for metrics (incase things are disabled... just queue it up
10087
# incase the user enables metrics later
10188
metric_queue = []
10289

10390

91+
def _d(value):
92+
"""Decode a stored string value."""
93+
return base64.b64decode(value.encode("ascii")).decode("ascii")
94+
95+
96+
def _base_event_params():
97+
"""Shared GA4 params for all events (used as custom dimensions)."""
98+
return {
99+
"engagement_time_msec": 1,
100+
"session_id": GA4_SESSION_ID,
101+
"os_app_name": info.PRODUCT_NAME,
102+
"os_app_version": info.VERSION,
103+
"os_libopenshot_version": openshot.OPENSHOT_VERSION_FULL,
104+
"os_python_version": platform.python_version(),
105+
"os_qt_version": QT_VERSION_STR,
106+
"os_pyqt_version": PYQT_VERSION_STR,
107+
"os_locale": language.get_current_locale().replace('_', '-').lower(),
108+
"os_platform": os_version,
109+
"os_distro": os_distro,
110+
}
111+
112+
113+
def _build_event(name, extra_params=None):
114+
"""Build a GA4 event payload."""
115+
event_params = _base_event_params()
116+
if extra_params:
117+
event_params.update(extra_params)
118+
return {
119+
"name": name,
120+
"params": event_params,
121+
}
122+
123+
104124
def track_metric_screen(screen_name):
105125
"""Track a GUI screen being shown"""
106-
metric_params = json.loads(json.dumps(params))
107-
metric_params["t"] = "screenview"
108-
metric_params["cd"] = screen_name
109-
metric_params["cid"] = s.get("unique_install_id")
110-
QTimer.singleShot(0, partial(send_metric, metric_params))
126+
event = _build_event("screen_view", {"screen_name": screen_name})
127+
QTimer.singleShot(0, partial(send_metric, event))
111128

112129

113130
def track_metric_event(event_action, event_label, event_category="General", event_value=0):
114-
"""Track a GUI screen being shown"""
115-
metric_params = json.loads(json.dumps(params))
116-
metric_params["t"] = "event"
117-
metric_params["ec"] = event_category
118-
metric_params["ea"] = event_action
119-
metric_params["el"] = event_label
120-
metric_params["ev"] = event_value
121-
metric_params["cid"] = s.get("unique_install_id")
122-
QTimer.singleShot(0, partial(send_metric, metric_params))
131+
"""Track a UI event."""
132+
event = _build_event(
133+
"ui_event",
134+
{
135+
"event_category": event_category,
136+
"event_action": event_action,
137+
"event_label": event_label,
138+
"value": event_value,
139+
},
140+
)
141+
QTimer.singleShot(0, partial(send_metric, event))
123142

124143

125144
def track_metric_error(error_name, is_fatal=False):
126145
"""Track an error has occurred"""
127-
metric_params = json.loads(json.dumps(params))
128-
metric_params["t"] = "exception"
129-
metric_params["exd"] = error_name
130-
metric_params["exf"] = 0
131-
if is_fatal:
132-
metric_params["exf"] = 1
133-
QTimer.singleShot(0, partial(send_metric, metric_params))
146+
event = _build_event(
147+
"exception",
148+
{
149+
"description": error_name,
150+
"fatal": 1 if is_fatal else 0,
151+
},
152+
)
153+
QTimer.singleShot(0, partial(send_metric, event))
134154

135155

136156
def track_metric_session(is_start=True):
137-
"""Track a GUI screen being shown"""
138-
metric_params = json.loads(json.dumps(params))
139-
metric_params["t"] = "screenview"
140-
metric_params["sc"] = "start"
141-
metric_params["cd"] = "launch-app"
142-
metric_params["cid"] = s.get("unique_install_id")
143-
if not is_start:
144-
metric_params["sc"] = "end"
145-
metric_params["cd"] = "close-app"
146-
QTimer.singleShot(0, partial(send_metric, metric_params))
157+
"""Track application session start/end."""
158+
event_name = "session_start" if is_start else "session_end"
159+
event = _build_event(event_name, {"screen_name": "launch-app" if is_start else "close-app"})
160+
QTimer.singleShot(0, partial(send_metric, event))
147161

148162

149-
def send_metric(params):
150-
"""Send anonymous metric over HTTP for tracking"""
163+
def send_metric(event):
164+
"""Send anonymous GA4 Measurement Protocol events over HTTP."""
151165

152166
# Add to queue and *maybe* send if the user allows it
153-
metric_queue.append(params)
167+
metric_queue.append(event)
168+
if len(metric_queue) > METRIC_QUEUE_MAX:
169+
metric_queue.pop(0)
154170

155171
# Check if the user wants to send metrics and errors
156172
if s.get("send_metrics"):
173+
if not GA4_MID or not GA4_MPS:
174+
log.warning("GA4 metrics disabled: missing GA4 configuration")
175+
return
157176

158-
for metric_params in metric_queue:
159-
url_params = urllib.parse.urlencode(metric_params)
160-
url = "https://www.google-analytics.com/collect?%s" % url_params
161-
162-
# Send metric HTTP data
163-
try:
164-
r = requests.get(url, headers={"user-agent": user_agent})
165-
except Exception:
166-
log.warning("Failed to track metric", exc_info=1)
167-
168-
# Wait a moment, so we don't spam the requests
169-
time.sleep(0.25)
170-
171-
# All metrics have been sent (or attempted to send)
172-
# Clear the queue
177+
events_to_send = list(metric_queue)
173178
metric_queue.clear()
179+
180+
url = "%s?measurement_id=%s&api_secret=%s" % (
181+
GA4_ENDPOINT,
182+
GA4_MID,
183+
_d(GA4_MPS),
184+
)
185+
payload = {
186+
"client_id": s.get("unique_install_id"),
187+
"events": events_to_send,
188+
}
189+
190+
# Send metric HTTP data
191+
try:
192+
r = requests.post(url, json=payload, headers={"user-agent": user_agent}, timeout=5)
193+
if r.status_code >= 300:
194+
log.warning("Failed to track metric (status=%s)", r.status_code)
195+
except Exception:
196+
log.warning("Failed to track metric", exc_info=1)
197+
198+
# Wait a moment, so we don't spam the requests
199+
time.sleep(0.25)

0 commit comments

Comments
 (0)