[[[release 0.7.0.16*fuzhou]]]

## 🧯Bug Fix

- 修复不是命令的消息却由命令系统错误抓取又由于包含"@"导致无法传递到其它事件的问题

## 📇功能

- 添加了为安妮吃药提醒功能 #35
- 添加管理员私聊中操作 morny 向某对话发送信息的指令 *msg *msgsend #31
- 删除了 KuohuanhuanNeedSleep 功能
- 删除了对 DP7 关闭 SlashAction 的 feature
- 用户资料获取支持了获取用户所在 dc (通过web-cdn方式) #6
- 添加功能 /XXX还是XXX 随机应答 #2
- 添加 /r 命令用于让用户可以删除自己触发的 bot 消息
- 添加对telegram"内置"命令 /start 的支持,使 /start 返回打招呼的信息

## 🔌系统接口

- 为 Morny Coeur 系统添加了 CODENAME 属性(意指每个大版本的名称)
- 启动参数的 alias 广泛化推进
- 添加 --api 和 --api-files 选项提供 local bot api server 自定义功能 #33
- 添加了从环境变量 TELEGRAM_BOT_API_TOKEN 或 MORNY_TG_TOKEN 获取 bot key 的支持 #34
- 添加了 daemon 包和 MornyDaemons 用于管理 morny coeur 的常驻任务
- 命令系统的规范中,命令名称的声明现在需要删除 "/" 前缀

## 🔩技术修改/typo

- 添加启动时显示 trusted-readers-of-dinner 数据
- 启动时回显 Coeur 版本信息
- 修改 /runtime 回显格式
  - cpu核心数量改放到 vm memory 段落
  - 添加显示系统架构os.arch
  - jvm 信息添加 jvm 制造商java.vm.vendor名称
  - 将 java 版本信息从 java 版本java.version 改为 jvm 版本java.vm.version
- 修改用户资料获取将 firstname 和 lastname 合并为 display name 单个条目
This commit is contained in:
A.C.Sukazyo Eyre 2022-06-01 17:16:06 +08:00
commit e42dfacb2e
Signed by: Eyre_S
GPG Key ID: EFB47D98FE082FAD
26 changed files with 690 additions and 111 deletions

View File

@ -43,6 +43,9 @@ task updateVersionCode {
ant.replaceregexp(match:'VERSION = ["a-zA-Z0-9.\\-_+@]+;', replace:"VERSION = \"$project.version\";", flags:'g', byline:true) { ant.replaceregexp(match:'VERSION = ["a-zA-Z0-9.\\-_+@]+;', replace:"VERSION = \"$project.version\";", flags:'g', byline:true) {
fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java') fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java')
} }
ant.replaceregexp(match:'CODENAME = ["a-zA-Z0-9]+;', replace:"CODENAME = \"${CODENAME}\";", flags:'g', byline:true) {
fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java')
}
ant.replaceregexp(match:'COMPILE_TIMESTAMP = [0-9]+L;', replace:"COMPILE_TIMESTAMP = ${System.currentTimeMillis()}L;", flags:'g', byline:true) { ant.replaceregexp(match:'COMPILE_TIMESTAMP = [0-9]+L;', replace:"COMPILE_TIMESTAMP = ${System.currentTimeMillis()}L;", flags:'g', byline:true) {
fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java') fileset(dir: 'src/main/java/cc/sukazyo/cono/morny', includes: 'GradleProjectConfigures.java')
} }

View File

@ -1,6 +1,8 @@
## Core ## Core
VERSION = 0.6.4.0 VERSION = 0.7.0.16
CODENAME = fuzhou
# dependencies # dependencies

View File

@ -4,6 +4,7 @@ package cc.sukazyo.cono.morny;
* the final field that will be updated by gradle automatically. * the final field that will be updated by gradle automatically.
*/ */
public class GradleProjectConfigures { public class GradleProjectConfigures {
public static final String VERSION = "0.6.4.0"; public static final String VERSION = "0.7.0.16";
public static final long COMPILE_TIMESTAMP = 1652197330102L; public static final String CODENAME = "fuzhou";
public static final long COMPILE_TIMESTAMP = 1654072970797L;
} }

View File

