2.1.0 - new features #6

Merged
pyaniz merged 7 commits from staging into main 2026-08-16 12:51:41 -05:00
Owner
No description provided.
Closes the request in issue #3 to look up rules by card text.

The literal request does not work on its own: the Comprehensive Rules contain
zero occurrences of "blood moon". The CR is a rules document and almost never
names individual cards, so searching rule text for a card name correctly
returns nothing, and shipping only that would have looked broken on the very
example the issue gives.

Phrase search: a quoted query now matches its words adjacently, and an unquoted
one requires every word before loosening to a ranked any-word match. Previously
every query was tokenised and OR'd, so "blood moon" returned each rule
mentioning blood or moon - five results, none of them relevant.

Card-aware lookup: when a search finds nothing and the query names a card, the
bot answers the question actually being asked. A card's type line and oracle
text are matched against the glossary, 657 of whose 739 entries cite the rule
that defines them, which turns Blood Moon into nonbasic land, enchantment and
mountain. Keyword abilities lead where a card has them, so Questing Beast
reports vigilance, haste and deathtouch rather than incidental terms.

Cited rules that are section headings carry no text of their own, so those fall
back to the glossary definition instead of rendering as the bare word
"Supertypes".

Searching for a card name is left literal rather than preferring the card: rule
107.10 genuinely contains "a lightning bolt for instant", and preferring cards
would hijack searches for words that are both a card and a rules concept, such
as counterspell.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds scripts/forgejo.py for reading and writing this repository's issues:
list, show, create, comment, close, reopen and labels. Stdlib only, so it runs
with any Python without the bot's dependencies, and it derives the host and
repository from the git remote so a rename or move needs no edit.

Reading needs no credentials because the repository is public. Writing reads a
token from $FORGEJO_TOKEN or ~/.config/forgejo/token, never from an argument,
so it cannot be captured in shell history or a terminal transcript. Without one
it explains how to create it rather than raising.

Output is forced to UTF-8. Issue text is full of arrows, em-dashes and curly
quotes, and a Windows console defaults to cp1252, which raised UnicodeEncodeError
when reading a comment back.

Ignore .forgejo/token and similar paths. A stray token file was sitting
untracked in the working tree of what is a public repository, one 'git add -A'
away from being published.

scripts/ and docs/ are excluded from the image; neither belongs in the runtime.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs: record the shelved spoiler-alert design
All checks were successful
Build and Publish Containers / build-and-push (push) Successful in 13s
0515c134bf
Design for spoiler alerts, agreed and then deliberately shelved before any code
was written. Kept because the Scryfall research underneath it is slow to redo:
there is no is:spoiler search term, the preview object is empty in practice, and
a single /sets request detects new cards across every upcoming set.

Also records the two decisions already taken - SQLite on a PersistentVolumeClaim
for state, and digesting announcements above three cards with a link to the full
gallery - so they are not relitigated from scratch later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat: pick from ambiguous card names, and make inline search a list
All checks were successful
Build and Publish Containers / build-and-push (push) Successful in 13s
373baa1f43
Closes the request in issue #4.

Ambiguous names in chat: [[bolt]] matches dozens of cards, and the reply was a
plain list of names that had to be retyped. It now offers the closest ten as
buttons, and tapping one posts that card.

Card names reach 141 characters while callback_data is capped at 64 bytes, so a
button cannot carry a name. The candidate list is cached under a short token and
each button carries only that token and an index, which keeps the payload at
about 15 bytes. The cache is in memory and deliberately short-lived: a pick
happens within seconds, and an expired token answers with an explanation rather
than failing silently.

Scryfall returns 404 both for an ambiguous name and for no match at all, so the
two are told apart by the detail text. An ambiguous name offers choices; a name
matching nothing still just says so.

Inline mode: card search returns article results, so every mode now renders as a
readable list rather than a grid of near-identical card images. The art still
arrives with the message as a link preview.

Inline searches also wait INLINE_DEBOUNCE_SECONDS (2 by default) and drop any
query the typist has already superseded, so typing "lightning bolt" costs one
search instead of fourteen. The inline handler is registered non-blocking
because python-telegram-bot processes updates sequentially by default, and the
wait would otherwise stall every other update behind it.

Ranking fix this exposed: Scryfall sorts by name, so searching "lightning bolt"
listed every card containing both words alphabetically and buried the obvious
one. Tolerable in a thumbnail grid, glaring in a list. The name is now looked up
concurrently with the search and floated to the top; queries containing search
operators skip this and behave as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two problems with card search, both visible from the same query.

Non-cards were being returned. Searching "lightning" resolved to a card
literally named "Lightning" - a Jumpstart front card, layout front_card and
set_type memorabilia, not something anyone can play - and it took the top slot.
Printings were worse: Lightning Bolt listed 71 of them, nine being oversized
gold-bordered display pieces rather than real printings.

Tokens, double-faced tokens, emblems, art series, front cards, memorabilia set
types and anything oversized are now excluded from search, from printings, and
from name lookup. Filtering happens before the result limit is applied, so the
limit is not spent on things that get dropped. Name lookup matters most of the
three: it was the path that surfaced the front card for [[lightning]]. When a
name resolves only to a non-card, a real card of that name is preferred, and
failing that it reports not found so the caller offers alternatives instead.

Ordering ignored what was typed. Scryfall sorts alphabetically, so "lightning"
led with Arc Lightning and Ball Lightning while the forty-one cards actually
beginning with the word came later. Results are now ranked exact name, then
starts-with, then a matching face of a split card, then a word match.

Two subtleties in that ranking. It has to run before the limit is applied, or
it only reorders an alphabetically clipped slice - "sol" returned Abby,
Merciless Soldier and never reached Sol Ring at all. And the fuzzy lookup that
rescues typos must not lead when real matches exist, or "goblin" opens with
whatever fuzzy happened to match rather than Goblin Airbrusher; it now applies
only when nothing starts with the query, so "lightnig bolt" still resolves.

Queries containing Scryfall operators skip prefix ranking and behave as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chore: point the local staging tag at 2.1.0
All checks were successful
Build and Publish Containers / build-and-push (push) Successful in 16s
404c010a81
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat: show card art in chat replies, and shorten the inline pause
All checks were successful
Build and Publish Containers / build-and-push (push) Successful in 16s
88e85c40dc
Card art in chat: ((card name)) and the Rulings button replied with text only.
Both now show the card, as a link preview pinned above the message rather than
a photo with a caption. A caption is capped at 1024 characters while a message
gets 4096, and a card's oracle text plus rulings runs close to that - Questing
Beast comes to 982 - so a caption would silently truncate some cards. [[card
name]] already sent a photo and is unchanged.

The preview is built by one helper shared with inline results, which previously
constructed the same options inline.

Inline debounce drops from two seconds to one. It is read from the environment,
so it can be retuned without a rebuild.

Ranking fix this refactor surfaced: searching "lightning bolt" had started
leading with "Emeritus of Conflict // Lightning Bolt". A card whose second face
matched exactly was given the same top rank as one whose whole name matched, and
the alphabetical tiebreak then put the wrong card first - the very complaint
prefix ranking was added to fix. Face matches now rank below whole-name matches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pyaniz merged commit 2f53d77f3b into main 2026-08-16 12:51:41 -05:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pyaniz/mtg-bot!6
No description provided.