Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions .github/ISSUE_TEMPLATE/app-bug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Android app problem
description: The app crashes, shows no locations, will not import a zip, or otherwise misbehaves.
title: "App: "
labels: ["bug", "@app"]
body:
- type: markdown
attributes:
value: |
For the **Android app**.

The most useful thing you can attach is the log. The app can write one for you — see the
box near the bottom, which explains where the button is and how to make it appear.

- type: input
id: version
attributes:
label: App version
description: >-
The ⋮ menu in the top right, then **Information**. If you built it yourself, paste
`git rev-parse --short HEAD` instead.
placeholder: "1.0.5"
validations:
required: true

- type: input
id: device
attributes:
label: Phone and Android version
description: >-
Include the ROM if it is not the manufacturer's — GrapheneOS, LineageOS and similar
restrict things this app relies on, and that has explained real reports here.
placeholder: "Pixel 7, Android 15 — or Pixel 7, GrapheneOS"
validations:
required: true

- type: dropdown
id: where
attributes:
label: Where does it go wrong?
options:
- Signing in to my Apple account
- Importing the .zip from the exporter
- The map — no locations, or wrong ones
- Location history
- Settings, or the app's own screens
- It crashes
- Something else
validations:
required: true

- type: textarea
id: what
attributes:
label: What happened
description: >-
What you did, what you expected, and what you got instead. **Screenshots are welcome and
often settle it faster than words** — drag them straight into this box. Anything on screen
that is yours and not the bug's is fine to scribble over.
placeholder: |
I imported the zip, it said 12 tags were added, and the map is empty …
validations:
required: true

- type: input
id: exporter
attributes:
label: Which exporter made the zip?
description: >-
Only if this is about importing or about missing locations. If the ⋮ menu →
**Information** lists what your tags were imported from, copy it from there; otherwise
whichever version you remember downloading, or leave it blank.
**Do not open the zip to find out.** It holds the keys to your tags, newer ones are
password-protected on purpose, and unpacking it to read a version line is not worth the
risk of what you might then attach.
placeholder: "OpenTagViewer.wizard:1.3.0 — or 1.3.0, or whenever I downloaded it"

- type: textarea
id: log
attributes:
label: The log
description: |
There are two ways to get one, and the first is easier if it is offered:

**If the app showed you an error page with an Export logs button**, use that button. It is
the same log, without any of the setting-up below.

**Otherwise** the button is hidden, because most people never need it:

1. **Settings** → turn on **Enable debug data**
2. Back on the map, open the **⋮** menu in the top right → **Export Logs**
3. Choose where to save it, and attach that file here

Either way, make the problem happen *first* and then export — it keeps only the last 500
lines, so anything older has already scrolled away.

**Never attach the export zip, or anything unpacked out of it.** It contains the private
keys to your tags: anybody who has it can locate them, and it cannot be un-posted. Nothing
that gets asked here needs it.

**This is a raw Android log and nothing is removed from it.** Read it before posting and
take out anything you would not put on a public page: your Apple ID, device names, serial
numbers, anything a notification happened to say. Unlike the desktop exporter's Save logs
button, this one does not redact.

Never paste your Apple ID password, a verification code, or a device passcode. No answer
requires them.
render: shell

- type: checkboxes
id: privacy
attributes:
label: Before you post
options:
- label: If I attached a log, I have read it and taken out anything personal I did not want public.
required: true

- type: markdown
attributes:
value: |
This project is not affiliated with Apple or Google. The app talks to Apple's Find My
network using your own account, so a fair number of problems come from Apple changing
something rather than from the app.

A log usually says which.
125 changes: 125 additions & 0 deletions .github/ISSUE_TEMPLATE/exporter-bug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: Export tool problem
description: The desktop exporter — the window or the command line — failed, or produced a zip that will not import.
title: "Exporter: "
labels: ["bug", "@exporter-tool"]
body:
- type: markdown
attributes:
value: |
For the **desktop export tool**: the window you download and run on a computer, or the
same thing driven from a terminal.

Nearly every one of these is answerable from the log, and almost unanswerable without
it. There is a box for it below.

