跳到主要内容

开发者 API

其他插件可以通过 StarMailAPI 把物品、金币、点券、指令奖励寄进玩家的邮箱。玩家背包满了、不在线、在别的子服都没关系,奖励会在邮箱里等他。 同系列的 StarCrate 星月造物就是用它把抽奖奖励寄给玩家的;StarAuction 星月市集则用它(加上 幂等寄送)寄出买到的物品、卖出的收入与退回的东西。

所有类都在 dev.starplugins.mail.api 包里:

类用途
StarMailAPI入口:寄信、查询数量、打开邮箱
MailDraft一封正在写的信(建造者写法)
MailSendEvent邮件存入前触发,可以修改或取消
MailClaimEvent玩家领取附件后触发

引入​

把 StarMail 的 jar 加进编译依赖(只在编译时使用,不要打包进你的插件),例如 Gradle:

dependencies {
compileOnly files('libs/StarMail.jar') // 资源中心下载的星月邮箱 jar
}

plugin.yml 加上软依赖,确保星月邮箱先载入(没装也能正常启动):

softdepend: [StarMail]

快速开始​

import dev.starplugins.mail.api.StarMailAPI;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;

if (StarMailAPI.available()) {
StarMailAPI.send(player, StarMailAPI.draft()
.sender("<gold>签到系统")
.title("每日签到奖励")
.content("感谢你今天也来签到!", "明天继续加油~")
.item(new ItemStack(Material.DIAMOND, 3))
.money(100)
.source("MySignIn"));
}
先检查 available()

星月邮箱没安装、被停用、或授权还没验证通过时,available() 返回 false,这时调用 send / sendOnline / sendAll 会抛出 IllegalStateException。

StarMailAPI​

方法返回说明
available()boolean现在能不能寄信
draft()MailDraft建立一封空白的信,等于 new MailDraft()
send(UUID receiver, String receiverName, MailDraft draft)CompletableFuture<List<Long>>寄给一个玩家(在线、离线都可以)。返回新邮件的编号;物品超过 mail.max-items 时拆成好几封,所以可能有好几个编号;被 MailSendEvent 取消时是空列表
send(OfflinePlayer receiver, MailDraft draft)同上同上,直接传玩家
sendOnline(MailDraft draft)CompletableFuture<Integer>寄给这个子服上所有在线玩家,每人一封。返回收到的人数
sendAll(MailDraft draft, boolean onlyExisting, String permission)CompletableFuture<Long>全服邮件,返回全服邮件编号(被取消时是 -1)。见下方
unread(UUID uuid)int在线玩家的未读数(读取缓存,不查数据库);不知道时 -1
unclaimed(UUID uuid)int在线玩家附件待领取的邮件数;不知道时 -1
open(Player player)boolean帮玩家打开邮箱(装了云界面时打开 云界面邮箱)

寄信的细节​

  • API 寄出的信类型是「系统」,期限默认 mail.system-expire-days(30 天),寄件人没写时用 default-sender。
  • 寄信是异步的:方法马上返回,数据库写入在后台完成。CompletableFuture 的回调在后台线程执行,要碰 Bukkit 的东西(发消息、改背包)请切回主线程(Folia 用对应的调度器)。
  • 收件人在线时,会马上收到新邮件提示;在别的子服时,几秒内收到。
StarMailAPI.send(uuid, name, draft).thenAccept(ids -> {
if (ids.isEmpty()) getLogger().info("寄信被其他插件取消了");
else getLogger().info("已寄出邮件 #" + ids.get(0));
}).exceptionally(e -> {
getLogger().warning("寄信失败:" + e);
return null;
});

全服邮件 sendAll​

