4 Commits

Author SHA1 Message Date
Dusk-92 0bc6523fb2 Promote compatibility layer to 1.4.6 stable 2026-08-24 09:48:43 +02:00
Dusk-92 c166f65316 Update audit for stable 1.4.6 2026-08-24 09:47:57 +02:00
Dusk-92 bb7c47a4d6 Mark 1.4.6 as stable in README 2026-08-24 09:47:39 +02:00
Dusk-92 52e66fcf4b Release TurtleMail 1.4.6 2026-08-24 09:47:22 +02:00
10 changed files with 38 additions and 1067 deletions
+1 -23
View File
@@ -17,28 +17,6 @@ Target: Turtle WoW 1.18.x / Octo client.
- **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.
## Fixed in 1.4.8
- **`MailMailButton` nil crash:** upstream stores the real `SendMailMailButton` in a loose global named `MailMailButton`, then replaces the Blizzard global with a proxy table. If the saved global is missing or another UI addon changes it, `SendMailFrame_CanSend()` and `sendmail_clear()` crash while calling `Enable()` / `Disable()`. The compatibility layer now preserves a strong reference to the real button before the proxy swap, restores it when the mailbox opens, replaces the fragile CanSend hook, and uses a guarded clear routine.
- **Paired subject edit-box hardening:** the same proxy pattern is used for `MailSubjectEditBox`; the real edit box is now preserved and restored alongside the send button to avoid the equivalent follow-up failure.
## Fixed in 1.4.9
- **Missing `Open Mail` button:** upstream creates `TurtleMailOpenMailButton` only once in `inbox_load()` and assumes it will remain parented, positioned, and visible forever. If that creation path is interrupted or another mailbox/UI addon hides or re-parents the button, TurtleMail never recovers it. The compatibility layer now uses an idempotent inbox loader and an `ensure_open_mail_button()` recovery path that recreates the button if needed and restores its parent/anchor/text/click handler/frame level.
## Fixed in 1.4.10
- **First mailbox-open freeze/stutter after the 1.4.9 recovery fix:** 1.4.9 called `ensure_open_mail_button()` on every `MAIL_INBOX_UPDATE`. Initial inbox population can emit a burst of those events, so the button was repeatedly re-parented, re-anchored, re-labeled, resized, and given a new frame level. 1.4.10 removes that work from `MAIL_INBOX_UPDATE` and makes `ensure_open_mail_button()` state-aware, so it only mutates the UI when the button is missing, hidden, moved, re-parented, or otherwise incorrect.
## Fixed in 1.4.11
- **Residual mailbox hitch / excessive garbage generation:** the upstream `GetContainerItemInfo` hook used `pack()` + `unpack()` for every bag query, allocating a temporary table each time. Bag/mailbox refreshes can call this path very frequently and trigger avoidable garbage collection pauses. 1.4.11 replaces the installed hook after login with a fixed-local wrapper that performs no per-call table allocation. The native return count is detected once and the exact arity is preserved for signatures of up to 12 return values; unusual signatures fall back to the previous hook.
## Fixed in 1.4.12
- **Open Mail recovery overhead on mailbox open:** 1.4.11 still called the full `ensure_open_mail_button()` routine from `MAIL_SHOW()`. Even when the button was healthy, that path inspected parent, anchor, label, script, size, frame level and visibility. 1.4.12 keeps the normal path Otari-like: if `TurtleMailOpenMailButton` already exists, is parented to `InboxFrame`, and is shown, `MAIL_SHOW()` leaves it completely untouched. The full recovery routine now runs only if the button is missing, hidden, or re-parented. No inbox polling, Open All, sending, logging, or mail-processing logic is changed.
## 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:
@@ -60,4 +38,4 @@ The compatibility build was tested in-game on Turtle WoW 1.18.x and the reported
## Status
**1.4.12 is the current test build pending in-game validation.** The fixes remain isolated in `TurtleMailFix.lua` so upstream code stays easy to compare and future upstream changes remain easier to merge.
**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
@@ -1,55 +0,0 @@
# 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
@@ -1,74 +0,0 @@
# 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
@@ -1,30 +0,0 @@
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
@@ -1,20 +0,0 @@
# 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
@@ -1,48 +0,0 @@
# 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.
+34 -164
View File
@@ -1,183 +1,53 @@
# 📬 TurtleMail Turtle WoW
# TurtleMail - WoW 1.12 / Turtle WoW
A lightweight mailbox enhancement for **World of Warcraft 1.12**, maintained for **Turtle WoW 1.18.x / Octo-like environments**.
This fork keeps the original TurtleMail features while adding a compatibility and safety layer for **Turtle WoW 1.18.x / Octo**.
TurtleMail improves the Vanilla mail interface with faster mail handling, multiple attachments, recipient autocomplete, mail logging and useful Auction House / returned-mail indicators.
> Stable release: **1.4.6**
> Based on upstream **sica42/TurtleMail 1.4.5**.
> The compatibility work is isolated in `TurtleMailFix.lua` so upstream changes remain easy to compare and merge.
> This fork keeps the original TurtleMail workflow while adding a compatibility and safety layer for modern Turtle WoW 1.12 environments.
## Features
## 📦 Installation
- **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**
1. Download the addon.
2. Make sure the addon folder is named `TurtleMail`.
3. Copy it to:
**\<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.
`World of Warcraft\Interface\AddOns\TurtleMail`
Note that COD is always ignored when opening, both automatically as well as by **\<Right Click>**.
4. Restart the game.
5. Make sure **TurtleMail** is enabled in the AddOns menu.
Logging is disabled by default. Enable with `/tm log`.
No additional addon is required for the core functionality.
## Turtle WoW compatibility fixes
## ✨ Features
Version **1.4.6** addresses:
- 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.
- `MailHorizontalBarLeft` / `MailHorizontalBarRight` nil crashes.
- Missing or malformed `TurtleMail_AutoCompleteNames` SavedVariables.
- Broken learning of sender names for autocomplete.
- Sent-money logging not being recorded correctly.
- Inbox index/COD nil safety during automatic opening.
- Stale AH/returned icons on empty inbox rows.
- Fragile mailbox/package-frame lookups.
- A calendar tooltip nil/stale-value edge case.
- Several additional guards around mailbox UI replacements and malformed state.
## 🖱️ Mail controls
See [`AUDIT.md`](AUDIT.md) for the full audit and remaining architectural risks.
### Inbox
## Validation status
**Right-click** an inbox entry to automatically:
Version **1.4.6** has been promoted to stable after in-game testing on Turtle WoW 1.18.x completed without Lua errors in the tested setup.
1. Loot attached gold.
2. Loot the attached item.
3. Delete the empty letter.
The patch intentionally keeps the original send/open workflow instead of redesigning it. Addons that heavily replace or re-parent the Blizzard mail UI can still create compatibility issues; known architectural risks are documented in `AUDIT.md`.
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.12** extends the compatibility, safety, and performance 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.
- Fixed `MailMailButton` nil crashes while enabling/disabling or clearing the send form, and hardened the paired subject edit-box reference.
- Restored the missing **Open Mail** button with an idempotent recovery path.
- Reduced first-open mailbox stutter by removing Open Mail layout repair from `MAIL_INBOX_UPDATE` and only changing the button when its state is actually wrong.
- Made Open Mail recovery fully lazy on mailbox open: normal openings now leave the existing button completely untouched, matching Otari-style behavior; full repair only runs if the button is missing, hidden, or re-parented.
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 test version:
**1.4.11**
Based on upstream **TurtleMail 1.4.5**.
Version 1.4.10 keeps the **Open Mail** recovery from 1.4.9 but makes it lightweight: the button is repaired on load/mailbox open only when needed, instead of being re-parented and re-anchored on every inbox refresh. This avoids repeated layout work during the initial mailbox update burst.
Version 1.4.11 also removes the per-call `pack()`/`unpack()` allocation from TurtleMail's `GetContainerItemInfo` hook. The native return count is detected once at login and preserved exactly (up to 12 values), while normal bag queries use fixed local values. This reduces garbage collection pressure during bag/mailbox refreshes without changing mail behavior.
## 🖼️ Screenshots
## 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
@@ -1,125 +0,0 @@
# 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.
+1 -1
View File
@@ -2,7 +2,7 @@
## Interface: 11200
## Title: |cffabd473Turtle|cffffffffMail
## Author: shirsig/sica, Dusk-92 (compatibility fixes)
## Version: 1.4.12
## Version: 1.4.6
## Notes: Mailbox enhancement - Turtle WoW 1.18.x compatibility
## SavedVariables: TurtleMail_AutoCompleteNames
## SavedVariablesPerCharacter: TurtleMail_To TurtleMail_Point TurtleMail_Log
+2 -527
View File
@@ -8,7 +8,7 @@ local m = TurtleMail
local getn = table.getn
local function pack( ... ) return arg end
m.compat_version = "1.4.12"
m.compat_version = "1.4.6"
local DEFAULT_SENT_FILTERS = {
Money = 1,
@@ -110,190 +110,6 @@ local function ensure_horizontal_bars()
return m.api.MailHorizontalBarLeft and m.api.MailHorizontalBarRight
end
-- Upstream keeps the real Send button and subject edit box in ad-hoc globals
-- (MailMailButton / MailSubjectEditBox), then replaces the Blizzard globals with
-- proxy tables. If another addon changes those globals, or sendmail_load() runs
-- with an already-proxied widget, the saved globals can become nil and later
-- calls crash. Preserve strong references before the upstream swap happens.
local preserved_send_mail_button = m.api.SendMailMailButton
local preserved_subject_edit_box = m.api.SendMailSubjectEditBox
local function is_named_widget( widget, expected_name )
if not widget or type( widget.GetName ) ~= "function" then return false end
return widget:GetName() == expected_name
end
local function ensure_send_mail_button()
local button = preserved_send_mail_button
if not is_named_widget( button, "SendMailMailButton" ) then
button = m.real_send_mail_button
end
if not is_named_widget( button, "SendMailMailButton" ) then
button = MailMailButton
end
if not is_named_widget( button, "SendMailMailButton" ) then
button = m.api.SendMailMailButton
end
if is_named_widget( button, "SendMailMailButton" ) then
preserved_send_mail_button = button
m.real_send_mail_button = button
MailMailButton = button
return button
end
return nil
end
local function ensure_subject_edit_box()
local edit_box = preserved_subject_edit_box
if not is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
edit_box = m.real_subject_edit_box
end
if not is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
edit_box = MailSubjectEditBox
end
if not is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
edit_box = m.api.SendMailSubjectEditBox
end
if is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
preserved_subject_edit_box = edit_box
m.real_subject_edit_box = edit_box
MailSubjectEditBox = edit_box
return edit_box
end
return nil
end
local function restore_real_mail_widgets_for_upstream()
local button = ensure_send_mail_button()
local edit_box = ensure_subject_edit_box()
-- Only restore the Blizzard globals immediately before upstream sendmail_load().
-- That function intentionally replaces them with proxy tables afterwards.
if button then m.api.SendMailMailButton = button end
if edit_box then m.api.SendMailSubjectEditBox = edit_box end
end
-- The upstream Open Mail button is created only once in inbox_load(). If that
-- creation is skipped/interrupted, or another UI addon hides/re-parents the
-- button afterwards, TurtleMail never recreates it. Keep the button recoverable,
-- but only mutate its layout when something is actually wrong. This avoids doing
-- repeated SetParent/ClearAllPoints/SetPoint work during inbox refresh bursts.
local function ensure_open_mail_button()
local inbox = m.api.InboxFrame
if not inbox or not m.api.CreateFrame then return nil end
local button = m.api.TurtleMailOpenMailButton
local created = false
if not button or type( button.SetPoint ) ~= "function" then
button = m.api.CreateFrame( "Button", "TurtleMailOpenMailButton", inbox, "UIPanelButtonTemplate" )
m.api.TurtleMailOpenMailButton = button
created = true
end
local parent_changed = false
if button.GetParent and button.SetParent then
if button:GetParent() ~= inbox then
button:SetParent( inbox )
parent_changed = true
end
elseif created and button.SetParent then
button:SetParent( inbox )
parent_changed = true
end
local needs_anchor = created or parent_changed
if not needs_anchor and button.GetPoint then
local point, relative_to, relative_point, x, y = button:GetPoint()
needs_anchor = point ~= "BOTTOM"
or (relative_to and relative_to ~= inbox)
or relative_point ~= "BOTTOM"
or x ~= -10
or y ~= 90
end
if needs_anchor then
if button.ClearAllPoints then button:ClearAllPoints() end
button:SetPoint( "BOTTOM", inbox, "BOTTOM", -10, 90 )
end
local wanted_text = m.api.OPENMAIL or "Open Mail"
local text_changed = created
if button.GetText then
text_changed = button:GetText() ~= wanted_text
end
if text_changed and button.SetText then
button:SetText( wanted_text )
end
-- Recalculate dimensions only when the button was created or its label changed.
if created or text_changed then
local width = 120
local font_string = button.GetFontString and button:GetFontString()
if font_string and font_string.GetStringWidth then
width = math.max( 120, 30 + (font_string:GetStringWidth() or 0) )
end
if button.SetWidth then button:SetWidth( width ) end
if button.SetHeight then button:SetHeight( 25 ) end
end
if button.SetScript then
local onclick = button.GetScript and button:GetScript( "OnClick" ) or nil
if created or onclick ~= m.inbox_open_all then
button:SetScript( "OnClick", m.inbox_open_all )
end
end
if inbox.GetFrameLevel and button.SetFrameLevel then
local target_level = (inbox:GetFrameLevel() or 0) + 5
local current_level = button.GetFrameLevel and button:GetFrameLevel() or nil
if current_level ~= target_level then
button:SetFrameLevel( target_level )
end
end
if button.Enable then
if not button.IsEnabled or not button:IsEnabled() then
button:Enable()
end
end
if button.Show then
if not button.IsShown or not button:IsShown() then
button:Show()
end
end
return button
end
-- Replace the fragile one-shot inbox loader with an idempotent version. This
-- preserves upstream behavior while making Open Mail recoverable instead of
-- relying on the initial CreateFrame call succeeding forever.
function m.inbox_load()
if m.api.InboxFrame and m.api.InboxFrame.EnableMouse then
m.api.InboxFrame:EnableMouse( false )
end
ensure_open_mail_button()
for i = 1, 7 do
local auction_texture = m.api[ "TurtleMailAuctionIcon" .. i .. "Texture" ]
local returned_texture = m.api[ "TurtleMailReturnedArrow" .. i .. "Texture" ]
local color = m.api.NORMAL_FONT_COLOR
if color and auction_texture and auction_texture.SetVertexColor then
auction_texture:SetVertexColor( color.r, color.g, color.b )
end
if color and returned_texture and returned_texture.SetVertexColor then
returned_texture:SetVertexColor( color.r, color.g, color.b )
end
end
end
-- SavedVariables are not guaranteed to have a valid schema. Repair them before
-- the original handlers touch nested fields.
do
@@ -350,50 +166,7 @@ do
local original = m.sendmail_load
function m.sendmail_load()
ensure_horizontal_bars()
restore_real_mail_widgets_for_upstream()
if original then
local result = original()
-- Upstream has now installed its proxy globals. Re-assert the private
-- references used by the legacy MailMailButton/MailSubjectEditBox code.
ensure_send_mail_button()
ensure_subject_edit_box()
return result
end
end
end
-- Replace the fragile upstream CanSend hook. The original directly calls the
-- global MailMailButton and crashes when that saved global is nil.
if m.hooks then
m.hook.SendMailFrame_CanSend = function()
local button = ensure_send_mail_button()
if not button then return end
local name_box = m.api.SendMailNameEditBox
local send_money_button = m.api.SendMailSendMoneyButton
local send_money_frame = m.api.SendMailMoney
local recipient = name_box and name_box.GetText and name_box:GetText() or ""
local attached = m.sendmail_num_attachments and m.sendmail_num_attachments() or 0
local price = m.api.GetSendMailPrice and (tonumber( m.api.GetSendMailPrice() ) or 0) or 0
local player_money = m.api.GetMoney and (tonumber( m.api.GetMoney() ) or 0) or 0
local money = 0
if send_money_button and send_money_button.GetChecked and send_money_button:GetChecked()
and m.api.MoneyInputFrame_GetCopper and send_money_frame then
money = tonumber( m.api.MoneyInputFrame_GetCopper( send_money_frame ) ) or 0
end
local can_send = not m.sendmail_sending
and string.len( recipient ) > 0
and money + price * math.max( 1, attached ) <= player_money
if can_send then
button:Enable()
else
button:Disable()
end
if original then return original() end
end
end
@@ -405,67 +178,6 @@ if m.hooks and m.hooks.SendMailFrame_Update then
end
end
-- Ensure the legacy click handler sees a valid subject edit box and real send
-- button before it reads them. This protects the same upstream proxy hack from
-- producing a follow-up MailSubjectEditBox nil error.
do
local original = m.send_mail_button_onclick
function m.send_mail_button_onclick()
ensure_send_mail_button()
ensure_subject_edit_box()
if original then return original() end
end
end
-- Replace the upstream clear routine because it directly dereferences
-- MailMailButton and MailSubjectEditBox. Keep its behavior, but guard every UI
-- object so mailbox replacements cannot turn cleanup into another Lua error.
function m.sendmail_clear()
local any_item
for i = 1, 21 do
local attachment = m.api[ "MailAttachment" .. i ]
if attachment then
any_item = any_item or attachment.item
attachment.item = nil
end
end
if any_item and m.api.ClearCursor and m.api.PickupContainerItem then
m.api.ClearCursor()
m.api.PickupContainerItem( unpack( any_item ) )
m.api.ClearCursor()
end
local button = ensure_send_mail_button()
if button and button.Disable then button:Disable() end
local name_box = m.api.SendMailNameEditBox
if name_box then
if name_box.SetText then name_box:SetText( "" ) end
if name_box.SetFocus then name_box:SetFocus() end
end
local subject = ensure_subject_edit_box()
if subject and subject.SetText then subject:SetText( "" ) end
if m.api.SendMailBodyEditBox and m.api.SendMailBodyEditBox.SetText then
m.api.SendMailBodyEditBox:SetText( "" )
end
if m.api.MoneyInputFrame_ResetMoney and m.api.SendMailMoney then
m.api.MoneyInputFrame_ResetMoney( m.api.SendMailMoney )
end
if m.api.SendMailRadioButton_OnClick then
m.api.SendMailRadioButton_OnClick( 1 )
end
if m.api.SendMailFrame_Update then
m.api.SendMailFrame_Update()
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
@@ -475,23 +187,6 @@ end
-- Safer MAIL_SHOW: package-button regions differ between UI replacements.
function m.MAIL_SHOW()
-- Re-assert the legacy send-widget references whenever the mailbox opens.
-- Open Mail follows the lighter Otari-style path: when the existing button
-- is still shown and parented to InboxFrame, do nothing at all. Only invoke
-- the full recovery routine if the button is missing, hidden, or re-parented.
ensure_send_mail_button()
ensure_subject_edit_box()
local open_mail_button = m.api.TurtleMailOpenMailButton
local open_mail_ok = open_mail_button
and type( open_mail_button.SetPoint ) == "function"
and (not open_mail_button.GetParent or open_mail_button:GetParent() == m.api.InboxFrame)
and (not open_mail_button.IsShown or open_mail_button:IsShown())
if not open_mail_ok then
ensure_open_mail_button()
end
if not m.api.MailFrame then return end
if m.api.TurtleMail_Point then
@@ -781,223 +476,3 @@ if m.calendar and m.calendar.show then
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
-- Performance compatibility fix inspired by Otari98/TurtleMail: the upstream
-- GetContainerItemInfo hook builds a temporary table with pack()/unpack() on
-- every bag query. Bag and mailbox refreshes can call this hook very frequently,
-- creating avoidable garbage and occasional GC hitches. Detect the native return
-- count once after the normal PLAYER_LOGIN hook setup, then use a fixed-local
-- wrapper with no per-call temporary table. Exact return arity is preserved for
-- up to 12 values so extended/custom clients remain compatible; if detection is
-- inconclusive, the existing upstream hook is left untouched.
do
local original_player_login = m.PLAYER_LOGIN
local legacy_hook = m.hooks and m.hooks.GetContainerItemInfo or nil
local detected_return_count
local function count_returns( ... )
return arg.n or getn( arg )
end
local function detect_container_info_return_count()
local fn = m.orig and m.orig.GetContainerItemInfo
if type( fn ) ~= "function" then return nil end
-- Prefer an occupied inventory slot so clients that return no values for an
-- empty slot cannot make us detect the wrong arity.
if m.api.GetContainerNumSlots then
for bag = 0, 4 do
local slots = tonumber( m.api.GetContainerNumSlots( bag ) ) or 0
for slot = 1, slots do
local texture = fn( bag, slot )
if texture then
return count_returns( fn( bag, slot ) )
end
end
end
end
-- A normal character should always have at least one item, but retain a
-- conservative fallback for unusual test clients or empty inventories.
local count = count_returns( fn( 0, 1 ) )
if count and count > 0 then return count end
return nil
end
local function optimized_get_container_item_info( bag, slot )
local r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12 =
m.orig.GetContainerItemInfo( bag, slot )
if not r3 and m.sendmail_attached( bag, slot ) then
r3 = 1
end
if detected_return_count == 1 then return r1 end
if detected_return_count == 2 then return r1, r2 end
if detected_return_count == 3 then return r1, r2, r3 end
if detected_return_count == 4 then return r1, r2, r3, r4 end
if detected_return_count == 5 then return r1, r2, r3, r4, r5 end
if detected_return_count == 6 then return r1, r2, r3, r4, r5, r6 end
if detected_return_count == 7 then return r1, r2, r3, r4, r5, r6, r7 end
if detected_return_count == 8 then return r1, r2, r3, r4, r5, r6, r7, r8 end
if detected_return_count == 9 then return r1, r2, r3, r4, r5, r6, r7, r8, r9 end
if detected_return_count == 10 then return r1, r2, r3, r4, r5, r6, r7, r8, r9, r10 end
if detected_return_count == 11 then return r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11 end
if detected_return_count == 12 then return r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12 end
-- Should not normally be reached; keep the previous behavior if a custom
-- client exposes an unexpected signature.
if legacy_hook then return legacy_hook( bag, slot ) end
return r1, r2, r3, r4, r5
end
function m.PLAYER_LOGIN()
if original_player_login then original_player_login() end
detected_return_count = detect_container_info_return_count()
if detected_return_count and detected_return_count >= 1 and detected_return_count <= 12 then
m.api.GetContainerItemInfo = optimized_get_container_item_info
end
end
end