mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 06:00:33 +02:00
Contact email: withdrawal covers earlier reports; consent is a real true
- A report with follow-up ticked carries this copy's contact id, and the inbox marks its permission withdrawn when a later choice from that copy no longer agrees to follow-up questions at that address. - The one-time prompt never appears in a visit that showed the privacy notice, even if the Frame connects just after it's dismissed. - Saving contact details isn't headset work: it can't hold up switching headsets or be refused after a switch. - Consent flags must be JSON true/false; "false" is no longer consent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2ca0e6924a
commit
01d5c612c0
7 files changed
+160
-20
No files matched your search
+37
-8
@@ -112,13 +112,15 @@ def send(body):
|
||||
"""Send the report to PostHog. Returns {"id", "message"}; raises ReportError."""
|
||||
kind = body.get('kind') if body.get('kind') in KINDS else 'bug'
|
||||
title, text, diag = compose(body)
|
||||
followup = bool(body.get('contactFollowup'))
|
||||
followup = frame_contact.flag(body, 'contactFollowup')
|
||||
contact = str(body.get('contact') or '').strip() if followup else ''
|
||||
if followup and not frame_contact.valid_email(contact):
|
||||
raise ValueError('add your email address for follow-up questions, or untick that box')
|
||||
ref = uuid.uuid4().hex[:8].upper()
|
||||
props = {**frame_telemetry.common(), 'kind': kind, 'title': title, 'message': text,
|
||||
'contact': contact, 'contact_followup': followup, 'diagnostics': diag,
|
||||
# Only with an address: so removing it later (Settings) also takes this permission back.
|
||||
'contact_id': frame_contact.contact_id() if followup else '',
|
||||
'report_id': ref, 'steamos': str(frame.get('build') or '')[:120], 'level': 'report'}
|
||||
# Its own random id: a report can carry contact details, so it isn't linked to this copy's analytics.
|
||||
event = {'event': 'problem_report', 'distinct_id': str(uuid.uuid4()), 'uuid': str(uuid.uuid4()),
|
||||
@@ -143,15 +145,43 @@ class ReportError(RuntimeError):
|
||||
|
||||
def inbox(days=30):
|
||||
"""The maintainer's recent reports from PostHog, newest first (needs the personal API key
|
||||
frame_compat_db.sync uses)."""
|
||||
frame_compat_db.sync uses). Column 10 is whether the person may be asked follow-up
|
||||
questions now: 'withdrawn' when a later choice from the same copy took it back."""
|
||||
import frame_compat_db
|
||||
days = int(days)
|
||||
res = frame_compat_db._posthog_query(
|
||||
"SELECT timestamp, properties.report_id, properties.kind, properties.title, properties.message, "
|
||||
"properties.contact, properties.app_version, properties.os, properties.steamos, properties.diagnostics, "
|
||||
"properties.contact_followup "
|
||||
f"FROM events WHERE event = 'problem_report' AND timestamp > now() - INTERVAL {int(days)} DAY "
|
||||
"properties.contact_followup, properties.contact_id "
|
||||
f"FROM events WHERE event = 'problem_report' AND timestamp > now() - INTERVAL {days} DAY "
|
||||
"ORDER BY timestamp DESC LIMIT 200")
|
||||
return res.get('results') or []
|
||||
rows = [r for r in res.get('results') or [] if isinstance(r, list) and len(r) == 12]
|
||||
if any(r[11] and _yes(r[10]) for r in rows):
|
||||
later = frame_compat_db._posthog_query(
|
||||
"SELECT distinct_id, properties.email, properties.followup, ifNull(toInt(properties.rev), 0), timestamp "
|
||||
f"FROM events WHERE event = 'contact_consent' AND timestamp > now() - INTERVAL {days} DAY LIMIT 100000")
|
||||
mark_withdrawn(rows, later.get('results') or [])
|
||||
return rows
|
||||
|
||||
|
||||
def mark_withdrawn(reports, consents):
|
||||
"""Mark reports whose follow-up permission was taken back: the newest contact choice from
|
||||
the same copy made at or after the report (same second counts, so a withdrawal wins) no
|
||||
longer agrees to follow-up questions at that address."""
|
||||
newest = {}
|
||||
for c in consents:
|
||||
if not isinstance(c, list) or len(c) != 5:
|
||||
continue
|
||||
cid, email, followup, rev, ts = c
|
||||
newest.setdefault(str(cid), []).append(((int(rev or 0), str(ts or '')), str(email or ''), followup))
|
||||
for r in reports:
|
||||
if not (r[11] and _yes(r[10])):
|
||||
continue
|
||||
after = [c for c in newest.get(str(r[11]), []) if c[0][1] >= str(r[0] or '')]
|
||||
if after:
|
||||
_, email, followup = max(after, key=lambda c: c[0])
|
||||
if not (_yes(followup) and email.strip().lower() == str(r[5] or '').strip().lower()):
|
||||
r[10] = 'withdrawn'
|
||||
|
||||
|
||||
def _yes(v):
|
||||
@@ -204,14 +234,13 @@ def main():
|
||||
if cmd != 'inbox':
|
||||
sys.exit(USAGE)
|
||||
for row in inbox(*(args[:1] or [30])):
|
||||
if not isinstance(row, list) or len(row) != 11:
|
||||
continue
|
||||
ts, ref, kind, title, text, contact, version, osname, steamos, diag = (str(v or '') for v in row[:10])
|
||||
# Reports from before contact_followup existed only carried an address given for a reply.
|
||||
reply = contact and (row[10] is None or _yes(row[10]))
|
||||
print(f"== {ts[:16].replace('T', ' ')} {ref} [{kind}] {title}")
|
||||
print(f" {version} on {osname}, SteamOS {steamos or 'unknown'}"
|
||||
f"{', may follow up at ' + contact if reply else ''}")
|
||||
f"{', may follow up at ' + contact if reply else ''}"
|
||||
f"{', follow-up permission since withdrawn' if row[10] == 'withdrawn' else ''}")
|
||||
print(' ' + text.replace('\n', '\n '))
|
||||
if diag:
|
||||
print(' --- diagnostics\n ' + diag.replace('\n', '\n '))
|
||||
|
||||
Reference in new issue
Block a user