// 所有玩家(含之后第一次进服的新玩家)
StarMailAPI.sendAll(draft, false, "");
// 只给现在已经进过服的玩家
StarMailAPI.sendAll(draft, true, "");
// 只给有 group.vip 权限的玩家
StarMailAPI.sendAll(draft, false, "group.vip");
参数说明
onlyExistingtrue = 只发给发送前就进过服的玩家;false = 之后的新玩家也收到
permission只发给有这个权限的玩家;"" 或 null = 所有人。权限在玩家在线时检查:现在没有权限的玩家不会收到,之后拿到权限、全服邮件还在发放期间上线时就会收到
  • 每个玩家只会收到一封;跨服时任何子服都会发放。
  • 期限从发出那一刻开始算,到期后不再发给新上线的玩家。想一直发,expireDays(0)。
  • 一封全服邮件只能有 mail.max-items 组物品,超过的部分会被丢弃(控制台警告),不会拆成多封。

MailDraft​

所有设定方法都返回 MailDraft 本身,可以一直串下去。文字都支持 & 颜色、&#RRGGBB 与 MiniMessage。

方法说明
sender(String name)寄件人名称。不写 = default-sender
senderUuid(UUID uuid)寄件人的 UUID(一般不需要)
title(String title)标题
content(String... lines) / content(Collection<String> lines)正文,会取代之前设的正文;字符串里的 \n 也会分行
item(ItemStack item)加一个物品(会复制一份,空气与数量 0 的会被忽略)
items(Collection<ItemStack> list)加好几个物品
clearItems()清空物品
money(double amount)金币(负数当 0)。领取的子服需要 Vault
points(int amount)点券(负数当 0)。领取的子服需要 PlayerPoints
command(String command) / commands(Collection<String> list)领取时执行的指令,见下方
note(String line) / notes(Collection<String> list)附件说明,显示在附件列表(例如「VIP 7 天」)
icon(String material) / icon(Material material)附件待领取时在邮箱里显示的图标;空 = 默认箱子
source(String source)来源标记(例如你的插件名称),会传到 MailClaimEvent,方便你认出自己寄的信
expireDays(double days)几天后过期;0 = 永不过期
expireMillis(long ms)同上,用毫秒;0 = 永不过期,-1 = 默认
key(String key)(1.1.0+)幂等寄送的唯一键:同一个 key 只会存一次邮件,最长 128 个字符,见 幂等寄送
copy()复制一份
hasAttachments()有没有物品、金币、点券或指令

对应的读取方法:sender()、senderUuid()、title()、content()、items()、money()、points()、commands()、notes()、icon()、source()、expireMillis()、key()(没设时是空字符串)。

领取指令​

draft.command("lp user {player} parent addtemp vip 7d") // 控制台执行
.command("[player] warp vip") // 玩家自己执行
.command("[message] <green>VIP 已开通!") // 给玩家发一条消息
.note("VIP 会员 7 天"); // 让玩家在附件列表看到奖励名称
  • 变量:{player} / %player% = 玩家名,{uuid} = 玩家 UUID。
  • 没有前缀(或 [console])= 控制台执行;[player] = 玩家执行;[message] = 发消息。开头的 / 会自动去掉。
  • 指令在玩家领取附件时、在领取的那个子服执行。

幂等寄送:同一个 key 只寄一次​

从 1.1.0 起,MailDraft 可以带一个 key。带 key 寄出的信,同一个 key 在数据库里最多只存一次:

  • 第一次寄出:照常存入邮件,并记下这个 key 与邮件编号(两者写在同一个数据库交易里,要么都存进去,要么都没有)。
  • 之后再用同一个 key 寄(不论内容是否相同):不会再存任何邮件,直接返回第一次的邮件编号;收件人也不会再收到新邮件提示。
  • 两个子服同时用同一个 key 寄:数据库主键只让一边成功,另一边拿到的是成功那一边的邮件编号。

这让「寄出去了没有」不确定的情况可以放心重试:等待回应超时、寄出后还没记录结果就当机、好几个子服都可能负责寄同一笔……只要每次都带同一个 key,玩家最后只会收到一份。