@ -4,9 +4,12 @@ import cc.sukazyo.cono.morny.bot.api.OnUpdate;
import cc.sukazyo.cono.morny.bot.command.MornyCommands; import cc.sukazyo.cono.morny.bot.command.MornyCommands;
import cc.sukazyo.cono.morny.bot.event.EventListeners; import cc.sukazyo.cono.morny.bot.event.EventListeners;
import cc.sukazyo.cono.morny.bot.query.MornyQueries; import cc.sukazyo.cono.morny.bot.query.MornyQueries;
import cc.sukazyo.cono.morny.data.tracker.TrackerDataManager; import cc.sukazyo.cono.morny.daemon.MornyDaemons;
import cc.sukazyo.cono.morny.daemon.TrackerDataManager;
import cc.sukazyo.untitled.telegram.api.extra.ExtraAction; import cc.sukazyo.untitled.telegram.api.extra.ExtraAction;
import com.pengrad.telegrambot.TelegramBot; import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.impl.FileApi;
import com.pengrad.telegrambot.model.User;
import com.pengrad.telegrambot.request.GetMe; import com.pengrad.telegrambot.request.GetMe;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -43,7 +46,13 @@ public class MornyCoeur {
* 如果在登陆之前就定义了此字段则登陆代码会验证登陆的 bot username * 如果在登陆之前就定义了此字段则登陆代码会验证登陆的 bot username
* 是否与定义的 username 符合如果不符合则会报错 * 是否与定义的 username 符合如果不符合则会报错
*/ */
private final String username; public final String username;
/**
* morny bot 账户的 telegram id<br>
* <br>
* 这个字段将会在登陆成功后赋值为登录到的 bot id
*/
public final long userid;
/** /**
* morny 的事件忽略前缀时间<br> * morny 的事件忽略前缀时间<br>
* <br> * <br>
@ -59,7 +68,7 @@ public class MornyCoeur {
public static final long DINNER_CHAT_ID = -1001707106392L; public static final long DINNER_CHAT_ID = -1001707106392L;
private record LogInResult(TelegramBot account, String username) { } private record LogInResult(TelegramBot account, String username, long userid) { }
/** /**
* 执行 bot 初始化 * 执行 bot 初始化
@ -74,6 +83,7 @@ public class MornyCoeur {
* 单位为毫秒 * 单位为毫秒
*/ */
private MornyCoeur ( private MornyCoeur (
@Nullable String botApi, @Nullable String botApi4File,
@Nonnull String botKey, @Nullable String botUsername, @Nonnull String botKey, @Nullable String botUsername,
long master, long trustedChat, Set<Long> trustedRDinner, long master, long trustedChat, Set<Long> trustedRDinner,
long latestEventTimestamp, long latestEventTimestamp,
@ -90,17 +100,21 @@ public class MornyCoeur {
} }
try { try {
final LogInResult loginResult = login(botKey, botUsername); final LogInResult loginResult = login(botApi, botApi4File, botKey, botUsername);
this.account = loginResult.account; this.account = loginResult.account;
this.username = loginResult.username; this.username = loginResult.username;
this.userid = loginResult.userid;
this.trusted = new MornyTrusted(master, trustedChat, trustedRDinner); this.trusted = new MornyTrusted(master, trustedChat, trustedRDinner);
StringBuilder trustedReadersDinnerIds = new StringBuilder();
trusted.getTrustedReadersOfDinnerSet().forEach(id -> trustedReadersDinnerIds.append("\n ").append(id));
logger.info(String.format(""" logger.info(String.format("""
trusted param set: trusted param set:
- master (id) - master (id)
%d %d
- trusted chat (id) - trusted chat (id)
%d""", %d
master, trustedChat - trusted reader-of-dinner (id)%s""",
master, trustedChat, trustedReadersDinnerIds
)); ));
} }
catch (Exception e) { catch (Exception e) {
@ -123,29 +137,31 @@ public class MornyCoeur {
* @see #MornyCoeur 程序初始化方法 * @see #MornyCoeur 程序初始化方法
*/ */
public static void main ( public static void main (
@Nullable String botApi, @Nullable String botApi4File,
@Nonnull String botKey, @Nullable String botUsername, @Nonnull String botKey, @Nullable String botUsername,
long master, long trustedChat, Set<Long> trustedRDinner, long latestEventTimestamp, long master, long trustedChat, Set<Long> trustedRDinner, long latestEventTimestamp,
boolean isAutomaticResetCommandList, boolean isRemoveCommandListWhenExit boolean isAutomaticResetCommandList, boolean isRemoveCommandListWhenExit
) { ) {
if (INSTANCE == null) { if (INSTANCE == null) {
logger.info("System Starting"); logger.info("Coeur Starting");
INSTANCE = new MornyCoeur( INSTANCE = new MornyCoeur(
botApi, botApi4File,
botKey, botUsername, botKey, botUsername,
master, trustedChat, trustedRDinner, master, trustedChat, trustedRDinner,
latestEventTimestamp, latestEventTimestamp,
isRemoveCommandListWhenExit isRemoveCommandListWhenExit
); );
TrackerDataManager.init(); MornyDaemons.start();
EventListeners.registerAllListeners(); EventListeners.registerAllListeners();
INSTANCE.account.setUpdatesListener(OnUpdate::onNormalUpdate); INSTANCE.account.setUpdatesListener(OnUpdate::onNormalUpdate);
if (isAutomaticResetCommandList) { if (isAutomaticResetCommandList) {
logger.info("resetting telegram command list"); logger.info("resetting telegram command list");
commandManager().automaticUpdateList(); commandManager().automaticUpdateList();
} }
logger.info("System start complete"); logger.info("Coeur start complete");
return; return;
} }
logger.error("System already started coeur!!!"); logger.error("Coeur already started!!!");
} }
/** /**
@ -161,8 +177,7 @@ public class MornyCoeur {
*/ */
private void exitCleanup () { private void exitCleanup () {
logger.info("clean:save tracker data."); logger.info("clean:save tracker data.");
TrackerDataManager.DAEMON.interrupt(); MornyDaemons.stop();
TrackerDataManager.trackingLock.lock();
if (isRemoveCommandListWhenExit) { if (isRemoveCommandListWhenExit) {
commandManager.automaticRemoveList(); commandManager.automaticRemoveList();
} }
@ -182,21 +197,51 @@ public class MornyCoeur {
* 会通过 GetMe 动作验证是否连接上了 telegram api 服务器 * 会通过 GetMe 动作验证是否连接上了 telegram api 服务器
* 同时也要求登录获得的 username {@link #username} 声明值相等 * 同时也要求登录获得的 username {@link #username} 声明值相等
* *
* @param api bot client 将会连接到的 telegram bot api 位置
* @param api4File bot client 将会连接到的 telegram file api 位置如果不指定则会跟随 {@code api} 选项的设定
* @param key bot api-token * @param key bot api-token
* @param requireName 要求登录到的需要的 username如果登陆后的 username 与此不同则会报错退出
* @return 成功登录后的 {@link TelegramBot} 对象 * @return 成功登录后的 {@link TelegramBot} 对象
*/ */
@Nonnull @Nonnull
private static LogInResult login (@Nonnull String key, @Nullable String requireName) { private static LogInResult login (
final TelegramBot account = new TelegramBot(key); @Nullable String api, @Nullable String api4File,
@Nonnull String key, @Nullable String requireName
) {
final TelegramBot.Builder accountConfig = new TelegramBot.Builder(key);
boolean isCustomApi = false;
String apiUrlSet = "https://api.telegram.org/bot";
String api4FileUrlSet = FileApi.FILE_API;
if (api != null) {
api = api.endsWith("/") ? api.substring(0, api.length() - 1) : api;
accountConfig.apiUrl(apiUrlSet = api.endsWith("/bot")? api : api + "/bot");
isCustomApi = true;
}
if (api4File != null) {
api4File = api4File.endsWith("/") ? api4File : api4File + "/";
accountConfig.fileApiUrl(api4FileUrlSet = api4File.endsWith("/file/bot")? api4File : api4File + "/file/bot");
isCustomApi = true;
} else if (api != null && !api.endsWith("/bot")) {
accountConfig.fileApiUrl(api4FileUrlSet = api + "/file/bot");
}
if (isCustomApi) {
logger.info(String.format("""
Telegram Bot API set to :
- %s
- %s""",
apiUrlSet, api4FileUrlSet
));
}
final TelegramBot account = accountConfig.build();
logger.info("Trying to login..."); logger.info("Trying to login...");
for (int i = 1; i < 4; i++) { for (int i = 1; i < 4; i++) {
if (i != 1) logger.info("retrying..."); if (i != 1) logger.info("retrying...");
try { try {
final String username = account.execute(new GetMe()).user().username(); final User remote = account.execute(new GetMe()).user();
if (requireName != null && !requireName.equals(username)) if (requireName != null && !requireName.equals(remote.username()))
throw new RuntimeException("Required the bot @" + requireName + " but @" + username + " logged in!"); throw new RuntimeException("Required the bot @" + requireName + " but @" + remote.username() + " logged in!");
logger.info("Succeed login to @" + username); logger.info("Succeed login to @" + remote.username());
return new LogInResult(account, username); return new LogInResult(account, remote.username(), remote.id());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(System.out); e.printStackTrace(System.out);
logger.error("login failed."); logger.error("login failed.");
@ -269,4 +314,6 @@ public class MornyCoeur {
return INSTANCE.extraActionInstance; return INSTANCE.extraActionInstance;
} }
public static long getUserid () { return INSTANCE.userid; }
} }

View File

@ -18,6 +18,16 @@ public class MornySystem {
*/ */
public static final String VERSION = GradleProjectConfigures.VERSION; public static final String VERSION = GradleProjectConfigures.VERSION;
/**
* Morny Coeur 当前的版本代号.<br>
* 一个单个单词一般作为一个大版本的名称只在重大更新改变<br>
* 格式保持为仅由小写字母和数字组成<br>
* 有时也可能是复合词或特殊的词句<br>
* <br>
* 会由 gradle 任务 {@code updateVersionCode} 更新
*/
public static final String CODENAME = GradleProjectConfigures.CODENAME;
/** /**
* 获取程序 jar 文件的 md5-hash <br> * 获取程序 jar 文件的 md5-hash <br>
* <br> * <br>

View File

@ -21,7 +21,7 @@ public class MornyTrusted {
*/ */
public final long MASTER; public final long MASTER;
public final Set<Long> TRUSTED_READERS_OF_DINNER; private final Set<Long> TRUSTED_READERS_OF_DINNER;
public MornyTrusted (long master, long trustedChatId, Set<Long> trustedRDinner) { public MornyTrusted (long master, long trustedChatId, Set<Long> trustedRDinner) {
this.TRUSTED_CHAT_ID = trustedChatId; this.TRUSTED_CHAT_ID = trustedChatId;
@ -51,4 +51,8 @@ public class MornyTrusted {
return TRUSTED_READERS_OF_DINNER.contains(userId); return TRUSTED_READERS_OF_DINNER.contains(userId);
} }
public Set<Long> getTrustedReadersOfDinnerSet () {
return Set.copyOf(TRUSTED_READERS_OF_DINNER);
}
} }

View File

@ -18,10 +18,8 @@ import static cc.sukazyo.cono.morny.Log.logger;
*/ */
public class ServerMain { public class ServerMain {
private static boolean versionEchoMode = false; public static final String PROP_TOKEN_KEY = "TELEGRAM_BOT_API_TOKEN";
private static boolean welcomeEchoMode = false; public static final String PROP_TOKEN_MORNY_KEY = "MORNY_TG_TOKEN";
private static boolean showWelcome = true;
/** /**
* 程序入口也是参数处理器<br> * 程序入口也是参数处理器<br>
@ -45,6 +43,14 @@ public class ServerMain {
* {@code --username} {@link MornyCoeur#getUsername() bot username} 预定义 * {@code --username} {@link MornyCoeur#getUsername() bot username} 预定义
* </li> * </li>
* <li> * <li>
* {@code --api} 设定 {@link MornyCoeur#getAccount() bot client} 使用的 telegram bot api server
* 需要注意的是如果带有后缀 {@code /bot} 则会单独设定 api server
* 而不会适应性的同时为 {@code --api-files} 设定值
* </li>
* <li>
* {@code --api-files} 单独设定 {@link MornyCoeur#getAccount() bot client} 使用的 telegram bot file api server
* </li>
* <li>
* {@code --no-hello} 不在主程序启动时输出用于欢迎消息的字符画 * {@code --no-hello} 不在主程序启动时输出用于欢迎消息的字符画
* {@code --only-hello} 参数不兼容 会导致程序完全没有任何输出 * {@code --only-hello} 参数不兼容 会导致程序完全没有任何输出
* </li> * </li>
@ -53,20 +59,20 @@ public class ServerMain {
* 赋值为程序启动的时间从而造成阻挡程序启动之前的消息事件处理效果 * 赋值为程序启动的时间从而造成阻挡程序启动之前的消息事件处理效果
* </li> * </li>
* <li> * <li>
* {@code --auto-cmd} (下面两个)选项 {@code --auto-cmd-list} {@code --auto-cmd-remove} 的合并版本
* </li>
* <li>
* {@code --auto-cmd-list} 使 morny 在启动时自动依据程序本体更新登录 bot 的命令列表 * {@code --auto-cmd-list} 使 morny 在启动时自动依据程序本体更新登录 bot 的命令列表
* </li> * </li>
* <li> * <li>
* {@code --auto-cmd-remove} 使 morny 在关闭时自动依据程序本体删除 bot 的命令列表 * {@code --auto-cmd-remove} 使 morny 在关闭时自动依据程序本体删除 bot 的命令列表
* </li> * </li>
* </ul> * </ul>
* 除去选项之外第一个参数会被赋值为 bot telegram bot api token * <s>除去选项之外第一个参数会被赋值为 bot telegram bot api token</s>
* 第二个参数会被赋值为 bot username 限定名其余的参数会被认定为无法理解<br> * <s>第二个参数会被赋值为 bot username 限定名其余的参数会被认定为无法理解</s><br>
* <br>
* <b> {@code 0.4.2.3}token username 的赋值已被选项组支持</b><br> * <b> {@code 0.4.2.3}token username 的赋值已被选项组支持</b><br>
* 使用参数所进行取值的 token username 已被转移至 {@code --token} {@code --username} 参数 * <b> {@code 0.5.0.4}旧的直接通过参数为 bot token & username 赋值的方式已被删除</b>
* <u>或许直接参数赋值的支持将计划在 {@code 0.4.3} 标记废弃并在 {@code 0.5} 删除</u> * 使用参数所进行取值的 token username 已被转移至 {@code --token} {@code --username} 参数<br>
* <s>但实际上这并不影响现在的使用选项赋值目前仍属于测试功能</s><br>
* <b>但请勿混用</b>这将使两个赋值出现混淆并<b>产生不可知的结果</b>
* *
* @see MornyCoeur#main * @see MornyCoeur#main
* @since 0.4.0.0 * @since 0.4.0.0
@ -74,6 +80,13 @@ public class ServerMain {
*/ */
public static void main (@Nonnull String[] args) { public static void main (@Nonnull String[] args) {
//#
//# 启动参数设置区块
//#
boolean versionEchoMode = false;
boolean welcomeEchoMode = false;
boolean showWelcome = true;
String key = null; String key = null;
String username = null; String username = null;
boolean outdatedBlock = false; boolean outdatedBlock = false;
@ -82,6 +95,8 @@ public class ServerMain {
long trustedChat = -1001541451710L; long trustedChat = -1001541451710L;
boolean autoCmdList = false; boolean autoCmdList = false;
boolean autoCmdRemove = false; boolean autoCmdRemove = false;
String api = null;
String api4File = null;
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
@ -92,11 +107,11 @@ public class ServerMain {
outdatedBlock = true; outdatedBlock = true;
continue; continue;
} }
case "--no-hello", "-hf" -> { case "--no-hello", "-hf", "--quiet", "-q" -> {
showWelcome = false; showWelcome = false;
continue; continue;
} }
case "--only-hello", "-o" -> { case "--only-hello", "-ho", "-o", "-hi" -> {
welcomeEchoMode = true; welcomeEchoMode = true;
continue; continue;
} }
@ -104,31 +119,37 @@ public class ServerMain {
versionEchoMode = true; versionEchoMode = true;
continue; continue;
} }
case "--token" -> { case "--token", "-t" -> {
i++; i++;
key = args[i]; key = args[i];
continue; continue;
} }
case "--username" -> { case "--username", "-u" -> {
i++; i++;
username = args[i]; username = args[i];
continue; continue;
} }
case "--master" -> { case "--master", "-mm" -> {
i++; i++;
master = Long.parseLong(args[i]); master = Long.parseLong(args[i]);
continue; continue;
} }
case "--trusted-chat" -> { case "--trusted-chat", "-trs" -> {
i++; i++;
trustedChat = Long.parseLong(args[i]); trustedChat = Long.parseLong(args[i]);
continue; continue;
} }
case "--trusted-reader-dinner" -> { //noinspection SpellCheckingInspection
case "--trusted-reader-dinner", "-trsd" -> {
i++; i++;
trustedReadersOfDinner.add(Long.parseLong(args[i])); trustedReadersOfDinner.add(Long.parseLong(args[i]));
continue; continue;
} }
case "--auto-cmd", "-cmd", "-c" -> {
autoCmdList = true;
autoCmdRemove = true;
continue;
}
case "--auto-cmd-list", "-ca" -> { case "--auto-cmd-list", "-ca" -> {
autoCmdList = true; autoCmdList = true;
continue; continue;
@ -137,6 +158,16 @@ public class ServerMain {
autoCmdRemove = true; autoCmdRemove = true;
continue; continue;
} }
case "--api", "-a" -> {
i++;
api = args[i];
continue;
}
case "--api-files", "files-api", "-af" -> {
i++;
api4File = args[i];
continue;
}
} }
} }
@ -145,18 +176,31 @@ public class ServerMain {
} }
String propToken = null;
String propTokenKey = null;
for (String iKey : new String[]{PROP_TOKEN_KEY, PROP_TOKEN_MORNY_KEY}) {
if (System.getenv(iKey) != null) {
propToken = System.getenv(iKey);
propTokenKey = iKey;
}
}
//#
//# 启动相关参数的检查和处理
//#
if (versionEchoMode) { if (versionEchoMode) {
logger.info(String.format(""" logger.info(String.format("""
Morny Cono Version Morny Cono Version
- version : - version :
%s %s %s
- md5hash : - md5hash :
%s %s
- co.time : - co.time :
%d %d
%s [UTC]""", %s [UTC]""",
MornySystem.VERSION, MornySystem.VERSION, MornySystem.CODENAME.toUpperCase(),
MornySystem.getJarMd5(), MornySystem.getJarMd5(),
GradleProjectConfigures.COMPILE_TIMESTAMP, GradleProjectConfigures.COMPILE_TIMESTAMP,
CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0) CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)
@ -168,11 +212,29 @@ public class ServerMain {
if (showWelcome) logger.info(MornyHello.MORNY_PREVIEW_IMAGE_ASCII); if (showWelcome) logger.info(MornyHello.MORNY_PREVIEW_IMAGE_ASCII);
if (welcomeEchoMode) return; if (welcomeEchoMode) return;
logger.info(String.format("""
ServerMain.java Loaded >>>
- version %s (%s)(%d)
- Morny %s""",
MornySystem.VERSION,
MornySystem.getJarMd5(), GradleProjectConfigures.COMPILE_TIMESTAMP,
MornySystem.CODENAME.toUpperCase()
));
//#
//# Coeur 参数检查和正式启动主程序
//#
if (propToken != null) {
key = propToken;
logger.info("Parameter <token> set by EnvVar $"+propTokenKey);
}
if (key == null) { if (key == null) {
logger.info("Parameter required has no value:\n --token."); logger.info("Parameter required has no value:\n --token.");
return; return;
} }
MornyCoeur.main( MornyCoeur.main(
api, api4File,
key, username, key, username,
master, trustedChat, trustedReadersOfDinner, master, trustedChat, trustedReadersOfDinner,
outdatedBlock?System.currentTimeMillis():0, outdatedBlock?System.currentTimeMillis():0,

View File

@ -0,0 +1,58 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.DeleteMessage;
import com.pengrad.telegrambot.request.GetChatMember;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static cc.sukazyo.cono.morny.Log.logger;
public class DirectMsgClear implements ISimpleCommand {
@Nonnull @Override public String getName () { return "r"; }
@Nullable @Override public String[] getAliases () { return new String[0]; }
@Override
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
logger.debug("Executing command /r");
if (event.message().replyToMessage() == null) return;
logger.trace("Message is a reply");
if (event.message().replyToMessage().from().id() != MornyCoeur.getUserid()) return;
logger.trace("Message is from me");
if (System.currentTimeMillis()/1000 - event.message().replyToMessage().date() > 48*60*60) return;
logger.trace("Message is not older than 48 hours");
final boolean isTrusted = MornyCoeur.trustedInstance().isTrusted(event.message().from().id());
if (
isTrusted || (
event.message().replyToMessage().replyToMessage() != null &&
event.message().replyToMessage().replyToMessage().from().id().equals(event.message().from().id())
)
) {
MornyCoeur.extra().exec(new DeleteMessage(
event.message().chat().id(), event.message().replyToMessage().messageId()
));
if (event.message().chat().type() == Chat.Type.Private || (
MornyCoeur.extra().exec(
new GetChatMember(event.message().chat().id(), event.message().from().id())
).chatMember().canDeleteMessages()
)) {
MornyCoeur.extra().exec(new DeleteMessage(
event.message().chat().id(), event.message().messageId()
));
}
} else logger.trace("User is not trusted");
}
}

View File

@ -18,7 +18,7 @@ import javax.annotation.Nullable;
*/ */
public class EventHack implements ITelegramCommand { public class EventHack implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/event_hack"; } @Nonnull @Override public String getName () { return "event_hack"; }
@Nullable @Override public String[] getAliases () { return null; } @Nullable @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return "[(user|group|any)]"; } @Nonnull @Override public String getParamRule () { return "[(user|group|any)]"; }
@Nonnull @Override public String getDescription () { return "输出 bot 下一个获取到的事件序列化数据"; } @Nonnull @Override public String getDescription () { return "输出 bot 下一个获取到的事件序列化数据"; }

View File

@ -15,7 +15,7 @@ import javax.annotation.Nullable;
public class GetUsernameAndId implements ITelegramCommand { public class GetUsernameAndId implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/user"; } @Nonnull @Override public String getName () { return "user"; }
@Nullable @Override public String[] getAliases () { return null; } @Nullable @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return "[userid]"; } @Nonnull @Override public String getParamRule () { return "[userid]"; }
@Nonnull @Override public String getDescription () { return "获取指定或回复的用户相关信息"; } @Nonnull @Override public String getDescription () { return "获取指定或回复的用户相关信息"; }

View File

@ -20,8 +20,11 @@ import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
*/ */
public class Ip186Query { public class Ip186Query {
public static final String CMD_IP = "ip";
public static final String CMD_WHOIS = "whois";
public static class Ip implements ITelegramCommand { public static class Ip implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/ip"; } @Nonnull @Override public String getName () { return CMD_IP; }
@Nullable @Override public String[] getAliases () { return new String[0]; } @Nullable @Override public String[] getAliases () { return new String[0]; }
@Nonnull @Override public String getParamRule () { return "[ip]"; } @Nonnull @Override public String getParamRule () { return "[ip]"; }
@Nonnull @Override public String getDescription () { return "通过 https://ip.186526.xyz 查询 ip 资料"; } @Nonnull @Override public String getDescription () { return "通过 https://ip.186526.xyz 查询 ip 资料"; }
@ -29,7 +32,7 @@ public class Ip186Query {
} }
public static class Whois implements ITelegramCommand { public static class Whois implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/whois"; } @Nonnull @Override public String getName () { return CMD_WHOIS; }
@Nullable @Override public String[] getAliases () { return new String[0]; } @Nullable @Override public String[] getAliases () { return new String[0]; }
@Nonnull @Override public String getParamRule () { return "[domain]"; } @Nonnull @Override public String getParamRule () { return "[domain]"; }
@Nonnull @Override public String getDescription () { return "通过 https://ip.186526.xyz 查询域名资料"; } @Nonnull @Override public String getDescription () { return "通过 https://ip.186526.xyz 查询域名资料"; }
@ -62,8 +65,8 @@ public class Ip186Query {
try { try {
IP186QueryResponse response = switch (command.getCommand()) { IP186QueryResponse response = switch (command.getCommand()) {
case "/ip" -> IP186QueryHandler.queryIp(arg); case CMD_IP -> IP186QueryHandler.queryIp(arg);
case "/whois" -> IP186QueryHandler.queryWhoisPretty(arg); case CMD_WHOIS -> IP186QueryHandler.queryWhoisPretty(arg);
default -> throw new IllegalArgumentException("Unknown 186-IP query method " + command.getCommand()); default -> throw new IllegalArgumentException("Unknown 186-IP query method " + command.getCommand());
}; };
MornyCoeur.extra().exec(new SendMessage( MornyCoeur.extra().exec(new SendMessage(

View File

@ -61,6 +61,7 @@ public class MornyCommands {
register( register(
new ON(), new ON(),
new Hello(), new Hello(),
new HelloOnStart(),
new GetUsernameAndId(), new GetUsernameAndId(),
new EventHack(), new EventHack(),
new Nbnhhsh(), new Nbnhhsh(),
@ -73,6 +74,11 @@ public class MornyCommands {
new Exit() new Exit()
); );
// 特殊的命令
register(
new DirectMsgClear()
);
// 统一注册这些奇怪的东西&.& // 统一注册这些奇怪的东西&.&
register( register(
new 喵呜.抱抱(), new 喵呜.抱抱(),
@ -153,7 +159,7 @@ public class MornyCommands {
/// ///
private static class ON implements ITelegramCommand { private static class ON implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/o"; } @Nonnull @Override public String getName () { return "o"; }
@Nullable @Nullable
@Override public String[] getAliases () { return null; } @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return ""; } @Nonnull @Override public String getParamRule () { return ""; }
@ -169,12 +175,13 @@ public class MornyCommands {
} }
private static class Hello implements ITelegramCommand { private static class Hello implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/hello"; } @Nonnull @Override public String getName () { return "hello"; }
@Nullable @Override public String[] getAliases () { return new String[]{"/hi"}; } @Nullable @Override public String[] getAliases () { return new String[]{"hi"}; }
@Nonnull @Override public String getParamRule () { return ""; } @Nonnull @Override public String getParamRule () { return ""; }
@Nonnull @Override public String getDescription () { return "打招呼"; } @Nonnull @Override public String getDescription () { return "打招呼"; }
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandHelloExec(event); } @Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandHelloExec(event); }
} }
private static class HelloOnStart implements ISimpleCommand { @Nonnull @Override public String getName () { return "start"; }@Nullable @Override public String[] getAliases () { return new String[0]; }@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { onCommandHelloExec(event); }}
private static void onCommandHelloExec (@Nonnull Update event) { private static void onCommandHelloExec (@Nonnull Update event) {
MornyCoeur.extra().exec(new SendSticker( MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(), event.message().chat().id(),
@ -184,7 +191,7 @@ public class MornyCommands {
} }
private static class Exit implements ITelegramCommand { private static class Exit implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/exit"; } @Nonnull @Override public String getName () { return "exit"; }
@Nullable @Override public String[] getAliases () { return new String[0]; } @Nullable @Override public String[] getAliases () { return new String[0]; }
@Nonnull @Override public String getParamRule () { return ""; } @Nonnull @Override public String getParamRule () { return ""; }
@Nonnull @Override public String getDescription () { return "关闭 Bot (仅可信成员)"; } @Nonnull @Override public String getDescription () { return "关闭 Bot (仅可信成员)"; }
@ -210,7 +217,7 @@ public class MornyCommands {
} }
private static class Version implements ITelegramCommand { private static class Version implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/version"; } @Nonnull @Override public String getName () { return "version"; }
@Nullable @Override public String[] getAliases () { return null; } @Nullable @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return ""; } @Nonnull @Override public String getParamRule () { return ""; }
@Nonnull @Override public String getDescription () { return "检查 Bot 版本信息"; } @Nonnull @Override public String getDescription () { return "检查 Bot 版本信息"; }
@ -222,12 +229,14 @@ public class MornyCommands {
String.format( String.format(
""" """
version: version:
- Morny <code>%s</code>
- <code>%s</code> - <code>%s</code>
core md5_hash: core md5_hash:
- <code>%s</code> - <code>%s</code>
compile timestamp: compile timestamp:
- <code>%d</code> - <code>%d</code>
- <code>%s [UTC]</code>""", - <code>%s [UTC]</code>""",
escapeHtml(MornySystem.CODENAME.toUpperCase()),
escapeHtml(MornySystem.VERSION), escapeHtml(MornySystem.VERSION),
escapeHtml(MornySystem.getJarMd5()), escapeHtml(MornySystem.getJarMd5()),
GradleProjectConfigures.COMPILE_TIMESTAMP, GradleProjectConfigures.COMPILE_TIMESTAMP,
@ -237,7 +246,7 @@ public class MornyCommands {
} }
private static class MornyRuntime implements ITelegramCommand { private static class MornyRuntime implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/runtime"; } @Nonnull @Override public String getName () { return "runtime"; }
@Nullable @Override public String[] getAliases () { return null; } @Nullable @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return ""; } @Nonnull @Override public String getParamRule () { return ""; }
@Nonnull @Override public String getDescription () { return "获取 Bot 运行时信息(包括版本号)"; } @Nonnull @Override public String getDescription () { return "获取 Bot 运行时信息(包括版本号)"; }
@ -260,14 +269,14 @@ public class MornyCommands {
- <code>%s</code> - <code>%s</code>
- <code>%s</code> - <code>%s</code>
- <code>%s</code> - <code>%s</code>
- <code>%d</code> cores
java runtime: java runtime:
- <code>%s</code> - <code>%s</code>
- <code>%s</code> - <code>%s</code>
vm memory: vm memory:
- <code>%d</code> / <code>%d</code> MB - <code>%d</code> / <code>%d</code> MB
- <code>%d</code> cores
coeur version: coeur version:
- <code>%s</code> - <code>%s</code> (<code>%s</code>)
- <code>%s</code> - <code>%s</code>
- <code>%s [UTC]</code> - <code>%s [UTC]</code>
- [<code>%d</code>] - [<code>%d</code>]
@ -278,17 +287,18 @@ public class MornyCommands {
- [<code>%d</code>]""", - [<code>%d</code>]""",
// system // system
escapeHtml(hostname), escapeHtml(hostname),
escapeHtml(System.getProperty("os.name")), escapeHtml(String.format("%s (%s)", System.getProperty("os.name"), System.getProperty("os.arch"))),
escapeHtml(System.getProperty("os.version")), escapeHtml(System.getProperty("os.version")),
Runtime.getRuntime().availableProcessors(),
// java // java
escapeHtml(System.getProperty("java.vm.name")), escapeHtml(System.getProperty("java.vm.vendor")+"."+System.getProperty("java.vm.name")),
escapeHtml(System.getProperty("java.version")), escapeHtml(System.getProperty("java.vm.version")),
// memory // memory
Runtime.getRuntime().totalMemory() / 1024 / 1024, Runtime.getRuntime().totalMemory() / 1024 / 1024,
Runtime.getRuntime().maxMemory() / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024,
Runtime.getRuntime().availableProcessors(),
// version // version
escapeHtml(MornySystem.VERSION), escapeHtml(MornySystem.VERSION),
escapeHtml(MornySystem.CODENAME),
escapeHtml(MornySystem.getJarMd5()), escapeHtml(MornySystem.getJarMd5()),
escapeHtml(formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)), escapeHtml(formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)),
GradleProjectConfigures.COMPILE_TIMESTAMP, GradleProjectConfigures.COMPILE_TIMESTAMP,
@ -302,7 +312,7 @@ public class MornyCommands {
} }
private static class Jrrp implements ITelegramCommand { private static class Jrrp implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/jrrp"; } @Nonnull @Override public String getName () { return "jrrp"; }
@Nullable @Override public String[] getAliases () { return null; } @Nullable @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return ""; } @Nonnull @Override public String getParamRule () { return ""; }
@Nonnull @Override public String getDescription () { return "获取 (假的) jrrp"; } @Nonnull @Override public String getDescription () { return "获取 (假的) jrrp"; }
@ -322,7 +332,7 @@ public class MornyCommands {
} }
private static class SaveData implements ITelegramCommand { private static class SaveData implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/save"; } @Nonnull @Override public String getName () { return "save"; }
@Nullable @Override public String[] getAliases () { return null; } @Nullable @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return ""; } @Nonnull @Override public String getParamRule () { return ""; }
@Nonnull @Override public String getDescription () { return "保存缓存数据到文件(仅可信成员)"; } @Nonnull @Override public String getDescription () { return "保存缓存数据到文件(仅可信成员)"; }

View File

@ -16,7 +16,7 @@ import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
public class Nbnhhsh implements ITelegramCommand { public class Nbnhhsh implements ITelegramCommand {
@Nonnull @Override public String getName () { return "/nbnhhsh"; } @Nonnull @Override public String getName () { return "nbnhhsh"; }
@Nullable @Override public String[] getAliases () { return null; } @Nullable @Override public String[] getAliases () { return null; }
@Nonnull @Override public String getParamRule () { return "[text]"; } @Nonnull @Override public String getParamRule () { return "[text]"; }
@Nonnull @Override public String getDescription () { return "检索文本内 nbnhhsh 词条"; } @Nonnull @Override public String getDescription () { return "检索文本内 nbnhhsh 词条"; }

View File

@ -13,7 +13,7 @@ import javax.annotation.Nullable;
public class 喵呜 { public class 喵呜 {
public static class 抱抱 implements ISimpleCommand { public static class 抱抱 implements ISimpleCommand {
@Nonnull @Override public String getName () { return "/抱抱"; } @Nonnull @Override public String getName () { return "抱抱"; }
@Nullable @Override public String[] getAliases () { return new String[0]; } @Nullable @Override public String[] getAliases () { return new String[0]; }
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { @Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
MornyCoeur.extra().exec(new SendMessage( MornyCoeur.extra().exec(new SendMessage(
@ -24,7 +24,7 @@ public class 喵呜 {
} }
public static class 揉揉 implements ISimpleCommand { public static class 揉揉 implements ISimpleCommand {
@Nonnull @Override public String getName () { return "/揉揉"; } @Nonnull @Override public String getName () { return "揉揉"; }
@Nullable @Override public String[] getAliases () { return new String[0]; } @Nullable @Override public String[] getAliases () { return new String[0]; }
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { @Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
MornyCoeur.extra().exec(new SendMessage( MornyCoeur.extra().exec(new SendMessage(
@ -35,7 +35,7 @@ public class 喵呜 {
} }
public static class 蹭蹭 implements ISimpleCommand { public static class 蹭蹭 implements ISimpleCommand {
@Nonnull @Override public String getName () { return "/蹭蹭"; } @Nonnull @Override public String getName () { return "蹭蹭"; }
@Nullable @Override public String[] getAliases () { return new String[0]; } @Nullable @Override public String[] getAliases () { return new String[0]; }
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { @Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
MornyCoeur.extra().exec(new SendMessage( MornyCoeur.extra().exec(new SendMessage(
@ -46,7 +46,7 @@ public class 喵呜 {
} }
public static class 贴贴 implements ISimpleCommand { public static class 贴贴 implements ISimpleCommand {
@Nonnull @Override public String getName () { return "/贴贴"; } @Nonnull @Override public String getName () { return "贴贴"; }
@Nullable @Override public String[] getAliases () { return new String[0]; } @Nullable @Override public String[] getAliases () { return new String[0]; }
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { @Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
MornyCoeur.extra().exec(new SendMessage( MornyCoeur.extra().exec(new SendMessage(

View File

@ -13,7 +13,7 @@ import java.util.concurrent.ThreadLocalRandom;
public class 私わね implements ISimpleCommand { public class 私わね implements ISimpleCommand {
@Nonnull @Nonnull
@Override public String getName () { return "/me"; } @Override public String getName () { return "me"; }
@Nullable @Nullable
@Override public String[] getAliases () { return null; } @Override public String[] getAliases () { return null; }

View File

@ -11,17 +11,21 @@ public class EventListeners {
public static final OnInlineQueries INLINE_QUERY = new OnInlineQueries(); public static final OnInlineQueries INLINE_QUERY = new OnInlineQueries();
public static final OnCallMe CALL_ME = new OnCallMe(); public static final OnCallMe CALL_ME = new OnCallMe();
public static final OnEventHackHandle EVENT_HACK_HANDLE = new OnEventHackHandle(); public static final OnEventHackHandle EVENT_HACK_HANDLE = new OnEventHackHandle();
public static final OnKuohuanhuanNeedSleep KUOHUANHUAN_NEED_SLEEP = new OnKuohuanhuanNeedSleep(); @SuppressWarnings("unused") static final OnKuohuanhuanNeedSleep KUOHUANHUAN_NEED_SLEEP = new OnKuohuanhuanNeedSleep();
public static final OnUserRandoms USER_RANDOMS = new OnUserRandoms();
public static final OnCallMsgSend CALL_MSG_SEND = new OnCallMsgSend();
public static void registerAllListeners () { public static void registerAllListeners () {
EventListenerManager.addListener( EventListenerManager.addListener(
ACTIVITY_RECORDER, ACTIVITY_RECORDER,
UPDATE_TIMESTAMP_OFFSET_LOCK, UPDATE_TIMESTAMP_OFFSET_LOCK,
KUOHUANHUAN_NEED_SLEEP, // KUOHUANHUAN_NEED_SLEEP,
COMMANDS_LISTENER, COMMANDS_LISTENER,
USER_RANDOMS,
USER_SLASH_ACTION, USER_SLASH_ACTION,
INLINE_QUERY, INLINE_QUERY,
CALL_ME, CALL_ME,
CALL_MSG_SEND,
EVENT_HACK_HANDLE EVENT_HACK_HANDLE
); );
} }

View File

@ -1,7 +1,7 @@
package cc.sukazyo.cono.morny.bot.event; package cc.sukazyo.cono.morny.bot.event;
import cc.sukazyo.cono.morny.bot.api.EventListener; import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.data.tracker.TrackerDataManager; import cc.sukazyo.cono.morny.daemon.TrackerDataManager;
import com.pengrad.telegrambot.model.Chat; import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.Update; import com.pengrad.telegrambot.model.Update;

View File

@ -0,0 +1,220 @@
package cc.sukazyo.cono.morny.bot.event;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.MessageEntity;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.GetChat;
import com.pengrad.telegrambot.request.SendMessage;
import com.pengrad.telegrambot.request.SendSticker;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import com.pengrad.telegrambot.response.GetChatResponse;
import com.pengrad.telegrambot.response.SendResponse;
import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
public class OnCallMsgSend extends EventListener {
private static final Pattern REGEX_MSG_SENDREQ_DATA_HEAD = Pattern.compile("^\\*msg([\\d-]+)(\\*\\S+)?\\n([\\s\\S]+)$");
private record MessageToSend (
String message,
MessageEntity[] entities,
ParseMode parseMode,
long targetId
) { }
@Override
public boolean onMessage(Update update) {
// 执行体检查
if (update.message().chat().type() != Chat.Type.Private) return false;
if (update.message().text() == null) return false;
if (!update.message().text().startsWith("*msg")) return false;
// 权限检查
if (!MornyCoeur.trustedInstance().isTrusted(update.message().from().id())) {
MornyCoeur.extra().exec(new SendSticker(
update.message().chat().id(),
TelegramStickers.ID_403
).replyToMessageId(update.message().messageId()));
return true;
}
Message msgsendReqRaw; // 用户书写的发送请求原文
MessageToSend msgsendReqBody; // 解析后的发送请求实例
// *msgsend 发送标识
// 处理发送要求
if (update.message().text().equals("*msgsend")) {
// 发送体处理
if (update.message().replyToMessage() == null) return answer404(update);
msgsendReqBody = parseRequest(update.message().replyToMessage());
if (msgsendReqBody == null) return answer404(update);
// 执行发送任务
SendResponse sendResponse = MornyCoeur.getAccount().execute(parseMessageToSend(msgsendReqBody));
if (!sendResponse.isOk()) { // 发送失败
MornyCoeur.extra().exec(new SendMessage(
update.message().chat().id(),
String.format("""
<b><u>%d</u> FAILED</b>
<code>%s</code>""",
sendResponse.errorCode(),
sendResponse.description()
)
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
} else { // 发送成功信号
MornyCoeur.extra().exec(new SendSticker(
update.message().chat().id(),
TelegramStickers.ID_SENT
).replyToMessageId(update.message().messageId()));
}
return true;
// 发送完成/失败 - 事件结束
}
// *msg 检查标识
if (update.message().text().equals("*msg")) { // 处理对曾经的原文的检查
if (update.message().replyToMessage() == null) {
return answer404(update);
}
msgsendReqRaw = update.message().replyToMessage();
} else if (update.message().text().startsWith("*msg")) { // 对接受到的原文进行检查
msgsendReqRaw = update.message();
} else {
return answer404(update); // 未定义的动作
}
// 对发送请求的用户原文进行解析
msgsendReqBody = parseRequest(msgsendReqRaw);
if (msgsendReqBody == null) {
return answer404(update);
}
// 输出发送目标信息
GetChatResponse targetChatReq = MornyCoeur.getAccount().execute(new GetChat(msgsendReqBody.targetId()));
if (!targetChatReq.isOk()) {
MornyCoeur.extra().exec(new SendMessage(
update.message().chat().id(),
String.format("""
<b><u>%d</u> FAILED</b>
<code>%s</code>""",
targetChatReq.errorCode(),
targetChatReq.description()
)
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
} else {
MornyCoeur.extra().exec(new SendMessage(
update.message().chat().id(),
targetChatReq.chat().type() == Chat.Type.Private ? (
String.format("""
<i><u>%d</u>@%s</i>
🔒 <b>%s</b> %s""",
msgsendReqBody.targetId(),
escapeHtml(targetChatReq.chat().type().name()),
escapeHtml(targetChatReq.chat().firstName()+(targetChatReq.chat().lastName()==null?"":" "+targetChatReq.chat().lastName())),
targetChatReq.chat().username()==null?
String.format("<a href='tg://user?id=%d'>@@</a>", targetChatReq.chat().id()):
(escapeHtml("@"+targetChatReq.chat().username()))
)
) : (
String.format("""
<i><u>%d</u>@%s</i>:::
%s <b>%s</b>%s""",
msgsendReqBody.targetId(),
escapeHtml(targetChatReq.chat().type().name()),
switch (targetChatReq.chat().type()) {
case group -> "💭";
case channel -> "📢";
case supergroup -> "💬";
default -> "⭕️";
},
escapeHtml(targetChatReq.chat().title()),
targetChatReq.chat().username() != null?String.format(
" @%s", escapeHtml(targetChatReq.chat().username())
):""
)
)
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
}
// 发送文本测试
SendResponse testSendResp = MornyCoeur.getAccount().execute(
parseMessageToSend(msgsendReqBody, update.message().chat().id()).replyToMessageId(update.message().messageId())
);
if (!testSendResp.isOk()) {
MornyCoeur.extra().exec(new SendMessage(
update.message().chat().id(),
String.format("""
<b><u>%d</u> FAILED</b>
<code>%s</code>""",
testSendResp.errorCode(),
testSendResp.description()
)
).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML));
}
return true;
}
@Nullable
private static MessageToSend parseRequest (@Nonnull Message requestBody) {
final Matcher matcher = REGEX_MSG_SENDREQ_DATA_HEAD.matcher(requestBody.text());
if (matcher.matches()) {
long targetId = Long.parseLong(matcher.group(1));
ParseMode parseMode = matcher.group(2) == null ? null : switch (matcher.group(2)) {
case "*markdown", "*md", "*m↓" -> ParseMode.MarkdownV2;
case "*md1" -> ParseMode.Markdown;
case "*html" -> ParseMode.HTML;
default -> null;
};
final int offset = "*msg".length()+matcher.group(1).length()+(matcher.group(2)==null?0:matcher.group(2).length())+1;
final ArrayList<MessageEntity> entities = new ArrayList<>();
if (requestBody.entities() != null) for (MessageEntity entity : requestBody.entities()) {
final MessageEntity parsed = new MessageEntity(entity.type(), entity.offset() - offset, entity.length());
if (entity.url() != null) parsed.url(entity.url());
if (entity.user() != null) parsed.user(entity.user());
if (entity.language() != null) parsed.language(entity.language());
entities.add(parsed);
}
return new MessageToSend(matcher.group(3), entities.toArray(MessageEntity[]::new), parseMode, targetId);
}
return null;
}
@Nonnull
private static SendMessage parseMessageToSend (@Nonnull MessageToSend body) {
return parseMessageToSend(body, body.targetId);
}
@Nonnull
private static SendMessage parseMessageToSend (@Nonnull MessageToSend body, long targetId) {
SendMessage sendingBody = new SendMessage(targetId, body.message);
if (body.entities != null) sendingBody.entities(body.entities);
if (body.parseMode != null) sendingBody.parseMode(body.parseMode);
return sendingBody;
}
private static boolean answer404 (@Nonnull Update update) {
MornyCoeur.extra().exec(new SendSticker(
update.message().chat().id(),
TelegramStickers.ID_404
).replyToMessageId(update.message().messageId()));
return true;
}
}

View File

@ -12,10 +12,10 @@ public class OnTelegramCommand extends EventListener {
@Override @Override
public boolean onMessage (@Nonnull Update event) { public boolean onMessage (@Nonnull Update event) {
if (event.message().text() == null) { if (event.message().text() == null || !event.message().text().startsWith("/")) {
return false; // 检测到无消息文本忽略掉命令处理 return false; // 检测到(命令格式)文本忽略掉命令处理
} }
final InputCommand command = new InputCommand(event.message().text()); final InputCommand command = new InputCommand(event.message().text().substring(1));
if (command.getTarget() != null && !MornyCoeur.getUsername().equals(command.getTarget())) { if (command.getTarget() != null && !MornyCoeur.getUsername().equals(command.getTarget())) {
return true; // 检测到命令并非针对 morny退出整个事件处理链 return true; // 检测到命令并非针对 morny退出整个事件处理链
} }

View File

@ -0,0 +1,52 @@
package cc.sukazyo.cono.morny.bot.event;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.untitled.util.command.CommonCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.SendMessage;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class OnUserRandoms extends EventListener {
private static final Pattern USER_OR_CN_QUERY = Pattern.compile("(.+)还是(.+)");
private static final Pattern USER_OR_EN_QUERY = Pattern.compile("(.+)or(.+)");
@Override
public boolean onMessage (@NotNull Update update) {
if (update.message().text() == null) return false;
if (!update.message().text().startsWith("/")) return false;
final String[] preProcess = CommonCommand.format(update.message().text());
if (preProcess.length > 1) return false;
final String query = preProcess[0];
// ----- START CODE BLOCK COMMENT -----
// 这里实现思路和代码优化有至少一半是 copilot IDEA 提供的
// 实现思路都可以从人类手里抢一半贡献太恐怖了aba
String result = null;
final Matcher matcher;
if (query.contains("还是")) {
matcher = USER_OR_CN_QUERY.matcher(query);
} else {
matcher = USER_OR_EN_QUERY.matcher(query);
}
if (matcher.find()) {
result = ThreadLocalRandom.current().nextBoolean() ? matcher.group(1) : matcher.group(2);
}
// ----- STOP CODE BLOCK COMMENT -----
if (result == null) return false;
MornyCoeur.extra().exec(new SendMessage(
update.message().chat().id(), result
).replyToMessageId(update.message().messageId()));
return true;
}
}

View File

@ -3,7 +3,6 @@ package cc.sukazyo.cono.morny.bot.event;
import cc.sukazyo.cono.morny.MornyCoeur; import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.bot.api.EventListener; import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.util.tgapi.TGToStringFromMessage; import cc.sukazyo.cono.morny.util.tgapi.TGToStringFromMessage;
import cc.sukazyo.untitled.telegram.api.formatting.TGToString;
import cc.sukazyo.untitled.util.command.CommonCommand; import cc.sukazyo.untitled.util.command.CommonCommand;
import cc.sukazyo.untitled.util.string.StringArrays; import cc.sukazyo.untitled.util.string.StringArrays;
@ -14,7 +13,6 @@ import com.pengrad.telegrambot.request.SendMessage;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import static cc.sukazyo.cono.morny.Log.logger;
import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml; import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
public class OnUserSlashAction extends EventListener { public class OnUserSlashAction extends EventListener {
@ -24,20 +22,21 @@ public class OnUserSlashAction extends EventListener {
final String text = event.message().text(); final String text = event.message().text();
if (text == null) return false; if (text == null) return false;
if (text.startsWith("/")) { if (text.startsWith("/"))
{
/// Due to @Lapis_Apple, we stopped slash action function at .DP7 groups. /// Due to @Lapis_Apple, we stopped slash action function at .DP7 groups.
/// It may be enabled after some updates when the function will not be conflicted to other bots. /// It may be enabled after some updates when the function will not be conflicted to other bots.
// if (event.message().chat().id() == ) return false; // if (event.message().chat().id() == ) return false;
if (event.message().chat().title() != null && event.message().chat().title().contains(".DP7")) { //{ if (event.message().chat().title() != null && event.message().chat().title().contains(".DP7")) {
logger.info(String.format(""" // logger.info(String.format("""
Chat slash action ignored due to the following keyword. // Chat slash action ignored due to the following keyword.
- %s // - %s
- ".DP7\"""", // - ".DP7\"""",
TGToString.as(event.message().chat()).toStringFullNameId() // TGToString.as(event.message().chat()).toStringFullNameId()
)); // ));
return false; // return false;
} // }
final String[] action = CommonCommand.format(text); final String[] action = CommonCommand.format(text);
action[0] = action[0].substring(1); action[0] = action[0].substring(1);

View File

@ -0,0 +1,50 @@
package cc.sukazyo.cono.morny.daemon;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import com.pengrad.telegrambot.request.PinChatMessage;
import com.pengrad.telegrambot.request.SendSticker;
import com.pengrad.telegrambot.response.SendResponse;
import static cc.sukazyo.cono.morny.Log.logger;
public class MedicationTimer extends Thread {
public static final long NOTIFY_RECEIVE_CHAT = 5028252995L;
MedicationTimer () {
super("TIMER_Medication");
}
@Override
public void run () {
logger.info("MedicationTimer started");
while (!interrupted()) {
try {
waitToNextRoutine();
sendNotification();
} catch (InterruptedException e) {
interrupt();
logger.info("MedicationTimer was interrupted, will be exit now");
} catch (Exception e) {
logger.error("Unexpected error occurred");
e.printStackTrace(System.out);
}
}
logger.info("MedicationTimer stopped");
}
private static void sendNotification () {
SendResponse m = MornyCoeur.extra().exec(new SendSticker(NOTIFY_RECEIVE_CHAT, TelegramStickers.ID_PROGYNOVA));
if (m.isOk()) MornyCoeur.extra().exec(new PinChatMessage(NOTIFY_RECEIVE_CHAT, m.message().messageId()));
}
private static long calcNextRoutineTimestamp () {
return ((System.currentTimeMillis()+8*60*60*1000) / (12*60*60*1000) + 1) * 12*60*60*1000 - 8*60*60*1000;
}
private void waitToNextRoutine () throws InterruptedException {
sleep(calcNextRoutineTimestamp() - System.currentTimeMillis());
}
}

View File

@ -0,0 +1,31 @@
package cc.sukazyo.cono.morny.daemon;
import static cc.sukazyo.cono.morny.Log.logger;
public class MornyDaemons {
static MedicationTimer medicationTimerInstance;
public static void start () {
logger.info("ALL Morny Daemons starting...");
TrackerDataManager.init();
medicationTimerInstance = new MedicationTimer();
medicationTimerInstance.start();
logger.info("Morny Daemons started.");
}
public static void stop () {
logger.info("ALL Morny Daemons stopping...");
TrackerDataManager.DAEMON.interrupt();
medicationTimerInstance.interrupt();
TrackerDataManager.trackingLock.lock();
try { medicationTimerInstance.join(); } catch (InterruptedException e) { e.printStackTrace(System.out); }
logger.info("ALL Morny Daemons STOPPED.");
}
}

View File

@ -1,4 +1,4 @@
package cc.sukazyo.cono.morny.data.tracker; package cc.sukazyo.cono.morny.daemon;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -27,6 +27,7 @@ public class TrackerDataManager {
@Override @Override
public void run () { public void run () {
trackingLock.lock(); trackingLock.lock();
logger.info("Tracker started.");
long lastWaitTimestamp = System.currentTimeMillis(); long lastWaitTimestamp = System.currentTimeMillis();
boolean postProcess = false; boolean postProcess = false;
do { do {
@ -48,6 +49,7 @@ public class TrackerDataManager {
else logger.info("nothing to do yet"); else logger.info("nothing to do yet");
} while (!postProcess); } while (!postProcess);
trackingLock.unlock(); trackingLock.unlock();
logger.info("Tracker exited.");
} }
} }

View File

@ -14,5 +14,6 @@ public class TelegramStickers {
public static final String ID_WAITING = "CAACAgEAAx0CSQh32gABA-8DYbh7W2VhJ490ucfZMUMrgMR2FW4AAm4nAAJ4_MYFjx6zpxJPWsQjBA"; public static final String ID_WAITING = "CAACAgEAAx0CSQh32gABA-8DYbh7W2VhJ490ucfZMUMrgMR2FW4AAm4nAAJ4_MYFjx6zpxJPWsQjBA";
public static final String ID_SENT = "CAACAgEAAx0CSQh32gABA--zYbiyU_wOijEitp-0tSl_k7W6l3gAAgMmAAJ4_MYF4GrompjXPx4jBA"; public static final String ID_SENT = "CAACAgEAAx0CSQh32gABA--zYbiyU_wOijEitp-0tSl_k7W6l3gAAgMmAAJ4_MYF4GrompjXPx4jBA";
public static final String ID_SAVED = "CAACAgEAAx0CSQh32gABBExuYdB_G0srfhQldRWkBYxWzCOv4-IAApooAAJ4_MYFcjuNZszfQcQjBA"; public static final String ID_SAVED = "CAACAgEAAx0CSQh32gABBExuYdB_G0srfhQldRWkBYxWzCOv4-IAApooAAJ4_MYFcjuNZszfQcQjBA";
public static final String ID_PROGYNOVA = "CAACAgUAAxkBAAICm2KEuL7UQqNP7vSPCg2DHJIND6UsAAKLAwACH4WSBszIo722aQ3jJAQ";
} }

View File

@ -1,11 +1,41 @@
package cc.sukazyo.cono.morny.util; package cc.sukazyo.cono.morny.util;
import com.pengrad.telegrambot.model.User; import com.pengrad.telegrambot.model.User;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml; import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
public class TelegramUserInformation { public class TelegramUserInformation {
public static final String DC_QUERY_SOURCE_SITE = "https://t.me/";
public static final Pattern DC_QUERY_PROCESSOR_REGEX = Pattern.compile("(cdn[1-9]).tele(sco.pe|gram-cdn.org)");
private static final OkHttpClient httpClient = new OkHttpClient();
@Nullable
public static String getDataCenterFromUsername (String username) {
final Request request = new Request.Builder().url(DC_QUERY_SOURCE_SITE + username).build();
try (Response response = httpClient.newCall(request).execute()) {
final ResponseBody body = response.body();
if (body == null) return "empty upstream response";
final Matcher matcher = DC_QUERY_PROCESSOR_REGEX.matcher(body.string());
if (matcher.find()) {
return matcher.group(1);
}
} catch (IOException e) {
return e.getMessage();
}
return null;
}
public static String informationOutputHTML (User user) { public static String informationOutputHTML (User user) {
final StringBuilder userInformation = new StringBuilder(); final StringBuilder userInformation = new StringBuilder();
@ -16,7 +46,7 @@ public class TelegramUserInformation {
user.id() user.id()
)); ));
if (user.username() == null) { if (user.username() == null) {
userInformation.append("\nusername : <u>null</u>"); userInformation.append("\nusername : <u>null</u>\ndatacenter : <u>null</u>");
} else { } else {
userInformation.append(String.format( userInformation.append(String.format(
""" """
@ -25,29 +55,19 @@ public class TelegramUserInformation {
- <code>%s</code>""", - <code>%s</code>""",
escapeHtml(user.username()) escapeHtml(user.username())
)); ));
// 依赖 username datacenter 查询
final String dataCenter = getDataCenterFromUsername(user.username());
if (dataCenter == null) { userInformation.append("\ndatacenter : <u>null</u>"); }
else { userInformation.append(String.format("\ndatacenter : <code>%s</code>", escapeHtml(dataCenter))); }
} }
if (user.firstName() == null) { userInformation.append(String.format(
userInformation.append("\nfirstname : <u>null</u>"); """
} else {
userInformation.append(String.format(
"""
firstname : display name :
- <code>%s</code>""", - <code>%s</code>%s""",
escapeHtml(user.firstName()) escapeHtml(user.firstName()),
)); user.lastName()==null ? "" : String.format("\n- <code>%s</code>", escapeHtml(user.lastName()))
} ));
if (user.lastName() == null) {
userInformation.append("\nlastname : <u>null</u>");
} else {
userInformation.append(String.format(
"""
lastname :
- <code>%s</code>""",
escapeHtml(user.lastName())
));
}
if (user.languageCode() != null) { if (user.languageCode() != null) {
userInformation.append(String.format( userInformation.append(String.format(
""" """