- type: dropdown
id: how
attributes:
label: How are you running it?
description: >-
The download is the window only. If you are using the command line then you are running
from a copy of the source, which is worth knowing on its own — it means a different
Python and different dependencies from everybody else's.
options:
- The window, from a downloaded release
- The window, run from source
- The command line (always from source)
validations:
required: true

- type: input
id: version
attributes:
label: Version, or commit
description: >-
**The first line of the log says this**, so if you are attaching one you can copy it from
there. Otherwise: from a downloaded release, the window's title bar. From source, paste
`git rev-parse --short HEAD` — the version in the source is whatever the last release set,
so on a checkout it does not say where you actually are.
placeholder: "1.3.0 — or 1.3.0 (from source, 4713711)"
validations:
required: true

- type: dropdown
id: os
attributes:
label: Operating system
options:
- Windows
- macOS
- Linux
validations:
required: true

- type: dropdown
id: route
attributes:
label: Which route?
description: >-
Signing in reads your account over the internet and works anywhere. Reading this Mac's
own files needs a Mac that already has your tags in Find My, and works offline.
options:
- Signing in to iCloud
- Reading this Mac's own Find My files
- Adding a self-generated tag from a key file
- Not sure
validations:
required: true

- type: textarea
id: what
attributes:
label: What happened
description: What you did, what you expected, and what you got instead.
placeholder: |
I signed in, chose 12 tags, pressed Export, and it said …
validations:
required: true

- type: textarea
id: log
attributes:
label: The log
description: |
**From the window:** press **Save logs…** at the bottom. It writes a `.txt` and takes the
obvious identifiers out on the way — email address, username, device names, serials.
Attach that file.

**Never attach the export zip itself, or anything unpacked out of it.** It contains the
private keys to your tags: anybody who has it can locate them, and it cannot be un-posted.
Nothing asked here needs it. The saved log is a different file and is the one to send.

**From the command line:** re-run with `-vv` and keep the output.

```
uv run python -m exporter.cli -vv 2> exporter-log.txt
```

`2>` matters — the log goes to standard error, so a plain `>` saves an empty file.

**The command line does not redact anything.** Read `exporter-log.txt` before posting and
take out anything you would not put on a public page: your Apple ID, your computer's user
name inside file paths, device names, serial numbers. Nothing here needs them.

Never paste an Apple ID password, a verification code, or a device passcode. No answer
requires them and none of them belong in a log.
render: shell
validations:
required: true

- type: checkboxes
id: privacy
attributes:
label: Before you post
options:
- label: I have read the log and taken out anything personal I did not want public.
required: true

- type: markdown
attributes:
value: |
This project is not affiliated with Apple. It reads Apple's Find My network using your own
account, so problems usually come from Apple changing something, or from an account shaped
in a way nobody here has seen yet.

A log that says which is worth more than any amount of description.
6 changes: 5 additions & 1 deletion python/exporter/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
suggested_name,
)
from exporter.icloud import Candidate, ExportSourceError
from exporter.version import EXPORT_VIA_CLI, GITHUB_ISSUES_LINK, VERSION
from exporter.version import EXPORT_VIA_CLI, GITHUB_ISSUES_LINK, VERSION, describe_build
from opentagviewer_export import (
ExportError,
KeyFileError,
Expand Down Expand Up @@ -241,6 +241,10 @@ def configure_logging(verbosity: int) -> None:
format="%(levelname)-8s %(name)s: %(message)s",
)

# Same reason as the wizard's copy: a `-vv` log is what a report attaches, and it said
# nothing about which build wrote it.
logging.getLogger("exporter.version").info("OpenTagViewer exporter %s", describe_build())

if verbosity >= 2:
# **The package, not a list of its subpackages.** This used to name `findmy.cloudkit`,
# `findmy.keychain` and `findmy.icloud`, which is every part anybody had needed so far and
Expand Down
71 changes: 70 additions & 1 deletion python/exporter/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

from __future__ import annotations

import subprocess
import sys
from pathlib import Path

VERSION = "1.3.0"

