18 Commits

Author SHA1 Message Date
Dusk-92 682e843d0e Align compatibility version with 1.4.7 2026-09-04 21:20:12 +02:00
Dusk-92 71569928b5 Bump TurtleMail version to 1.4.7 2026-09-04 21:18:58 +02:00
Dusk-92 c6e924493b Document TurtleMail 1.4.7 fixes 2026-09-04 21:18:47 +02:00
Dusk-92 1f130dd5b5 Load consolidated TurtleMail fix file 2026-09-04 21:03:23 +02:00
Dusk-92 010b2da105 Remove merged targeted fix file 2026-09-04 21:03:14 +02:00
Dusk-92 1871cd6e1d Merge targeted fixes into TurtleMailFix 2026-09-04 21:03:10 +02:00
Dusk-92 d9a40f18e6 Load targeted 1.4.7 test fixes 2026-09-04 20:43:40 +02:00
Dusk-92 d36dbd5ca5 Add targeted TurtleMail 1.4.7 test fixes 2026-09-04 20:43:31 +02:00
Dusk-92 6f41c63922 Link licensing and provenance documentation 2026-08-31 17:07:09 +02:00
Dusk-92 e19fdc0460 Add code and asset provenance records 2026-08-31 17:06:56 +02:00
Dusk-92 8ed0dbc1ad Add code and asset provenance records 2026-08-31 17:06:51 +02:00
Dusk-92 fec171302c Add code and asset provenance records 2026-08-31 17:06:44 +02:00
Dusk-92 1d2cd4069c Add project licensing and provenance documentation 2026-08-31 17:06:12 +02:00
Dusk-92 2a6599ac9e Add project licensing and provenance documentation 2026-08-31 17:06:08 +02:00
Dusk-92 87e59c24da Add project licensing and provenance documentation 2026-08-31 17:06:05 +02:00
Dusk-92 62d4024b7e Improve README presentation 2026-08-31 17:02:14 +02:00
Dusk-92 b4fc752b4e Release TurtleMail 1.4.6 stable
Promote the tested Turtle WoW compatibility build to stable 1.4.6, update metadata/documentation, and keep behavior unchanged.
2026-08-24 09:49:16 +02:00
Dusk-92 b22361f758 Turtle WoW 1.18.x compatibility fixes (1.4.6-compat.1)
Validated in-game without Lua errors. Adds compatibility and safety fixes for TurtleMail on Turtle WoW 1.18.x / Octo while keeping upstream code largely intact.
2026-08-24 09:44:29 +02:00
10 changed files with 1186 additions and 19 deletions
+41
View File
@@ -0,0 +1,41 @@
# TurtleMail 1.4.5 — Compatibility audit
Target: Turtle WoW 1.18.x / Octo client.
## Fixed in 1.4.6
- **Horizontal mail bar crash:** the upstream code creates `MailHorizontalBarLeft/Right` and then assumes the textures are exported as globals. Some client/UI combinations return the textures without the expected global, causing `attempt to index field 'MailHorizontalBarLeft' (a nil value)`. The compatibility layer guarantees valid texture references before setup and updates.
- **Autocomplete SavedVariables crash:** `TurtleMail_AutoCompleteNames` and its realm/faction table are used without schema validation. The patch initializes/repairs missing or malformed SavedVariables before `ADDON_LOADED`, `PLAYER_LOGIN`, slash commands, and autocomplete.
- **Broken sender-learning hook:** upstream `GetInboxHeaderInfo` hook reads `arg[3]`/`arg[12]` from function inputs even though sender/canReply are return values. The patch captures return values first, so received senders can actually populate autocomplete.
- **Sent money not logged:** upstream checks the nonexistent `state.send_money` field. The patch records sent money when it is not COD and `sent_money > 0`.
- **Inbox bounds/COD safety:** header data is no longer queried/compared unsafely after the inbox index has moved past the last mail; nil COD values are treated as zero.
- **Stale inbox icons:** AH/returned icons are explicitly hidden on empty rows/pages.
- **Fragile package-button regions:** `MAIL_SHOW` no longer assumes regions 1 and 3 always exist.
- **Mailbox frame guards:** bag updates, attachment bookkeeping, inbox locking, and money display tolerate missing/replaced frames instead of immediately throwing Lua errors.
- **Locale-safe COD label:** unexpected `COD_AMOUNT` formatting no longer turns the label into nil and causes string concatenation errors.
- **Malformed log entries:** logging tolerates missing subjects/participant fields and repairs missing log tables.
- **Calendar tooltip:** disabled/empty calendar days no longer concatenate a nil/stale `mails` value.
- **OnUpdate rebinding:** `TurtleMail:init()` binds the original `on_update` function before the compatibility file loads; the patch explicitly rebinds the frame script so the safer handler is actually used.
## Remaining architectural risks upstream
These are intentionally not rewritten in the compatibility layer because changing them would be much more invasive and could alter mail behavior:
1. TurtleMail directly replaces global Blizzard functions during `PLAYER_LOGIN` rather than using a cooperative hook system. This makes load order important when another addon also replaces the mail/bag functions.
2. `sendmail_load()` temporarily/proxy-replaces global `SendMailMailButton` and `SendMailSubjectEditBox`. This is a clever Vanilla-era workaround but can conflict with UI replacements.
3. Several layouts rely on specific Blizzard frame names and region ordering. The compatibility layer protects the confirmed/high-risk cases, but a full mail-frame replacement can still be incompatible.
4. The pfUI skin accesses pfUI internals (`MailFrame.backdrop`, `pfUI_config`, specific widget members). A future pfUI layout change can therefore break the skin even when core TurtleMail remains functional.
5. Inbox polling is frame-count based (`200` OnUpdate ticks), so the interval changes with FPS. It is not a correctness bug but is less predictable than elapsed-time polling.
6. The addon has no automated runtime test harness for Turtle WoW mail APIs; future changes still need in-game validation with representative mail/send scenarios.
## Compatibility context
The upstream repository already had a Turtle WoW 1.18.1 report where `MailFrame` became nil; that report was ultimately traced to MoveAnything. This reinforces that TurtleMail is sensitive to addons that replace or re-parent the mail UI.
## Validation
The compatibility build was tested in-game on Turtle WoW 1.18.x and the reported setup produced no Lua errors after the fixes were applied. Because mailbox behavior can be affected by other UI addons, future changes should still be checked with normal mail, item mail, gold, COD, AH/returned mail, multi-attachment send, autocomplete, logging, and pfUI when applicable.
## Status
**1.4.6 is the current stable release.** The fixes remain isolated in `TurtleMailFix.lua` so upstream code stays easy to compare and future upstream changes remain easier to merge.
+55
View File
@@ -0,0 +1,55 @@
# TurtleMail asset provenance
Audit date: 2026-08-31
No visual asset was modified during this documentation pass.
A software or repository-level notice does not by itself establish ownership or
relicensing authority for every visual asset. This file records what can be
verified without making broader ownership claims.
## Bundled visual assets
| File | Git blob SHA-1 | Immediate upstream | Status |
| --- | --- | --- | --- |
| `TurtleMail-AH.blp` | `29727e7334dac8891f1483e2a07f8e53d0bae3d5` | `sica42/TurtleMail` | exact upstream Git blob match |
| `TurtleMail-RetArrow.blp` | `ac2279ad04168c03e12d693e0056da843d2f3079` | `sica42/TurtleMail` | exact upstream Git blob match |
| `TurtleMail-DownArrow.tga` | `fb6c3b2ca14cc6c4f612711a62184c270e329d3f` | `sica42/TurtleMail` | exact upstream Git blob match |
## Immediate upstream history
`TurtleMail-AH.blp` and `TurtleMail-RetArrow.blp` were introduced in
`sica42/TurtleMail` by commit:
`814a322b87d3262181e78bbdd1f2e6418ac14f8d`
"Add icons for returned & AH mails" — 2025-03-17
`TurtleMail-DownArrow.tga` appears in the upstream logging work including
commit:
`91142ac27c8fabd651b8e8db6c3d30baa31a604e`
"New logging feature" — 2025-03-23
## Provenance status
The immediate source is verified. The ultimate creator/source and any separate
underlying asset license were not established during this audit.
Therefore:
- no claim is made that Dusk-92 created these assets;
- no claim is made that sica42 necessarily owned every underlying visual
element;
- no additional license is asserted for these assets;
- the files are retained with documented provenance.
## Screenshots
The README references externally hosted screenshots. They are not bundled as
repository files.
## Trademark and game-asset boundary
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
marks, artwork, and game assets remain the property of their respective rights
holders.
+74
View File
@@ -0,0 +1,74 @@
# TurtleMail code provenance
Audit date: 2026-08-31
## Known fork chain
1. `shirsig/Mail`
- https://github.com/shirsig/Mail
- original known Vanilla mail addon source
2. `sica42/TurtleMail`
- https://github.com/sica42/TurtleMail
- immediate upstream fork with TurtleMail features and later maintenance
3. `Dusk-92/TurtleMail`
- https://github.com/Dusk-92/TurtleMail
- current maintained compatibility fork
GitHub repository metadata confirms that `sica42/TurtleMail` is a fork of
`shirsig/Mail`, and `Dusk-92/TurtleMail` is a fork of
`sica42/TurtleMail`.
## Immediate-upstream identity
The following current files are byte-identical at Git blob level to
`sica42/TurtleMail`:
| File | Git blob SHA-1 |
| --- | --- |
| `Calendar.lua` | `1a5e918bd4b6bf74f1c4a3372a60e944c5e1dcb8` |
| `TurtleMail.lua` | `009463c946c47f9fbf44c666005c5b95468cba97` |
| `TurtleMail.xml` | `e0ccb3ca6db13f999468a0eef9a91c564c8adae8` |
| `localization.lua` | `2ff7c48bd9c8ee555c7e2e0111bedce86e3e01bc` |
| `localization.de.lua` | `40227ed5306eafd43b97189967459a65487b32a9` |
| `localization.es.lua` | `3f181ba3dc36d8539e8eb6adb91b68f30ec37b75` |
| `localization.fr.lua` | `a72708fd39c212f4478779fa9e73200347495d51` |
| `localization.ru.lua` | `732e204b50c88eb410f48a835da3f5874d48c312` |
The three visual assets are documented separately in
`Docs/ASSET_PROVENANCE.md`.
## Dusk-92-specific maintenance
The current fork adds an isolated compatibility layer in
`TurtleMailFix.lua` and updates addon metadata/documentation around the
1.4.6 maintenance release.
The current `TurtleMail.toc` differs from the immediate upstream version and
includes the compatibility layer.
`AUDIT.md` documents the technical compatibility pass.
## Calendar provenance
`Calendar.lua` is unchanged from the immediate upstream.
Relevant upstream history:
- commit `91142ac27c8fabd651b8e8db6c3d30baa31a604e`
("New logging feature", 2025-03-23)
- commit `2a8cc7204c93dd83b54ea95009fbc6c6075d7573`
("Fix bug in calendar dropdown", 2025-04-02)
This establishes the immediate development history without asserting a license
that is not present in the upstream repository.
## Licensing boundary
Neither `shirsig/Mail` nor `sica42/TurtleMail` exposed an explicit
project-wide LICENSE file during this audit.
A public GitHub repository or fork relationship is not, by itself, proof of a
permissive license.
For that reason, the current fork records provenance rather than assigning an
invented license to inherited code.
+30
View File
@@ -0,0 +1,30 @@
TurtleMail mixed-origin copyright and license notice
This repository contains material from multiple origins.
1. Original modifications and documentation authored by Dusk-92
Copyright (c) 2026 Dusk-92.
All rights reserved unless a specific file states otherwise.
2. Historical and upstream TurtleMail / Mail code
Significant portions of this repository descend from:
- https://github.com/shirsig/Mail
- https://github.com/sica42/TurtleMail
No explicit project-wide license was identified in either upstream
repository during the 2026-08-31 provenance audit.
This file does NOT relicense inherited code and does not grant rights that
the applicable original copyright holders did not grant.
3. Visual assets, trademarks, and game-facing material
These remain subject to their respective rights holders. The presence of a
file in this repository does not by itself establish ownership or a right to
relicense any underlying third-party or game-derived asset.
World of Warcraft, Warcraft, Blizzard Entertainment, Turtle WoW, and associated
names, marks, artwork, and game assets remain the property of their respective
rights holders.
See THIRD_PARTY_NOTICES.md, PROJECT_IDENTITY.md, and Docs/ for provenance and
scope details.
+20
View File
@@ -0,0 +1,20 @@
# License and provenance records
No explicit project-wide LICENSE file was identified in either known upstream
repository during the 2026-08-31 audit:
- https://github.com/shirsig/Mail
- https://github.com/sica42/TurtleMail
For that reason, this directory does not contain an invented upstream license
text.
The repository root `LICENSE` is a **mixed-origin scope notice**. It applies
only to original Dusk-92 material where stated and explicitly does not
relicense inherited code or assets.
See:
- `../THIRD_PARTY_NOTICES.md`
- `../Docs/CODE_PROVENANCE.md`
- `../Docs/ASSET_PROVENANCE.md`
+48
View File
@@ -0,0 +1,48 @@
# TurtleMail project identity
## Canonical project
The canonical repository designated by the current maintainer is:
- https://github.com/Dusk-92/TurtleMail
Mirrors, package caches, downstream forks, repackaged copies, and modified
versions hosted elsewhere are independent unless the canonical project
explicitly states otherwise.
## Independent community project
TurtleMail is an independent community-maintained fork.
It is not affiliated with, sponsored by, approved by, or endorsed by Blizzard
Entertainment, Turtle WoW, OctoWoW-like projects, shirsig, sica42, or any other
referenced project unless explicitly stated by that party.
Compatibility with World of Warcraft 1.12-compatible clients, Turtle WoW-like
environments, Octo-like environments, or pfUI does not imply affiliation,
endorsement, partnership, or ownership.
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
marks, artwork, and game assets remain the property of their respective rights
holders.
## Upstream relationship
The known fork chain is:
- https://github.com/shirsig/Mail
- https://github.com/sica42/TurtleMail
- https://github.com/Dusk-92/TurtleMail
The current fork preserves upstream attribution and maintains its compatibility
changes independently.
Nothing in this repository should be read as a statement that upstream authors
or compatibility-target projects endorse, support, or are responsible for this
fork or its releases.
## Support boundary
Dusk-92 is responsible only for changes and releases published by the canonical
fork. Upstream authors and compatibility-target projects are not responsible
for this fork's modifications or current behavior.
+171 -16
View File
@@ -1,22 +1,177 @@
# TurtleMail - WoW 1.12 addOn
# 📬 TurtleMail — Turtle WoW
An extension to the Blizzard mail interface which
- **Automatically opens mail, very rapidly**
- **Mails multiple items at once, very rapidly**
- **Autocompletes recipient names**
- **Icons to show if mail was returned or is from AH**
- **Shows collected gold from opened mails**
- **Apply COD to 1st or all mails**
- **Logging of all sent and received mails**
A lightweight mailbox enhancement for **World of Warcraft 1.12**, maintained for **Turtle WoW 1.18.x / Octo-like environments**.
**\<Right Click>** on inbox items to loot the gold, loot the item and destroy the letter, in that order, if any.<br/>
**\<Right Click>** or **\<Left Drag>** to add inventory items to the attachments.<br/>
**\<Right Click>** to add inventory items to the trade frame.
TurtleMail improves the Vanilla mail interface with faster mail handling, multiple attachments, recipient autocomplete, mail logging and useful Auction House / returned-mail indicators.
Note that COD is always ignored when opening, both automatically as well as by **\<Right Click>**.
> This fork keeps the original TurtleMail workflow while adding a compatibility and safety layer for modern Turtle WoW 1.12 environments.
Logging is disabled by default. Enable with `/tm log` command
## 📦 Installation
![Alt text](https://i.imgur.com/H0MUmXd.png)
1. Download the addon.
2. Make sure the addon folder is named `TurtleMail`.
3. Copy it to:
![Alt text](https://i.imgur.com/LM7tRcx.png)
`World of Warcraft\Interface\AddOns\TurtleMail`
4. Restart the game.
5. Make sure **TurtleMail** is enabled in the AddOns menu.
No additional addon is required for the core functionality.
## ✨ Features
- Quickly open multiple mails.
- Quickly send multiple item attachments.
- Recipient name autocomplete.
- Automatically learns names from received mail.
- Auction House mail indicators.
- Returned-mail indicators.
- Displays the amount of gold collected while opening mail.
- Apply **Cash on Delivery (COD)** to the first attachment or all outgoing mails.
- Optional logging for sent and received mail.
- Dedicated mail log interface.
- Multiple localization support.
- Vanilla-style mailbox integration.
## 🖱️ Mail controls
### Inbox
**Right-click** an inbox entry to automatically:
1. Loot attached gold.
2. Loot the attached item.
3. Delete the empty letter.
COD mail is intentionally ignored by automatic opening and right-click collection.
### Attachments
- **Right-click** an inventory item to add it to an outgoing mail.
- **Left-drag** an inventory item to add it to the attachment list.
- **Right-click** an inventory item to add it to the trade frame.
## 📝 Mail logging
Mail logging is disabled by default.
Enable or disable it with:
`/tm log`
When enabled, TurtleMail records sent and received mail information in its dedicated log.
## ⚙️ Commands
| Command | Description |
|---|---|
| `/tm` | Display TurtleMail help |
| `/turtlemail` | Long version of `/tm` |
| `/tm help` | Display TurtleMail commands |
| `/tm log` | Toggle mail logging |
| `/tm clear sent` | Clear the sent-mail log |
| `/tm clear received` | Clear the received-mail log |
| `/tm clear names` | Clear saved autocomplete recipient names |
## 🔧 Turtle WoW compatibility
Version **1.4.7** extends the compatibility and safety layer for Turtle WoW 1.18.x environments.
The compatibility work is isolated in `TurtleMailFix.lua` so the original TurtleMail code remains easier to compare with upstream versions.
### Main fixes
- Fixed `MailHorizontalBarLeft` / `MailHorizontalBarRight` nil crashes.
- Added validation and repair for malformed autocomplete SavedVariables.
- Fixed learning sender names for recipient autocomplete.
- Fixed persistent autocomplete timestamp aging across client restarts.
- Fixed sent-money logging.
- Added safer inbox index and COD handling.
- Prevented stale Auction House and returned-mail icons.
- Added guards for missing or replaced mailbox frames.
- Improved package-frame safety.
- Added safer handling for malformed log entries.
- Fixed calendar tooltip nil/stale-value cases.
- Rebound the safer `OnUpdate` handler correctly after loading the compatibility layer.
- Prevented the send queue from remaining stuck when an attachment becomes unavailable during sending.
The complete technical audit is available in [`AUDIT.md`](AUDIT.md).
## 🛡️ Compatibility notes
TurtleMail directly interacts with several Blizzard mailbox functions and frames.
Most standard Turtle WoW setups should work normally, but addons that heavily replace or re-parent the default mail interface may conflict with TurtleMail.
The compatibility layer keeps the upstream workflow intact where possible and only replaces fragile paths when needed for compatibility or safety.
## 🌍 Localization
TurtleMail includes localization support for:
- English
- French
- German
- Spanish
- Russian
## 🔧 Compatibility
- World of Warcraft 1.12
- Interface version `11200`
- Turtle WoW 1.18.x
- Octo-like Vanilla environments
- Vanilla Blizzard mailbox UI
- Optional pfUI integration
## 📜 Version
Current stable version:
**1.4.7**
Based on upstream **TurtleMail 1.4.5**.
Version 1.4.7 has been validated in-game on Turtle WoW 1.18.x without Lua errors in the tested setup.
## 🖼️ Screenshots
![TurtleMail](https://i.imgur.com/H0MUmXd.png)
![TurtleMail](https://i.imgur.com/LM7tRcx.png)
## 📜 Project identity & licensing
TurtleMail is an independent community-maintained fork with this known source
chain:
- [shirsig/Mail](https://github.com/shirsig/Mail)
- [sica42/TurtleMail](https://github.com/sica42/TurtleMail)
- this maintained fork
No explicit project-wide license was identified in the two known upstream
repositories during the provenance audit, so this fork does **not** claim to
relicense inherited code.
Compatibility with **World of Warcraft**, **Turtle WoW / Octo-like
environments**, or **pfUI** does not imply affiliation, endorsement, or
sponsorship.
For details, see:
- [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
- [PROJECT_IDENTITY.md](PROJECT_IDENTITY.md)
- [Docs/CODE_PROVENANCE.md](Docs/CODE_PROVENANCE.md)
- [Docs/ASSET_PROVENANCE.md](Docs/ASSET_PROVENANCE.md)
- [LICENSES/](LICENSES/)
## 🙏 Credits
Original TurtleMail addon by **shirsig / sica**.
Upstream maintenance by **sica42**.
Turtle WoW compatibility fixes and additional maintenance by **Dusk-92**.
This fork aims to preserve the original TurtleMail experience while improving compatibility and stability on modern Vanilla server environments.
+125
View File
@@ -0,0 +1,125 @@
# TurtleMail third-party notices
Audit date: 2026-08-31
This file records known upstream sources, inherited code, assets, compatibility
references, and unresolved licensing for the Dusk-92 TurtleMail fork.
Existing source comments, Git history, upstream repository metadata, and README
credits remain part of the provenance trail.
## Historical source: shirsig/Mail
The original known source repository is:
- https://github.com/shirsig/Mail
It provides the earlier Vanilla WoW mail addon from which TurtleMail ultimately
descends.
No explicit project-wide LICENSE file was present in that repository during
this audit.
## Immediate upstream: sica42/TurtleMail
The immediate upstream repository is:
- https://github.com/sica42/TurtleMail
That repository is itself a fork of `shirsig/Mail`.
No explicit project-wide LICENSE file was present in the immediate upstream
repository during this audit.
Accordingly, this fork does **not** claim that inherited TurtleMail or Mail code
is MIT, GPL, public domain, or otherwise freely relicensed. The root `LICENSE`
notice is intentionally limited in scope and does not override upstream rights.
## Dusk-92 compatibility layer
The current fork adds and maintains Turtle WoW / Octo-like compatibility and
safety work.
Notably:
- `TurtleMailFix.lua` is maintained in this fork as an isolated compatibility
layer.
- `AUDIT.md` documents the compatibility/stability audit.
- `TurtleMail.toc` and `README.md` include fork-specific maintenance and
compatibility changes.
Historical Git commits remain the authoritative record for individual changes.
## Unchanged immediate-upstream files
At the time of this audit, the following runtime files in the Dusk-92 fork were
byte-identical at Git blob level to `sica42/TurtleMail`:
- `Calendar.lua`
- `TurtleMail.lua`
- `TurtleMail.xml`
- `localization.lua`
- `localization.de.lua`
- `localization.es.lua`
- `localization.fr.lua`
- `localization.ru.lua`
- `TurtleMail-AH.blp`
- `TurtleMail-RetArrow.blp`
- `TurtleMail-DownArrow.tga`
This establishes immediate provenance, not a new license grant.
## Calendar
`Calendar.lua` is inherited unchanged from `sica42/TurtleMail`.
The immediate upstream history shows the calendar as part of the 2025 logging
work, with a later calendar dropdown bug fix by Sica.
No separate license for that file was identified, so it remains documented as
upstream-derived material under unresolved project-wide licensing.
## Visual assets
The three bundled visual assets are tracked in
`Docs/ASSET_PROVENANCE.md`.
Their immediate upstream source is verified, but their ultimate artistic source
or underlying asset license was not established during this audit.
No additional ownership or relicensing claim is made.
## pfUI integration
TurtleMail includes optional compatibility/integration behavior for pfUI.
Compatibility or API integration does not imply that pfUI is bundled, nor does
it imply affiliation or endorsement.
## Turtle WoW / Octo-like compatibility
This fork targets Turtle WoW 1.18.x and Octo-like Vanilla environments.
Compatibility, naming, testing, or behavioral reference does not create an
affiliation, endorsement, partnership, or ownership relationship with those
projects or their maintainers.
## Project identity and trademarks
Canonical maintained fork:
- https://github.com/Dusk-92/TurtleMail
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
marks, artwork, and game assets remain the property of their respective rights
holders.
See `PROJECT_IDENTITY.md`.
## Preservation rule
Do not remove historical attribution, source comments, upstream references, or
provenance records merely because inherited code is later modified.
When replacing or substantially rewriting inherited material, update the
provenance record rather than erasing the historical chain.
+4 -3
View File
@@ -1,10 +1,11 @@
## Interface: 11200
## Title: |cffabd473Turtle|cffffffffMail
## Author: shirsig/sica
## Version: 1.4.5
## Notes: Mailbox enhancement
## Author: shirsig/sica, Dusk-92 (compatibility fixes)
## Version: 1.4.7
## Notes: Mailbox enhancement - Turtle WoW 1.18.x compatibility
## SavedVariables: TurtleMail_AutoCompleteNames
## SavedVariablesPerCharacter: TurtleMail_To TurtleMail_Point TurtleMail_Log
TurtleMail.xml
TurtleMailFix.lua
+618
View File
@@ -0,0 +1,618 @@
-- TurtleMailFix.lua
-- Compatibility/safety layer for TurtleMail 1.4.5 on Turtle WoW 1.18.x / Octo.
-- Loaded after TurtleMail.xml so it can replace fragile handlers before ADDON_LOADED/PLAYER_LOGIN.
if not TurtleMail then return end
local m = TurtleMail
local getn = table.getn
local function pack( ... ) return arg end
m.compat_version = "1.4.7"
local DEFAULT_SENT_FILTERS = {
Money = 1,
COD = 1,
Other = 1,
}
local DEFAULT_RECEIVED_FILTERS = {
Money = 1,
COD = 1,
Other = 1,
Returned = 1,
AH = 1,
AHSold = 1,
AHOutbid = 1,
AHWon = 1,
AHCancelled = 1,
AHExpired = 1,
}
local INBOX_AUCTIONHOUSES = {
[ "Stormwind Auction House" ] = true,
[ "Alliance Auction House" ] = true,
[ "Darnassus Auction House" ] = true,
[ "Undercity Auction House" ] = true,
[ "Thunder Bluff Auction House" ] = true,
[ "Horde Auction House" ] = true,
[ "Blackwater Auction House" ] = true,
}
local function merge_defaults( target, defaults )
for key, value in pairs( defaults ) do
if target[ key ] == nil then
target[ key ] = value
end
end
end
local function ensure_saved_variables()
if type( m.api.TurtleMail_Log ) ~= "table" then
m.api.TurtleMail_Log = {}
end
local log = m.api.TurtleMail_Log
if type( log.Sent ) ~= "table" then log.Sent = {} end
if type( log.Received ) ~= "table" then log.Received = {} end
if type( log.Settings ) ~= "table" then log.Settings = {} end
local settings = log.Settings
if settings.Enabled == nil then settings.Enabled = false end
if type( settings.SentFilters ) ~= "table" then settings.SentFilters = {} end
if type( settings.ReceivedFilters ) ~= "table" then settings.ReceivedFilters = {} end
merge_defaults( settings.SentFilters, DEFAULT_SENT_FILTERS )
merge_defaults( settings.ReceivedFilters, DEFAULT_RECEIVED_FILTERS )
if type( m.api.TurtleMail_AutoCompleteNames ) ~= "table" then
m.api.TurtleMail_AutoCompleteNames = {}
end
end
local function autocomplete_key()
local realm = m.api.GetCVar and m.api.GetCVar( "realmName" ) or "UnknownRealm"
local faction = m.api.UnitFactionGroup and m.api.UnitFactionGroup( "player" ) or "UnknownFaction"
return tostring( realm or "UnknownRealm" ) .. "|" .. tostring( faction or "UnknownFaction" )
end
local function ensure_autocomplete_table()
ensure_saved_variables()
local key = autocomplete_key()
if type( m.api.TurtleMail_AutoCompleteNames[ key ] ) ~= "table" then
m.api.TurtleMail_AutoCompleteNames[ key ] = {}
end
return key, m.api.TurtleMail_AutoCompleteNames[ key ]
end
local function sanitize_autocomplete_table()
local _, names = ensure_autocomplete_table()
for name, last_seen in pairs( names ) do
if type( name ) ~= "string" or type( last_seen ) ~= "number" then
names[ name ] = nil
end
end
end
-- Turtle WoW/Octo can return a texture from CreateTexture without exposing it
-- through the expected global name. The original addon assumes the global exists.
local function ensure_horizontal_bars()
local frame = m.api.SendMailFrame
if not frame or not frame.CreateTexture then return false end
if not m.api.MailHorizontalBarLeft then
m.api.MailHorizontalBarLeft = frame:CreateTexture( nil, "BACKGROUND" )
end
if not m.api.MailHorizontalBarRight then
m.api.MailHorizontalBarRight = frame:CreateTexture( nil, "BACKGROUND" )
end
return m.api.MailHorizontalBarLeft and m.api.MailHorizontalBarRight
end
-- SavedVariables are not guaranteed to have a valid schema. Repair them before
-- the original handlers touch nested fields.
do
local original = m.ADDON_LOADED
function m.ADDON_LOADED()
if arg1 == "TurtleMail" then
ensure_saved_variables()
end
if original then original() end
if arg1 == "TurtleMail" and m.info then
m.info( "Compatibility fixes loaded (|cffeda55f" .. m.compat_version .. "|r)." )
end
end
end
do
local original = m.PLAYER_LOGIN
function m.PLAYER_LOGIN()
ensure_saved_variables()
sanitize_autocomplete_table()
if original then original() end
end
end
do
local original = m.slash_command
function m.slash_command( args )
ensure_saved_variables()
if original then return original( args ) end
end
end
-- Make autocomplete resilient to missing/corrupt SavedVariables.
do
local original = m.add_auto_complete_name
function m.add_auto_complete_name( name )
if type( name ) ~= "string" or name == "" then return end
ensure_autocomplete_table()
if original then return original( name ) end
end
end
if GetSuggestions then
local original = GetSuggestions
function GetSuggestions()
ensure_autocomplete_table()
return original()
end
end
-- Ensure the two horizontal bar textures exist before the original setup/update
-- routines try to address them by global name.
do
local original = m.sendmail_load
function m.sendmail_load()
ensure_horizontal_bars()
if original then return original() end
end
end
if m.hooks and m.hooks.SendMailFrame_Update then
local original = m.hooks.SendMailFrame_Update
m.hook.SendMailFrame_Update = function( ... )
ensure_horizontal_bars()
return original( unpack( arg ) )
end
end
-- Do not assume MailFrame exists during bag events.
function m.BAG_UPDATE()
if m.api.MailFrame and m.api.MailFrame:IsVisible() and m.api.SendMailFrame_Update then
m.api.SendMailFrame_Update()
end
end
-- Safer MAIL_SHOW: package-button regions differ between UI replacements.
function m.MAIL_SHOW()
if not m.api.MailFrame then return end
if m.api.TurtleMail_Point then
m.debug( "Set point" )
m.api.MailFrame:SetPoint( m.api.TurtleMail_Point.point, m.api.TurtleMail_Point.x, m.api.TurtleMail_Point.y )
end
if not m.first_show then
m.first_show = true
local package = m.api.SendMailPackageButton
if package then
if m.pfui_skin_enabled and m.api.pfUI and m.api.pfUI.api and m.api.pfUI.api.StripTextures then
m.api.pfUI.api.StripTextures( package )
end
local regions = { package:GetRegions() }
if regions[ 1 ] and regions[ 1 ].Hide then regions[ 1 ]:Hide() end
if regions[ 3 ] and regions[ 3 ].Hide then regions[ 3 ]:Hide() end
package:Disable()
package:SetScript( "OnReceiveDrag", nil )
package:SetScript( "OnDragStart", nil )
end
end
if m.api.MailFrameTab3 then
if m.log_enabled then
m.api.MailFrameTab3:Show()
else
m.api.MailFrameTab3:Hide()
end
end
m.timer = 0
m.money_received = 0
m.update_money( 0 )
end
-- Check bounds before querying inbox headers and tolerate nil COD values.
function m.on_update()
if not m.api.MailFrame or not m.api.MailFrame:IsVisible() then return end
if m._cursorItem then
m.debug( "on_update: cursorItem" )
m.cursorItem = m._cursorItem
m._cursorItem = nil
end
if m.sendmail_update then
m.debug( "on_update: sendmail" )
m.sendmail_update = nil
if m.sendmail_sending then
m.debug( "m.sendmail_sending" )
m.sendmail_send()
end
end
if m.inbox_update then
m.debug( "on_update: inbox_update" )
m.inbox_update = false
local index = tonumber( m.inbox_index ) or 1
local total = m.api.GetInboxNumItems and (m.api.GetInboxNumItems() or 0) or 0
if index > total then
if (tonumber( m.money_received ) or 0) > 0 then
m.info( string.format( "%s%s.", m.format_money( m.money_received ), L[ "collected" ] ) )
end
m.inbox_abort()
else
local _, _, _, _, _, cod, _, _, _, _, _, _, isGM = m.api.GetInboxHeaderInfo( index )
cod = tonumber( cod ) or 0
if m.inbox_skip or cod > 0 or isGM then
m.inbox_skip = false
m.inbox_index = index + 1
m.inbox_update = true
else
m.inbox_open( index )
end
end
end
if type( m.timer ) ~= "number" then m.timer = 0 end
if m.timer > 0 then
m.timer = m.timer - 1
elseif not m.inbox_opening and m.api.CheckInbox then
m.timer = 200
m.api.CheckInbox()
end
end
-- init() bound the original on_update function before this compatibility file was
-- loaded. Rebind the frame script so the safer implementation above is actually used.
if m.update_frame and m.update_frame.SetScript then
m.update_frame:SetScript( "OnUpdate", m.on_update )
end
-- Hide stale AH/returned icons on rows that no longer contain mail.
function m.MAIL_INBOX_UPDATE()
if m.inbox_opening then
m.inbox_update = true
end
if not m.api.InboxFrame then return end
local page = tonumber( m.api.InboxFrame.pageNum ) or 1
local total = m.api.GetInboxNumItems and (m.api.GetInboxNumItems() or 0) or 0
for i = 1, 7 do
local index = i + (page - 1) * 7
local auction_icon = m.api[ "TurtleMailAuctionIcon" .. i ]
local returned_icon = m.api[ "TurtleMailReturnedArrow" .. i ]
if index <= total then
local _, _, sender, _, _, _, _, _, _, was_returned = m.api.GetInboxHeaderInfo( index )
if auction_icon then
if INBOX_AUCTIONHOUSES[ sender ] then auction_icon:Show() else auction_icon:Hide() end
end
if returned_icon then
if was_returned then returned_icon:Show() else returned_icon:Hide() end
end
else
if auction_icon then auction_icon:Hide() end
if returned_icon then returned_icon:Hide() end
end
end
end
-- Guard frame lookups used while the mailbox is opening/closing.
function m.inbox_update_lock()
for i = 1, 7 do
local icon = m.api[ "MailItem" .. i .. "ButtonIcon" ]
local button = m.api[ "MailItem" .. i .. "Button" ]
if icon and icon.SetDesaturated then
icon:SetDesaturated( m.inbox_opening )
end
if m.inbox_opening and button and button.SetChecked then
button:SetChecked( nil )
end
end
end
-- Locale-safe COD label.
function m.set_cod_text()
local cod_amount = m.api.COD_AMOUNT or "COD:"
local text = string.sub( cod_amount, 1, math.max( 0, string.len( cod_amount ) - 1 ) )
if not m.pfui_skin_enabled then
text = string.match( text, "^(.-)%s+%S+$" ) or text
end
if not m.api.SendMailMoneyText then return end
if m.api.SendMailCODAllButton and m.api.SendMailCODAllButton:GetChecked() then
m.api.SendMailMoneyText:SetText( text .. " " .. L[ "each mail" ] .. ":" )
else
m.api.SendMailMoneyText:SetText( text .. " " .. L[ "1st mail" ] .. ":" )
end
end
function m.update_money( money )
m.money_received = (tonumber( m.money_received ) or 0) + (tonumber( money ) or 0)
if not m.api.MoneyReceived then return end
m.api.MoneyReceived:SetText( L[ "Money received" ] .. ": " .. m.format_money( m.money_received ) )
if m.money_received > 0 then
m.api.MoneyReceived:Show()
else
m.api.MoneyReceived:Hide()
end
end
-- The original hook reads arg[3]/arg[12] from the function INPUT arguments.
-- GetInboxHeaderInfo receives an inbox index; sender/canReply are RETURN values.
-- Capture the return list first so sender autocomplete actually works.
if m.hooks and m.hooks.GetInboxHeaderInfo then
m.hook.GetInboxHeaderInfo = function( ... )
if not m.orig.GetInboxHeaderInfo then return end
local ret = pack( m.orig.GetInboxHeaderInfo( unpack( arg ) ) )
local sender = ret[ 3 ]
local canReply = ret[ 12 ]
if sender and canReply then
m.add_auto_complete_name( sender )
end
return unpack( ret )
end
end
-- Guard MailFrame lookup in attachment bookkeeping.
do
local original = m.sendmail_attached
function m.sendmail_attached( bag, slot )
if not m.api.MailFrame or not m.api.MailFrame:IsVisible() then return false end
if original then return original( bag, slot ) end
return false
end
end
-- Fix sent-money logging and tolerate malformed/system mail subjects.
function m.log.add( log_type, state )
if not m.log_enabled or type( state ) ~= "table" then return end
ensure_saved_variables()
m.debug( "Logging " .. tostring( log_type ) .. " message" )
local data = {
timestamp = time(),
icon = state.icon,
item = state.item,
}
local cod = tonumber( state.cod )
if cod and cod > 0 then data.cod = cod end
if log_type == "Sent" then
data.participant = state.to or ""
data.subject = state.sent_subject or state.subject or ""
local sent_money = tonumber( state.sent_money ) or 0
if not data.cod and sent_money > 0 then
data.money = sent_money
end
else
data.participant = state.from or ""
data.subject = state.subject or ""
data.returned = state.returned
data.gm = state.gm
local received_money = tonumber( state.money ) or 0
if received_money > 0 then data.money = received_money end
local function subject_matches( pattern )
if type( pattern ) ~= "string" or pattern == "" or data.subject == "" then return false end
local needle = string.gsub( pattern, "%%s", "" )
return needle ~= "" and string.find( data.subject, needle, 1, true ) ~= nil
end
if subject_matches( m.api.AUCTION_SOLD_MAIL_SUBJECT ) then
data.ah = "Sold"
elseif subject_matches( m.api.AUCTION_REMOVED_MAIL_SUBJECT ) then
data.ah = "Removed"
elseif subject_matches( m.api.AUCTION_EXPIRED_MAIL_SUBJECT ) then
data.ah = "Expired"
elseif subject_matches( m.api.AUCTION_WON_MAIL_SUBJECT ) then
data.ah = "Won"
elseif subject_matches( m.api.AUCTION_OUTBID_MAIL_SUBJECT ) then
data.ah = "Outbid"
end
end
if type( m.api.TurtleMail_Log[ log_type ] ) ~= "table" then
m.api.TurtleMail_Log[ log_type ] = {}
end
table.insert( m.api.TurtleMail_Log[ log_type ], data )
end
-- Calendar day buttons can retain an old .mails value or have no value at all.
-- Replace only the tooltip handler; calendar selection/layout remains upstream.
if m.calendar and m.calendar.show then
local original_show = m.calendar.show
local function calendar_on_enter( button )
return function()
local enabled = true
if button.IsEnabled then enabled = button:IsEnabled() end
local mails = tonumber( button.mails ) or 0
if enabled and mails > 0 then
m.api.GameTooltip:SetOwner( button, "ANCHOR_RIGHT" )
m.api.GameTooltip:SetText( mails .. " mail" .. (mails > 1 and "s" or ""), 1, 1, 1, 1, true )
m.api.GameTooltip:Show()
else
m.api.GameTooltip:Hide()
end
end
end
m.calendar.show = function( data, current_date, anchor, on_select )
if type( data ) ~= "table" then data = {} end
original_show( data, current_date or time(), anchor, on_select )
for i = 1, 42 do
local button = m.api[ "TurtleMailCalendarDay" .. i .. "Button" ]
if button then
button:SetScript( "OnEnter", calendar_on_enter( button ) )
end
end
end
end
-- Validated targeted fixes: persistent autocomplete timestamps and clean send abort.
-- Kept in a local block so integrating the former test file does not alter scope.
do
local m = TurtleMail
local getn = table.getn
local AUTOCOMPLETE_MAX_AGE = 60 * 60 * 24 * 30
local EPOCH_THRESHOLD = 1000000000
local function autocomplete_key()
local realm = m.api.GetCVar and m.api.GetCVar( "realmName" ) or "UnknownRealm"
local faction = m.api.UnitFactionGroup and m.api.UnitFactionGroup( "player" ) or "UnknownFaction"
return tostring( realm or "UnknownRealm" ) .. "|" .. tostring( faction or "UnknownFaction" )
end
local function ensure_autocomplete_table()
if type( m.api.TurtleMail_AutoCompleteNames ) ~= "table" then
m.api.TurtleMail_AutoCompleteNames = {}
end
local key = autocomplete_key()
if type( m.api.TurtleMail_AutoCompleteNames[ key ] ) ~= "table" then
m.api.TurtleMail_AutoCompleteNames[ key ] = {}
end
return m.api.TurtleMail_AutoCompleteNames[ key ]
end
-- Older TurtleMail builds stored GetTime() values here. GetTime() is session uptime,
-- so those values cannot be compared reliably across game/client restarts. Convert
-- legacy uptime-style values to a safe current timestamp, then expire names using time().
local function migrate_and_prune_autocomplete()
local names = ensure_autocomplete_table()
local now = time()
for name, last_seen in pairs( names ) do
if type( name ) ~= "string" or type( last_seen ) ~= "number" then
names[ name ] = nil
else
if last_seen < EPOCH_THRESHOLD then
last_seen = now
names[ name ] = last_seen
end
if now - last_seen > AUTOCOMPLETE_MAX_AGE then
names[ name ] = nil
end
end
end
end
-- Store persistent timestamps for newly learned recipients.
function m.add_auto_complete_name( name )
if type( name ) ~= "string" or name == "" then return end
local names = ensure_autocomplete_table()
names[ name ] = time()
end
-- Keep the complete existing login flow, but migrate before the legacy cleanup
-- runs and prune again afterwards using persistent timestamps.
local original_player_login = m.PLAYER_LOGIN
function m.PLAYER_LOGIN()
migrate_and_prune_autocomplete()
if original_player_login then original_player_login() end
migrate_and_prune_autocomplete()
end
-- The send flow can remain stuck in sendmail_sending when an attachment
-- disappears or cannot be attached between queueing and the actual SendMail call.
-- Keep the upstream behavior, but explicitly abort the send state on that failure.
function m.sendmail_send()
if type( m.sendmail_state ) ~= "table" or type( m.sendmail_state.attachments ) ~= "table" then
m.sendmail_sending = false
m.sendmail_update = nil
return
end
local item = table.remove( m.sendmail_state.attachments, 1 )
if item then
m.api.ClearCursor()
m.orig.ClickSendMailItemButton()
m.api.ClearCursor()
m.orig.PickupContainerItem( unpack( item ) )
m.orig.ClickSendMailItemButton()
if not m.api.GetSendMailItem() then
m.api.DEFAULT_CHAT_FRAME:AddMessage( "|cffabd473TurtleMail|r: " .. m.api.ERROR_CAPS, 1, 0, 0 )
m.sendmail_sending = false
m.sendmail_update = nil
m.sendmail_state = nil
m.api.ClearCursor()
if m.api.SendMailFrame_Update then
m.api.SendMailFrame_Update()
end
return
end
end
local amount = m.sendmail_state.money
m.sendmail_state.sent_money = m.sendmail_state.money
m.sendmail_state.sent = false
if amount > 0 then
if not m.api.SendMailCODAllButton:GetChecked() then
m.sendmail_state.money = 0
end
if m.sendmail_state.cod then
m.sendmail_state.cod = amount
m.api.SetSendMailCOD( amount )
else
m.sendmail_state.money = 0
m.api.SetSendMailMoney( amount )
end
end
local subject = m.sendmail_state.subject
if subject == "" then
if item then
local item_name, texture, stack_count = m.api.GetSendMailItem()
subject = item_name .. (stack_count > 1 and " (" .. stack_count .. ")" or "")
m.sendmail_state.item = item_name
m.sendmail_state.icon = texture
else
subject = "<" .. m.api.NO_ATTACHMENTS .. ">"
end
elseif m.sendmail_state.numMessages > 1 then
subject = subject .. string.format( " [%d/%d]", m.sendmail_state.numMessages - getn( m.sendmail_state.attachments ),
m.sendmail_state.numMessages )
end
m.sendmail_state.sent_subject = subject
m.debug( "SendMail" )
m.api.SendMail( m.sendmail_state.to, subject, m.sendmail_state.body )
if getn( m.sendmail_state.attachments ) == 0 then
m.sendmail_sending = false
end
end
end