long rewardId = 1234; // 你自己数据库里这笔奖励的编号
StarMailAPI.send(uuid, name, StarMailAPI.draft()
.title("活动奖励")
.item(new ItemStack(Material.DIAMOND, 5))
.source("MyEvent")
.key("myevent:reward:" + rewardId))
.thenAccept(ids -> {
// 第一次或重试都会拿到同一组邮件编号:这时才把奖励标记为「已寄出」
if (!ids.isEmpty()) markSent(rewardId, ids.get(0));
});
// 超时、失败、开服时发现还没标记:用同一个 key 再寄一次就好,不会重复
注意事项说明
key 要唯一且固定建议写成 插件名:用途:编号,例如 myplugin:reward:1234;超过 128 个字符的部分会被截掉
先检查 available()星月邮箱没就绪时照样会抛出 IllegalStateException,之后再重试即可
被事件取消MailSendEvent 仍然会触发;被取消时返回空列表、不会记下 key,之后可以再寄
物品拆成多封物品超过 mail.max-items 拆成好几封时,整组邮件共用这个 key,重寄时返回同一组编号
跨服key 存在星月邮箱的数据库里:子服要共用同一个 MySQL(同样的 table-prefix)才能跨服防重复,见 跨服设置
保留多久key 记录在 mail_key 表,与邮件记录一起保留 mail.log-keep-days 天(默认 90 天,0 = 永久),过期后清除;同一个 key 隔这么久再寄会被当成新邮件
不带 key没设 key(或设成空字符串)时和以前完全一样,每次寄送都是新邮件
需要 1.1.0 以上

key() 是 1.1.0 新增的方法。想兼容旧版时,可以先用反射检查 MailDraft.class.getMethod("key", String.class) 是否存在;StarAuction 星月市集就是这样在开服时检查星月邮箱版本的。

一个完整的例子:抽奖奖励寄到邮箱​

public void giveRewards(Player player, List<ItemStack> prizes, String crateName) {
if (!StarMailAPI.available()) {
// 没有星月邮箱:直接给,放不下的掉在地上
player.getInventory().addItem(prizes.toArray(new ItemStack[0]))
.values().forEach(i -> player.getWorld().dropItemNaturally(player.getLocation(), i));
return;
}
MailDraft draft = StarMailAPI.draft()
.sender("<gradient:#F7971E:#FFD200>" + crateName + "</gradient>")
.title("开箱奖励")
.content("恭喜获得以下奖励:", "请在 7 天内领取。")
.items(prizes)
.icon(prizes.isEmpty() ? Material.CHEST : prizes.get(0).getType())
.source("MyCrates")
.expireDays(7);
StarMailAPI.send(player, draft);
}

事件​

MailSendEvent​

邮件存入数据库之前触发。可以修改内容,或取消。

方法说明
target()PLAYER(寄给一个玩家)、ONLINE(本服全部在线)、ALL(全服邮件)
receiver() / receiverName()收件人;ONLINE、ALL 时为 null
type()SYSTEM(API)、ADMIN(管理员 / 指令)、PLAYER(玩家互寄)、BROADCAST(全服)
draft()这封信,可以直接修改(标题、物品…)
isCancelled() / setCancelled(boolean)取消后不会寄出
  • 可能在非主线程触发(isAsynchronous() 为 true),监听器里不要直接操作世界与玩家。
  • 全服邮件只在发出时触发一次(target() 为 ALL),之后复制到每个玩家邮箱时不会再触发。
  • 取消玩家互寄(PLAYER)时,寄件人的附件、邮费与金币会退回。
  • 「背包已满 · 物品退回」与过期退信是插件自己保管物品用的,不会触发这个事件,所以不会被其他插件取消或修改。
@EventHandler
public void onSend(MailSendEvent e) {
if (e.type().equals("PLAYER") && e.draft().money() > 100000) {
e.setCancelled(true); // 不允许玩家一次寄太多金币
}
}

MailClaimEvent​

玩家领取附件之后触发(每封邮件一次),在玩家所在的线程。只用来通知,不能取消。

方法说明
getPlayer()领取的玩家
mailId()邮件编号
title()标题
source()寄信时 MailDraft.source(...) 设的来源
items()领到的物品(复制品)
money() / points()领到的金币 / 点券
commands()执行的指令
@EventHandler
public void onClaim(MailClaimEvent e) {
if ("MyCrates".equals(e.source())) {
e.getPlayer().sendMessage("你领取了开箱奖励 #" + e.mailId());
}
}