APP_TITLE = f"OpenTagViewer AirTag Exporter {VERSION}"
Expand All @@ -43,11 +47,76 @@
report that says which is worth more than one that says "the exporter".
"""

GITHUB_ISSUES_LINK = "https://github.com/parawanderer/OpenTagViewer/issues/new"
GITHUB_ISSUES_LINK = (
"https://github.com/parawanderer/OpenTagViewer/issues/new?template=exporter-bug.yml"
)
"""
Where to report something neither the user nor this program can fix.

Here rather than in `wizard.py`, which is where it used to live alone, so the CLI can say it too
without importing tkinter - the same reason `VERSION` is here. Two copies of a URL is exactly the
sort of thing that goes stale in one place and is never noticed, because nothing tests a link.

**Straight at the template, not at a blank form.** The form asks for the version, the route and
the log, which is most of what a report of this needs and almost none of what one arrives with -
and its own front matter carries the labels. Labels can also be put in a URL, as
`?labels=bug,%40exporter-tool`, and that is the worse way round: GitHub applies those only for
somebody with permission to label, which a person reporting a bug generally is not.

`exporter-bug.yml` is a filename in `.github/ISSUE_TEMPLATE/`, so renaming that file breaks this
link silently - it degrades to a blank issue rather than an error, which is the failure nobody
notices.
"""


def describe_build() -> str:
"""
What to write in a log so a report says which exporter produced it.

**`VERSION` alone is not the answer on a checkout.** It is a committed literal, so it says
whatever the last release set for every commit after it - a run from `main` two months into a
release cycle reports the old version perfectly confidently. The commit is the only thing that
identifies such a build, and `exporter-bug.yml` has to ask for it by hand precisely because
nothing in the program said it.

Three cases, distinguished because they are genuinely different builds:

- **Frozen**, which is what people download. There is no checkout to ask and `VERSION` is
exactly right, so nothing is spent finding that out.
- **A source tree with git**, where the commit is the truth and the version is a hint.
- **Anything else** - a source zip off a release tag, most likely, where `VERSION` is right
again and there is no commit to name.
"""
if getattr(sys, "frozen", False):
return VERSION

commit = _commit()

return f"{VERSION} (from source, {commit})" if commit else VERSION


def _commit() -> str | None:
"""
The short commit of the checkout this is running from, or None if that is not a thing.

**Nothing here is allowed to matter.** It runs while logging is being set up, before anything
the user asked for has started, so every way it can go wrong returns None: no git on PATH, not
a repository, a git that hangs on a network-mounted directory. A version string is not worth
failing a run over, and it is certainly not worth waiting on.
"""
try:
finished = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
cwd=Path(__file__).resolve().parent,
capture_output=True,
text=True,
timeout=2,
check=False,
)
except (OSError, subprocess.SubprocessError):
return None

if finished.returncode != 0:
return None

return finished.stdout.strip() or None
14 changes: 13 additions & 1 deletion python/exporter/wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@
from findmy.keychain.recovery import RecoveryError

from exporter.icloud import Candidate, ExportSourceError
from exporter.version import APP_TITLE, EXPORT_VIA_WIZARD, GITHUB_ISSUES_LINK, VERSION
from exporter.version import (
APP_TITLE,
EXPORT_VIA_WIZARD,
GITHUB_ISSUES_LINK,
VERSION,
describe_build,
)
from opentagviewer_export import (
ExportError,
KeyFileError,
Expand Down Expand Up @@ -1240,6 +1246,12 @@ def configure_logging() -> None:
force=True,
)

# **Before the caution, so it is the first line of every run.** A log that arrives attached
# to a report used to say nothing about what produced it, so `exporter-bug.yml` had to ask -
# and the answer people give is the version they remember, which on a checkout is whatever the
# last release set. This is the same gap the app has with `Import.via`.
logging.getLogger("exporter.version").info("OpenTagViewer exporter %s", describe_build())

_warn_at_the_top_of_the_log()

# Anything that escapes Tk's callback handling as well, which otherwise vanishes the same way.
Expand Down
Loading
Loading