[[[release 0.8.0.11*putian]]]

## 📇功能

- 关闭了 tracker 功能(以及现在 morny 确实的没有任何文件 IO 了)
- 添加 /encrypt 命令可以对文本使用加密算法处理
  - 支持文本,图片,文件,但不支援复数个图片/文件,同时支援的大小受到 API 限制
  - 支援 base64(enc/dec,url-format) md5, sha1/256/512
  - md5 以及 sha1/256/512 支援 uppercase 选项
- 添加内联查询 twitter 分享链接 format 为 vxtwitter 链接的功能
- 添加内联查询 bilibili 分享链接或av/bv号 format 为 av/bv 视频链接的功能
  - 目前仅支援 bilibili av/bv 视频链接不支援 b23 分享链接
- 添加 /info 命令
  - 目前仅支援 stickers (以及stickers.ID) 选项输出 morny 使用的贴纸
- 添加了可以输出一个之前加进去但是没用到的贴纸的 /install 命令

## 🔌系统接口

- 将所有之前在 sukazyo.cc:untitled-* 的工具类重新放回了 morny.utils 下
  - CommonCommand 名称改为了 UniversalCommand
  - 也将之前的 TelegramUserInformation 和 TGToStringMessage 移动到了 utils.tgapi 的相应位置
  - stringsConnect 添加进了 CommonConvert 当中
- 删除了 EncryptUtils,与之代替的是 CommonEncrypt
  - 之前放在 EncryptUtils 的 byte[] 转 hex string 的工具转移到了新的 CommonConvert 当中
  - encryptByXXX 的命名改为了 hashXXX 的命名(虽然由于目前都是散列工具)
  - CommonEncrypt 也添加了可以用于规范化的 ENC_STD_CHARSET 字段,以及 sha1/256/512 一系列工具方法
- CommonFormatUtils 也跟随上文,类名改为了 CommonFormat(内容没有变)
- 为这三个 utils.CommonXXX 都添加了 Test 类

## 🔩技术修改/typo

- CommonFormat.formatDate 中定义日期格式的字符串独立为了一个字段
- CommonConvert byte -> hex-char 的实现方式改为了 Integer.toHexString
This commit is contained in:
A.C.Sukazyo Eyre 2022-11-02 16:33:44 +08:00
commit 3661cb1264
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
60 changed files with 1392 additions and 161 deletions

3
.gitignore vendored
View File

@ -4,7 +4,8 @@
.vscode/
.gradle/
.settings/
/src/test/*
/src/test/java/test/*
/src/test/resources/test/*
#build
/build/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "_book"]
path = _book
url = https://storage.sukazyo.cc/Eyre_S/morny-book.git

View File

@ -2,7 +2,7 @@
[tg-account]: https://t.me/morny_cono_annie_bot
[issues]: https://github.com/Eyre-S/Coeur-Morny-Cono/issues
[todo]: https://github.com/users/Eyre-S/projects/1
[artifact]: https://mvn.sukazyo.cc/main/cc/sukazyo/morny-coeur
[artifact]: https://mvn.sukazyo.cc/#/releases/cc/sukazyo/morny-coeur
[tg4j]: https://github.com/pengrad/java-telegram-bot-api
[spotbugs]: https://spotbugs.github.io/

1
_book Submodule

@ -0,0 +1 @@
Subproject commit 3072bcee8e498e87ecdd36958185ad423e80bcf3

View File

@ -17,24 +17,16 @@ repositories {
maven { name '-ws'; url 'https://mvn.sukazyo.cc/releases' }
}
String untitled (String lib, String upd = null) {
int majorCode = Integer.parseInt(project.libUntitledVersionMajor)
return "cc.sukazyo.untitled:$lib:[$majorCode${upd==null?"":".$upd"}, ${majorCode+1}["
}
dependencies {
compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${libSpotbugsVersion}"
implementation untitled("util-command-parser","1.0")
implementation untitled("util-string-commons", "1.0")
implementation untitled("util-telegram-api", "2.1")
implementation untitled("util-telegram-api-formatter", "3.3")
implementation untitled("util-telegram-commons", "1.0")
api "cc.sukazyo:messiva:${libMessivaVersion}"
implementation "com.github.pengrad:java-telegram-bot-api:${libJavaTelegramBotApiVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${libJunitVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-params:${libJunitVersion}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${libJunitVersion}"
}

View File

@ -1,15 +1,13 @@
## Core
VERSION = 0.7.2.1
VERSION = 0.8.0.11
CODENAME = fuzhou
CODENAME = putian
# dependencies
libSpotbugsVersion = 4.7.2
libUntitledVersionMajor = 1
libMessivaVersion = 0.1.0.1
libJavaTelegramBotApiVersion = 5.6.0

View File

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

View File

@ -6,7 +6,7 @@ import cc.sukazyo.cono.morny.bot.event.EventListeners;
import cc.sukazyo.cono.morny.bot.query.MornyQueries;
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.cono.morny.util.tgapi.ExtraAction;
import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.impl.FileApi;
import com.pengrad.telegrambot.model.User;
@ -59,7 +59,7 @@ public class MornyCoeur {
* {@link cc.sukazyo.cono.morny.bot.event.OnUpdateTimestampOffsetLock}
* 会根据这里定义的时间戳取消掉比此时间更早的事件链
*/
public long latestEventTimestamp;
public final long latestEventTimestamp;
/**
* morny 主程序启动时间<br>
* 用于统计数据

View File

@ -1,6 +1,6 @@
package cc.sukazyo.cono.morny;
import cc.sukazyo.cono.morny.util.CommonFormatUtils;
import cc.sukazyo.cono.morny.util.CommonFormat;
import javax.annotation.Nonnull;
@ -205,7 +205,7 @@ public class ServerMain {
MornySystem.VERSION, MornySystem.CODENAME.toUpperCase(),
MornySystem.getJarMd5(),
GradleProjectConfigures.COMPILE_TIMESTAMP,
CommonFormatUtils.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)
CommonFormat.formatDate(GradleProjectConfigures.COMPILE_TIMESTAMP, 0)
));
return;

View File

@ -1,10 +1,9 @@
package cc.sukazyo.cono.morny.bot.api;
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException;
import com.google.gson.GsonBuilder;
import com.pengrad.telegrambot.model.Update;
import cc.sukazyo.untitled.telegram.api.event.EventRuntimeException;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Arrays;

View File

@ -1,7 +1,7 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.DeleteMessage;

View File

@ -0,0 +1,205 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import cc.sukazyo.cono.morny.util.CommonConvert;
import cc.sukazyo.cono.morny.util.CommonEncrypt;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape;
import com.pengrad.telegrambot.model.PhotoSize;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.GetFile;
import com.pengrad.telegrambot.request.SendDocument;
import com.pengrad.telegrambot.request.SendMessage;
import com.pengrad.telegrambot.request.SendSticker;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Base64;
import static cc.sukazyo.cono.morny.Log.logger;
public class Encryptor implements ITelegramCommand {
@Nonnull @Override public String getName () { return "encrypt"; }
@Nullable @Override public String[] getAliases () { return new String[0]; }
@Nonnull @Override public String getParamRule () { return "[algorithm|(l)] [(uppercase)]"; }
@Nonnull @Override public String getDescription () { return "通过指定算法加密回复的内容 (目前只支持文本)"; }
@Override
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
// show a simple help page
// the first paragraph lists available encrypt algorithms, and its aliases.
// with the separator "---",
// the second paragraphs shows the mods available and its aliases.
if (!command.hasArgs() || (command.getArgs()[0].equals("l") && command.getArgs().length==1)) {
MornyCoeur.extra().exec(new SendMessage(
event.message().chat().id(), """
<b><u>base64</u></b>, b64
<b><u>base64url</u></b>, base64u, b64u
<b><u>base64decode</u></b>, base64d, b64d
<b><u>base64url-decode</u></b>, base64ud, b64ud
<b><u>sha1</u></b>
<b><u>sha256</u></b>
<b><u>sha512</u></b>
<b><u>md5</u></b>
---
<b><i>uppercase</i></b>, upper, u <i>(sha1/sha256/sha512/md5 only)</i>
"""
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
return;
}
// param1 is the encrypting algorithm, it MUST EXIST.
// so the mod will be set in param2.
// and for now only support UPPERCASE mod, so it exists in param2, or there should no any params.
boolean modUpperCase = false;
if (command.getArgs().length > 1) {
if (command.getArgs().length < 3 && (
command.getArgs()[1].equalsIgnoreCase("uppercase") ||
command.getArgs()[1].equalsIgnoreCase("u") ||
command.getArgs()[1].equalsIgnoreCase("upper")
)) {
modUpperCase = true;
} else {
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(), TelegramStickers.ID_404
).replyToMessageId(event.message().messageId()));
return;
}
}
// for now, only support reply to A TEXT MESSAGE or ONE UNIVERSAL FILE
// if the replied message contains a UNIVERSAL FILE, it will use the file and will not use the text with it
// do not support TELEGRAM INLINE IMAGE/VIDEO/AUDIO yet
// do not support MULTI_FILE yet
// if there's no text message in reply, it will report null as result.
boolean inputText;
byte[] data;
String dataName;
if (event.message().replyToMessage() != null && event.message().replyToMessage().document() != null) {
inputText = false;
try {
data = MornyCoeur.getAccount().getFileContent(MornyCoeur.extra().exec(new GetFile(
event.message().replyToMessage().document().fileId()
)).file());
} catch (IOException e) {
logger.warn("NetworkRequest error: TelegramFileAPI:\n\t" + e.getMessage());
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(),
TelegramStickers.ID_NETWORK_ERR
).replyToMessageId(event.message().messageId()));
return;
}
dataName = event.message().replyToMessage().document().fileName();
} else if (event.message().replyToMessage() != null && event.message().replyToMessage().photo() != null) {
inputText = false;
try {
PhotoSize originPhoto = null;
long photoSize = 0;
for (PhotoSize size : event.message().replyToMessage().photo()) if (photoSize < (long)size.width() *size.height()) {
originPhoto = size;
photoSize = (long)size.width() *size.height();
} // found max size (original) image in available sizes
if (originPhoto==null) throw new IOException("no photo object from api.");
data = MornyCoeur.getAccount().getFileContent(MornyCoeur.extra().exec(new GetFile(
originPhoto.fileId()
)).file());
} catch (IOException e) {
logger.warn("NetworkRequest error: TelegramFileAPI:\n\t" + e.getMessage());
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(),
TelegramStickers.ID_NETWORK_ERR
).replyToMessageId(event.message().messageId()));
return;
}
dataName = "photo"+CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(String.valueOf(System.currentTimeMillis()))).substring(32-12).toUpperCase()+".png";
} else if (event.message().replyToMessage() != null && event.message().replyToMessage().text() != null) {
inputText = true;
data = event.message().replyToMessage().text().getBytes(CommonEncrypt.ENCRYPT_STANDARD_CHARSET);
dataName = null;
} else {
MornyCoeur.extra().exec(new SendMessage(
event.message().chat().id(),
"<i><u>null</u></i>"
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
return;
}
boolean echoString = true;
String resultString = null;
byte[] result = null;
String resultName = null;
switch (command.getArgs()[0]) {
case "base64", "b64", "base64url", "base64u", "b64u" -> {
final Base64.Encoder b64tool = command.getArgs()[0].contains("u") ? Base64.getUrlEncoder() : Base64.getEncoder();
result = b64tool.encode(data);
if (!inputText) {
echoString = false;
resultName = dataName+".b64.txt";
} else {
resultString = new String(result, CommonEncrypt.ENCRYPT_STANDARD_CHARSET);
}
}
case "base64decode", "base64d", "b64d", "base64url-decode", "base64ud", "b64ud" -> {
final Base64.Decoder b64tool = command.getArgs()[0].contains("u") ? Base64.getUrlDecoder() : Base64.getDecoder();
try { result = b64tool.decode(data); }
catch (IllegalArgumentException e) {
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(), TelegramStickers.ID_404
).replyToMessageId(event.message().messageId()));
return;
}
if (!inputText) {
echoString = false;
resultName = CommonEncrypt.base64FilenameLint(dataName);
} else {
resultString = new String(result, CommonEncrypt.ENCRYPT_STANDARD_CHARSET);
}
}
case "md5" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(data));
case "sha1" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashSha1(data));
case "sha256" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashSha256(data));
case "sha512" -> resultString = CommonConvert.byteArrayToHex(CommonEncrypt.hashSha512(data));
default -> {
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(), TelegramStickers.ID_404
).replyToMessageId(event.message().messageId()));
return;
}
}
if (modUpperCase) {
// modUpperCase support only algorithm that showed as HEX value.
// it means md5, sha1, sha256, sha512 here.
// other will report wrong param.
switch (command.getArgs()[0]) {
case "md5", "sha1", "sha256", "sha512" -> {
assert resultString != null;
resultString = resultString.toUpperCase();
}
default -> {
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(), TelegramStickers.ID_404
).replyToMessageId(event.message().messageId()));
return;
}
}
}
if (echoString) {
MornyCoeur.extra().exec(new SendMessage(
event.message().chat().id(),
"<pre><code>" + MsgEscape.escapeHtml(resultString) + "</code></pre>"
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
} else {
MornyCoeur.extra().exec(new SendDocument(
event.message().chat().id(),
result
).fileName(resultName).replyToMessageId(event.message().messageId()));
}
}
}

View File

@ -4,8 +4,8 @@ import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.MornyTrusted;
import cc.sukazyo.cono.morny.bot.event.OnEventHackHandle;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.SendSticker;

View File

@ -1,8 +1,8 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.util.TelegramUserInformation;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramUserInformation;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.User;
import com.pengrad.telegrambot.model.request.ParseMode;

View File

@ -1,6 +1,6 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import javax.annotation.Nonnull;

View File

@ -2,8 +2,8 @@ package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.data.ip186.IP186QueryResponse;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.data.ip186.IP186QueryHandler;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.SendMessage;
@ -12,7 +12,8 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
/**
* {@value IP186QueryHandler#SITE_URL} 查询的 telegram 命令前端

View File

@ -5,8 +5,8 @@ import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.MornySystem;
import cc.sukazyo.cono.morny.data.MornyJrrp;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import cc.sukazyo.untitled.telegram.api.formatting.TGToString;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString;
import com.pengrad.telegrambot.model.BotCommand;
import com.pengrad.telegrambot.model.DeleteMyCommands;
import com.pengrad.telegrambot.model.Update;
@ -27,9 +27,9 @@ import java.util.List;
import java.util.Map;
import static cc.sukazyo.cono.morny.Log.logger;
import static cc.sukazyo.cono.morny.util.CommonFormatUtils.formatDate;
import static cc.sukazyo.cono.morny.util.CommonFormatUtils.formatDuration;
import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
import static cc.sukazyo.cono.morny.util.CommonFormat.formatDate;
import static cc.sukazyo.cono.morny.util.CommonFormat.formatDuration;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
public class MornyCommands {
@ -57,6 +57,7 @@ public class MornyCommands {
}
}
@SuppressWarnings("NonAsciiCharacters")
public MornyCommands () {
register(
@ -67,7 +68,9 @@ public class MornyCommands {
new Nbnhhsh(),
new Ip186Query.Ip(),
new Ip186Query.Whois(),
new Encryptor(),
new SaveData(),
new MornyInformations(),
new Version(),
new MornyRuntime(),
new Jrrp(),
@ -76,6 +79,7 @@ public class MornyCommands {
// 特殊的命令
register(
new Testing(),
new DirectMsgClear()
);
@ -85,7 +89,8 @@ public class MornyCommands {
new 喵呜.揉揉(),
new 喵呜.蹭蹭(),
new 喵呜.贴贴(),
new 私わね()
new 私わね(),
new 喵呜.Progynova()
);
}

View File

@ -0,0 +1,46 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.SendSticker;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class MornyInformations implements ITelegramCommand {
private static final String ACT_STICKER = "stickers";
@Nonnull @Override public String getName () { return "info"; }
@Nullable @Override public String[] getAliases () { return new String[0]; }
@Nonnull @Override public String getParamRule () { return "[(stickers)|(stickers.)sticker_id]"; }
@Nonnull @Override public String getDescription () { return "输出 Morny 当前版本的一些预定义信息"; }
@Override
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
if (!command.hasArgs() || command.getArgs().length > 1) {
MornyCoeur.extra().exec(new SendSticker(event.message().chat().id(), TelegramStickers.ID_404).replyToMessageId(event.message().messageId()));
}
final String action = command.getArgs()[0];
if (action.startsWith("stickers")) {
if (action.equals("stickers"))
TelegramStickers.echoAllStickers(MornyCoeur.extra(), event.message().chat().id(), event.message().messageId());
else {
TelegramStickers.echoStickerByID(
action.substring((ACT_STICKER+".").length()),
MornyCoeur.extra(), event.message().chat().id(), event.message().messageId()
);
}
return;
}
MornyCoeur.extra().exec(new SendSticker(event.message().chat().id(), TelegramStickers.ID_404).replyToMessageId(event.message().messageId()));
}
}

View File

@ -1,18 +1,18 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.SendMessage;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.data.NbnhhshQuery;
import cc.sukazyo.untitled.util.string.StringArrays;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
import static cc.sukazyo.cono.morny.util.CommonConvert.stringsConnecting;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
public class Nbnhhsh implements ITelegramCommand {
@ -30,7 +30,7 @@ public class Nbnhhsh implements ITelegramCommand {
if (event.message().replyToMessage() != null && event.message().replyToMessage().text() != null)
queryTarget = event.message().replyToMessage().text();
if (command.hasArgs())
queryTarget = StringArrays.connectStringArray(command.getArgs(), " ", 0, command.getArgs().length-1);
queryTarget = stringsConnecting(command.getArgs(), " ", 0, command.getArgs().length-1);
NbnhhshQuery.GuessResult response = NbnhhshQuery.sendGuess(queryTarget);

View File

@ -0,0 +1,36 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.SendMessage;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class Testing implements ISimpleCommand {
@Nonnull
@Override
public String getName () {
return "test";
}
@Nullable
@Override
public String[] getAliases () {
return null;
}
@Override
public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
MornyCoeur.extra().exec(new SendMessage(
event.message().chat().id(),
"<b>Just<b/> a TEST command."
).replyToMessageId(event.message().messageId()).parseMode(ParseMode.HTML));
}
}

View File

@ -1,10 +1,12 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.ParseMode;
import com.pengrad.telegrambot.request.SendMessage;
import com.pengrad.telegrambot.request.SendSticker;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -56,4 +58,17 @@ public class 喵呜 {
}
}
public static class Progynova implements ITelegramCommand {
@Nonnull @Override public String getName () { return "install"; }
@Nullable @Override public String[] getAliases () { return new String[0]; }
@Nonnull @Override public String getParamRule () { return ""; }
@Nonnull @Override public String getDescription () { return "抽取一个神秘盒子"; }
@Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(),
TelegramStickers.ID_PROGYNOVA
).replyToMessageId(event.message().messageId()));
}
}
}

View File

@ -1,7 +1,7 @@
package cc.sukazyo.cono.morny.bot.command;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.untitled.util.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.SendMessage;

View File

@ -5,7 +5,7 @@ import cc.sukazyo.cono.morny.bot.api.EventListenerManager;
public class EventListeners {
public static final OnTelegramCommand COMMANDS_LISTENER = new OnTelegramCommand();
public static final OnActivityRecord ACTIVITY_RECORDER = new OnActivityRecord();
@SuppressWarnings("unused") public static final OnActivityRecord ACTIVITY_RECORDER = new OnActivityRecord();
public static final OnUserSlashAction USER_SLASH_ACTION = new OnUserSlashAction();
public static final OnUpdateTimestampOffsetLock UPDATE_TIMESTAMP_OFFSET_LOCK = new OnUpdateTimestampOffsetLock();
public static final OnInlineQueries INLINE_QUERY = new OnInlineQueries();
@ -19,7 +19,7 @@ public class EventListeners {
public static void registerAllListeners () {
EventListenerManager.addListener(
ACTIVITY_RECORDER,
// ACTIVITY_RECORDER,
UPDATE_TIMESTAMP_OFFSET_LOCK,
/* write functional event behind here */
// KUOHUANHUAN_NEED_SLEEP,

View File

@ -4,9 +4,9 @@ import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.MornyTrusted;
import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.data.TelegramStickers;
import cc.sukazyo.cono.morny.util.CommonFormatUtils;
import cc.sukazyo.untitled.telegram.api.formatting.TGToString;
import cc.sukazyo.untitled.util.telegram.formatting.MsgEscape;
import cc.sukazyo.cono.morny.util.CommonFormat;
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape;
import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString;
import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.Update;
@ -122,9 +122,9 @@ public class OnCallMe extends EventListener {
event.message().from().id(),
String.format("<i>on</i> <code>%s [UTC+8]</code>\n- <code>%s</code> <i>before</i>",
MsgEscape.escapeHtml(
CommonFormatUtils.formatDate((long)lastDinnerData.forwardDate()*1000, 8)
CommonFormat.formatDate((long)lastDinnerData.forwardDate()*1000, 8)
), MsgEscape.escapeHtml(
CommonFormatUtils.formatDuration(System.currentTimeMillis()-(long)lastDinnerData.forwardDate()*1000)
CommonFormat.formatDuration(System.currentTimeMillis()-(long)lastDinnerData.forwardDate()*1000)
)
)
).replyToMessageId(sendResp.message().messageId()).parseMode(ParseMode.HTML));

View File

@ -22,7 +22,8 @@ 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;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
public class OnCallMsgSend extends EventListener {

View File

@ -2,7 +2,7 @@ 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.telegram.formatting.MsgEscape;
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape;
import com.google.gson.GsonBuilder;
import com.pengrad.telegrambot.model.Update;

View File

@ -2,7 +2,7 @@ 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.telegram.object.InputCommand;
import cc.sukazyo.cono.morny.util.tgapi.InputCommand;
import com.pengrad.telegrambot.model.Update;

View File

@ -2,7 +2,7 @@ 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 cc.sukazyo.cono.morny.util.UniversalCommand;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.request.SendMessage;
import org.jetbrains.annotations.NotNull;
@ -22,7 +22,7 @@ public class OnUserRandoms extends EventListener {
if (update.message().text() == null) return false;
if (!update.message().text().startsWith("/")) return false;
final String[] preProcess = CommonCommand.format(update.message().text());
final String[] preProcess = UniversalCommand.format(update.message().text());
if (preProcess.length > 1) return false;
final String query = preProcess[0];

View File

@ -2,9 +2,8 @@ package cc.sukazyo.cono.morny.bot.event;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.util.tgapi.TGToStringFromMessage;
import cc.sukazyo.untitled.util.command.CommonCommand;
import cc.sukazyo.untitled.util.string.StringArrays;
import cc.sukazyo.cono.morny.util.UniversalCommand;
import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString;
import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.Update;
@ -13,7 +12,8 @@ import com.pengrad.telegrambot.request.SendMessage;
import javax.annotation.Nonnull;
import static cc.sukazyo.untitled.util.telegram.formatting.MsgEscape.escapeHtml;
import static cc.sukazyo.cono.morny.util.CommonConvert.stringsConnecting;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
public class OnUserSlashAction extends EventListener {
@ -38,7 +38,7 @@ public class OnUserSlashAction extends EventListener {
// return false;
// }
final String[] action = CommonCommand.format(text);
final String[] action = UniversalCommand.format(text);
action[0] = action[0].substring(1);
if (action[0].matches("^\\w+(@\\w+)?$")) {
@ -53,7 +53,7 @@ public class OnUserSlashAction extends EventListener {
final boolean hasObject = action.length != (isHardParse?2:1);
final String object =
hasObject ?
StringArrays.connectStringArray(action, " ", isHardParse?2:1, action.length-1) :
stringsConnecting(action, " ", isHardParse?2:1, action.length-1) :
"";
final Message origin = event.message();
final Message target = (event.message().replyToMessage() == null ? (
@ -66,11 +66,11 @@ public class OnUserSlashAction extends EventListener {
event.message().chat().id(),
String.format(
"%s %s%s %s %s!",
TGToStringFromMessage.as(origin).getSenderFirstNameRefHtml(),
TGToString.as(origin).getSenderFirstNameRefHtml(),
escapeHtml(verb), escapeHtml((hasObject?"":"")),
origin==target ?
"<a href='tg://user?id="+TGToStringFromMessage.as(target).getSenderId()+"'>自己</a>" :
TGToStringFromMessage.as(target).getSenderFirstNameRefHtml(),
"<a href='tg://user?id="+TGToString.as(target).getSenderId()+"'>自己</a>" :
TGToString.as(target).getSenderFirstNameRefHtml(),
escapeHtml(hasObject ? object+" " : "")
)
).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));

View File

@ -4,11 +4,12 @@ import javax.annotation.Nullable;
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.InlineQueryResult;
public interface ITelegramQuery <T extends InlineQueryResult<T>> {
import java.util.List;
public interface ITelegramQuery {
@Nullable
InlineQueryUnit<T> query (Update event);
List<InlineQueryUnit<?>> query (Update event);
}

View File

@ -9,19 +9,21 @@ import java.util.List;
public class MornyQueries {
private final List<ITelegramQuery<?>> queryInstances = new ArrayList<>();
private final List<ITelegramQuery> queryInstances = new ArrayList<>();
public MornyQueries () {
queryInstances.add(new RawText());
queryInstances.add(new MyInformation());
queryInstances.add(new ShareToolTwitter());
queryInstances.add(new ShareToolBilibili());
}
@Nonnull
public List<InlineQueryUnit<?>> query (@Nonnull Update event) {
final List<InlineQueryUnit<?>> results = new ArrayList<>();
for (ITelegramQuery<?> instance : queryInstances) {
final InlineQueryUnit<?> r = instance.query(event);
if (r!=null) results.add(r);
for (ITelegramQuery instance : queryInstances) {
final List<InlineQueryUnit<?>> r = instance.query(event);
if (r!=null) results.addAll(r);
}
return results;
}

View File

@ -8,23 +8,28 @@ import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
import com.pengrad.telegrambot.model.request.ParseMode;
import cc.sukazyo.cono.morny.util.TelegramUserInformation;
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramUserInformation;
public class MyInformation implements ITelegramQuery<InlineQueryResultArticle> {
import java.util.Collections;
import java.util.List;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
public class MyInformation implements ITelegramQuery {
public static final String ID_PREFIX = "[morny/info/me]";
public static final String TITLE = "My Account Information";
@Override
@Nullable
public InlineQueryUnit<InlineQueryResultArticle> query(Update event) {
public List<InlineQueryUnit<?>> query(Update event) {
if (!(event.inlineQuery().query() == null || "".equals(event.inlineQuery().query()))) return null;
return new InlineQueryUnit<>(new InlineQueryResultArticle(
ID_PREFIX, TITLE,
return Collections.singletonList(new InlineQueryUnit<>(new InlineQueryResultArticle(
inlineIds(ID_PREFIX), TITLE,
new InputTextMessageContent(
TelegramUserInformation.informationOutputHTML(event.inlineQuery().from())
).parseMode(ParseMode.HTML)
)).isPersonal(true).cacheTime(10);
)).isPersonal(true).cacheTime(10));
}
}

View File

@ -1,7 +1,6 @@
package cc.sukazyo.cono.morny.bot.query;
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
import cc.sukazyo.cono.morny.util.EncryptUtils;
import javax.annotation.Nullable;
@ -9,20 +8,24 @@ import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
public class RawText implements ITelegramQuery<InlineQueryResultArticle> {
import java.util.Collections;
import java.util.List;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
public class RawText implements ITelegramQuery {
public static final String ID_PREFIX = "[morny/r/text]";
public static final String TITLE = "Raw Text";
@Override
@Nullable
public InlineQueryUnit<InlineQueryResultArticle> query (Update event) {
public List<InlineQueryUnit<?>> query (Update event) {
if (event.inlineQuery().query() == null || "".equals(event.inlineQuery().query())) return null;
return new InlineQueryUnit<>(new InlineQueryResultArticle(
ID_PREFIX + EncryptUtils.encryptByMD5(event.inlineQuery().query()),
TITLE,
return Collections.singletonList(new InlineQueryUnit<>(new InlineQueryResultArticle(
inlineIds(ID_PREFIX, event.inlineQuery().query()), TITLE,
new InputTextMessageContent(event.inlineQuery().query())
));
)));
}
}

View File

@ -0,0 +1,80 @@
package cc.sukazyo.cono.morny.bot.query;
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
import cc.sukazyo.cono.morny.util.BiliTool;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
import com.pengrad.telegrambot.model.request.InputTextMessageContent;
import com.pengrad.telegrambot.model.request.ParseMode;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//import static cc.sukazyo.cono.morny.Log.logger;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
public class ShareToolBilibili implements ITelegramQuery {
public static final String TITLE_BILI_AV = "[bilibili] Share video / av";
public static final String TITLE_BILI_BV = "[bilibili] Share video / BV";
public static final String ID_PREFIX_BILI_AV = "[morny/share/bili/av]";
public static final String ID_PREFIX_BILI_BV = "[morny/share/bili/bv]";
public static final Pattern REGEX_BILI_VIDEO = Pattern.compile("^(?:(?:https?://)?(?:www\\.)?bilibili\\.com(?:/s)?/video/((?:av|AV)(\\d+)|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]+))/?(\\?(?:p=(\\d+))?.*)?|(?:av|AV)(\\d+)|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]+))$");
private static final String SHARE_FORMAT_HTML = "<a href='%s'>%s</a>";
@Nullable
@Override
public List<InlineQueryUnit<?>> query (Update event) {
if (event.inlineQuery().query() == null) return null;
final Matcher regex = REGEX_BILI_VIDEO.matcher(event.inlineQuery().query());
if (regex.matches()) {
// logger.debug(String.format(
// "====== ok\n1: %s\n2: %s\n3: %s\n4: %s\n5: %s\n6: %s\n7: %s",
// regex.group(1), regex.group(2), regex.group(3), regex.group(4),
// regex.group(5), regex.group(6), regex.group(7)
// ));
// get video id from input, also get video part id
String av = regex.group(2)==null ? regex.group(6)==null ? null : regex.group(6) : regex.group(2);
String bv = regex.group(3)==null ? regex.group(7)==null ? null : regex.group(7) : regex.group(3);
// logger.trace(String.format("catch id av[%s] bv[%s]", av, bv));
final int part = regex.group(5)==null ? -1 : Integer.parseInt(regex.group(5));
// logger.trace(String.format("catch part [%s]", part));
if (av == null) {
assert bv != null;
av = String.valueOf(BiliTool.toAv(bv));
// logger.trace(String.format("converted bv[%s] to av[%s]", bv, av));
} else {
bv = BiliTool.toBv(Long.parseLong(av));
// logger.trace(String.format("converted av[%s] to bv[%s]", av, bv));
}
// build standard share links
final String linkPartParam = part==-1 ? "" : "?p="+part;
final String linkAv = "https://www.bilibili.com/video/av"+av + linkPartParam;
final String linkBv = "https://www.bilibili.com/video/BV"+bv + linkPartParam;
final String idAv = "av"+av;
final String idBv = "BV"+bv;
// logger.trace("built all data.");
// build share message element
List<InlineQueryUnit<?>> result = new ArrayList<>();
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
inlineIds(ID_PREFIX_BILI_AV+av), TITLE_BILI_AV+av,
new InputTextMessageContent(String.format(SHARE_FORMAT_HTML, linkAv, idAv)).parseMode(ParseMode.HTML)
)));
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
inlineIds(ID_PREFIX_BILI_BV+bv), TITLE_BILI_BV+bv,
new InputTextMessageContent(String.format(SHARE_FORMAT_HTML, linkBv, idBv)).parseMode(ParseMode.HTML)
)));
return result;
}
return null;
}
}

View File

@ -0,0 +1,50 @@
package cc.sukazyo.cono.morny.bot.query;
import cc.sukazyo.cono.morny.bot.api.InlineQueryUnit;
import com.pengrad.telegrambot.model.Update;
import com.pengrad.telegrambot.model.request.InlineQueryResultArticle;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cc.sukazyo.cono.morny.util.tgapi.formatting.NamedUtils.inlineIds;
public class ShareToolTwitter implements ITelegramQuery {
public static final String TITLE_VX = "[tweet] Share as VxTwitter";
public static final String TITLE_VX_COMBINED = "[tweet] Share as VxTwitter(combination)";
public static final String ID_PREFIX_VX = "[morny/share/twitter/vxtwi]";
public static final String ID_PREFIX_VX_COMBINED = "[morny/share/twitter/vxtwi_combine]";
public static final Pattern REGEX_TWEET_LINK = Pattern.compile(
"^(?:https?://)?((?:(?:c\\.)?vx|fx|www\\.)?twitter\\.com)/((\\w+)/status/(\\d+)(?:/photo/(\\d+))?)/?(\\?[\\w&=-]+)?$");
@Nullable
@Override
public List<InlineQueryUnit<?>> query (@Nonnull Update event) {
if (event.inlineQuery().query() == null) return null;
final Matcher regex = REGEX_TWEET_LINK.matcher(event.inlineQuery().query());
if (regex.matches()) {
List<InlineQueryUnit<?>> result = new ArrayList<>();
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
inlineIds(ID_PREFIX_VX+event.inlineQuery().query()), TITLE_VX,
String.format("https://vxtwitter.com/%s", regex.group(2))
)));
result.add(new InlineQueryUnit<>(new InlineQueryResultArticle(
inlineIds(ID_PREFIX_VX_COMBINED+event.inlineQuery().query()), TITLE_VX_COMBINED,
String.format("https://c.vxtwitter.com/%s", regex.group(2))
)));
return result;
}
return null;
}
}

View File

@ -1,7 +1,7 @@
package cc.sukazyo.cono.morny.daemon;
import cc.sukazyo.cono.morny.MornyCoeur;
import cc.sukazyo.cono.morny.util.CommonFormatUtils;
import cc.sukazyo.cono.morny.util.CommonFormat;
import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.MessageEntity;
import com.pengrad.telegrambot.model.request.ParseMode;
@ -54,7 +54,7 @@ public class MedicationTimer extends Thread {
public void refreshNotificationWrite (Message edited) {
if (edited.messageId() != lastNotify) return;
final String editTime = CommonFormatUtils.formatDate(edited.editDate()*1000, 8);
final String editTime = CommonFormat.formatDate(edited.editDate()*1000, 8);
ArrayList<MessageEntity> entities = new ArrayList<>();
if (edited.entities() != null) entities.addAll(List.of(edited.entities()));
entities.add(new MessageEntity(MessageEntity.Type.italic, edited.text().length() + "\n-- ".length(), editTime.length()));

View File

@ -8,7 +8,7 @@ public class MornyDaemons {
public static void start () {
logger.info("ALL Morny Daemons starting...");
TrackerDataManager.init();
// TrackerDataManager.init();
medicationTimerInstance.start();
logger.info("Morny Daemons started.");
}
@ -17,10 +17,10 @@ public class MornyDaemons {
logger.info("ALL Morny Daemons stopping...");
TrackerDataManager.DAEMON.interrupt();
// TrackerDataManager.DAEMON.interrupt();
medicationTimerInstance.interrupt();
TrackerDataManager.trackingLock.lock();
// TrackerDataManager.trackingLock.lock();
try { medicationTimerInstance.join(); } catch (InterruptedException e) { e.printStackTrace(System.out); }
logger.info("ALL Morny Daemons STOPPED.");

View File

@ -1,6 +1,7 @@
package cc.sukazyo.cono.morny.data;
import cc.sukazyo.cono.morny.util.EncryptUtils;
import cc.sukazyo.cono.morny.util.CommonConvert;
import cc.sukazyo.cono.morny.util.CommonEncrypt;
import com.pengrad.telegrambot.model.User;
/**
@ -39,7 +40,7 @@ public class MornyJrrp {
* @return 算法得到的 jrrp 取值为 {@code [0.00. 100.00]}
*/
public static double calcJrrpXmomi (long userId, long dayStamp) {
return (double)Long.parseLong(EncryptUtils.encryptByMD5(userId + "@" + dayStamp).substring(0, 4), 16) / (double)0xffff;
return (double)Long.parseLong(CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(userId + "@" + dayStamp)).substring(0, 4), 16) / (double)0xffff;
}
}

View File

@ -1,5 +1,12 @@
package cc.sukazyo.cono.morny.data;
import cc.sukazyo.cono.morny.util.tgapi.ExtraAction;
import com.pengrad.telegrambot.request.SendMessage;
import com.pengrad.telegrambot.request.SendSticker;
import com.pengrad.telegrambot.response.SendResponse;
import java.lang.reflect.Field;
/**
* 存放 bot 使用到的贴纸
* @since 0.4.2.0
@ -15,5 +22,47 @@ public class TelegramStickers {
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_PROGYNOVA = "CAACAgUAAxkBAAICm2KEuL7UQqNP7vSPCg2DHJIND6UsAAKLAwACH4WSBszIo722aQ3jJAQ";
public static final String ID_NETWORK_ERR = "CAACAgEAAxkBAAID0WNJgNEkD726KW4vZeFlw0FlVVyNAAIXJgACePzGBb50o7O1RbxoKgQ";
public static void echoAllStickers (ExtraAction actionObject, long sentChat, int replyToMessageId) {
for (Field object : TelegramStickers.class.getFields()) {
if (object.getType()==String.class && object.getName().startsWith("ID_")) {
try {
final String stickerId = (String)object.get("");
SendSticker echo = new SendSticker(sentChat, stickerId);
SendMessage echoName = new SendMessage(sentChat, object.getName());
if (replyToMessageId!=-1) echo.replyToMessageId(replyToMessageId);
SendResponse echoedName = actionObject.exec(echoName);
actionObject.exec(echo.replyToMessageId(echoedName.message().messageId()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
public static void echoStickerByID (String stickerFieldID, ExtraAction actionObject, long sentChat, int replyToMessageId) {
try {
// normally get the sticker and echo
Field sticker = TelegramStickers.class.getField(stickerFieldID);
SendMessage echoName = new SendMessage(sentChat, sticker.getName());
SendSticker echo = new SendSticker(sentChat, (String)sticker.get(""));
if (replyToMessageId!=-1) echo.replyToMessageId(replyToMessageId);
SendResponse echoedName = actionObject.exec(echoName);
actionObject.exec(echo.replyToMessageId(echoedName.message().messageId()));
} catch (NoSuchFieldException e) {
// no such sticker found
SendSticker echo404 = new SendSticker(sentChat, TelegramStickers.ID_404);
if (replyToMessageId!=-1) echo404.replyToMessageId(replyToMessageId);
actionObject.exec(echo404);
} catch (IllegalAccessException e) {
// java-reflect get sticker FILE_ID failed
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,73 @@
package cc.sukazyo.cono.morny.util;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
public class BiliTool {
private static final long V_CONV_XOR = 177451812L;
private static final long V_CONV_ADD = 8728348608L;
private static final char[] BV_TABLE = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF".toCharArray();
private static final int TABLE_INT = BV_TABLE.length;
private static final Map<Character, Integer> BV_TABLE_REVERSED = new HashMap<>();
static { for (int i = 0; i < BV_TABLE.length; i++) BV_TABLE_REVERSED.put(BV_TABLE[i], i); }
private static final char[] BV_TEMPLATE = "1 4 1 7 ".toCharArray();
private static final int[] BV_TEMPLATE_FILTER = new int[]{9, 8, 1, 6, 2, 4};
/**
* Convert a <a href="https://www.bilibili.com/">Bilibili</a> AV video id format to BV id format.
* <p>
* the AV id is a number; the BV id is a special base58 number, it shows as String in programming.<br>
* eg:<br>
* while the link <i>{@code https://www.bilibili.com/video/BV17x411w7KC/}</i>
* shows the same with <i>{@code https://www.bilibili.com/video/av170001/}</i>,
* the AV id is <u>{@code 170001}</u>, the BV id is <u>{@code 17x411w7KC}</u>
* <p>
* for now , the BV id has 10 digits.
* the method <b>available while the <u>av-id < 2^27</u></b>, while it theoretically available when the av-id < 2^30.
*
* @see <a href="https://www.zhihu.com/question/381784377/answer/1099438784">mcfx的回复: 如何看待 2020 3 23 日哔哩哔哩将稿件的av 变更为BV </a>
*
* @param bv the BV id, a string in (a special) base58 number format, <b>without "BV" prefix</b>.
* @return the AV id corresponding to this bv id in <a href="https://www.bilibili.com/">Bilibili</a>, formatted as a number.
*/
@Nonnegative
public static long toAv (@Nonnull String bv) {
long av = 0;
for (int i = 0; i < BV_TEMPLATE_FILTER.length; i++) {
av += BV_TABLE_REVERSED.get(bv.charAt(BV_TEMPLATE_FILTER[i])) * Math.pow(TABLE_INT,i);
}
return (av-V_CONV_ADD)^V_CONV_XOR;
}
/**
* Convert a <a href="https://www.bilibili.com/">Bilibili</a> BV video id format to AV id format.
* <p>
* the AV id is a number; the BV id is a special base58 number, it shows as String in programming.<br>
* eg:<br>
* while the link <i>{@code https://www.bilibili.com/video/BV17x411w7KC/}</i>
* shows the same with <i>{@code https://www.bilibili.com/video/av170001/}</i>,
* the AV id is <u>{@code 170001}</u>, the BV id is <u>{@code 17x411w7KC}</u>
* <p>
* for now , the BV id has 10 digits.
* the method <b>available while the <u>av-id < 2^27</u></b>, while it theoretically available when the av-id < 2^30.
*
* @see <a href="https://www.zhihu.com/question/381784377/answer/1099438784">mcfx的回复: 如何看待 2020 3 23 日哔哩哔哩将稿件的av 变更为BV </a>
*
* @param av the (base10) AV id.
* @return the AV id corresponding to this bv id in <a href="https://www.bilibili.com/">Bilibili</a>,
* as a (special) base 58 number format <b>without "BV" prefix</b>.
*/
@Nonnull
public static String toBv (@Nonnegative long av) {
av = (av^V_CONV_XOR)+V_CONV_ADD;
final char[] bv = BV_TEMPLATE.clone();
for (int i = 0; i < BV_TEMPLATE_FILTER.length; i++) {
bv[BV_TEMPLATE_FILTER[i]] = BV_TABLE[(int)(Math.floor(av/(Math.pow(TABLE_INT, i)))%TABLE_INT)];
}
return String.copyValueOf(bv);
}
}

View File

@ -0,0 +1,61 @@
package cc.sukazyo.cono.morny.util;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
/**
* 进行简单类型转换等工作的类.
*/
public class CommonConvert {
/**
* 将字节数组转换成 hex 字符串.
* @param b 字节数组
* @return String 格式的字节数组的 hex 每个字节当中没有分隔符
* @see #byteToHex(byte)
*/
@Nonnull
public static String byteArrayToHex(@Nonnull byte[] b){
StringBuilder sb = new StringBuilder();
for (byte value : b) {
sb.append(byteToHex(value));
}
return sb.toString();
}
/**
* 将一个字节转换成十六进制 hex 字符串.
* @param b 字节值
* @return String 格式的字节的 hex 小写
*/
@Nonnull
public static String byteToHex(byte b) {
final String hex = Integer.toHexString(b & 0xff);
return hex.length()<2?"0"+hex:hex;
}
/**
* 将一个字符串数组按照一定规则连接.
* <p>
* 连接的方式类似于"数据1+分隔符+数据2+分隔符+...+数据n-1+分隔符+数据n"
*
* @param array 需要进行连接的字符串数组数组中每一个元素会是一个数据
* @param connector 在每两个传入数据中插入的分隔符
* @param startIndex 从传入的数据组中的哪一个位置开始第一个元素的位置是 {@code 0}
* @param stopIndex 从传入的数据组中的哪一个位置停止元素位置计算方式同上
* @return 连接好的字符串
*/
@Nonnull
public static String stringsConnecting (
@Nonnull String[] array, @Nonnull String connector, @Nonnegative int startIndex, @Nonnegative int stopIndex
) {
final StringBuilder builder = new StringBuilder();
for (int i = startIndex; i < stopIndex; i++) {
builder.append(array[i]);
builder.append(connector);
}
builder.append(array[stopIndex]);
return builder.toString();
}
}

View File

@ -0,0 +1,144 @@
package cc.sukazyo.cono.morny.util;
import javax.annotation.Nonnull;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* 用于数据加密或编解码的工具类.
* <p>
* 出于 java std Base64 {@link Base64.Encoder encode}/{@link Base64.Decoder decode} 十分好用在此不再进行包装
*/
public class CommonEncrypt {
/**
* 在使用加密算法处理字符串时默认会使用的字符串编码.
* <p>
* Morny 使用 UTF-8 编码因为这是一般而言加解密工具的默认行为
*/
public static final Charset ENCRYPT_STANDARD_CHARSET = StandardCharsets.UTF_8;
@Nonnull
private static byte[] hashAsJavaMessageDigest(String algorithm, @Nonnull byte[] data) {
try {
return MessageDigest.getInstance(algorithm).digest(data);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/**
* 取得数据的 md5 散列值.
*
* @param data byte 数组形式的数据体
* @return 二进制(byte数组)格式的数据的 md5 散列值
*/
@Nonnull
public static byte[] hashMd5 (@Nonnull byte[] data) {
return hashAsJavaMessageDigest("md5", data);
}
/**
* 取得一个字符串的 md5 散列值.
* <p>
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
*
* @param originString 要进行散列的字符串
* @return 二进制(byte数组)格式的 md5 散列值
*/
@Nonnull
public static byte[] hashMd5 (String originString) {
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
}
/**
* 取得数据的 sha1 散列值.
*
* @param data byte 数组形式的数据体
* @return 二进制(byte数组)格式的数据的 sha1 散列值
*/
@Nonnull
public static byte[] hashSha1 (@Nonnull byte[] data) {
return hashAsJavaMessageDigest("sha1", data);
}
/**
* 取得一个字符串的 sha1 散列值.
* <p>
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
*
* @param originString 要进行散列的字符串
* @return 二进制(byte数组)格式的 sha1 散列值
*/
@Nonnull
public static byte[] hashSha1 (String originString) {
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
}
/**
* 取得数据的 sha256 散列值.
*
* @param data byte 数组形式的数据体
* @return 二进制(byte数组)格式的数据的 sha256 散列值
*/
@Nonnull
public static byte[] hashSha256 (@Nonnull byte[] data) {
return hashAsJavaMessageDigest("sha256", data);
}
/**
* 取得一个字符串的 sha256 散列值.
* <p>
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
*
* @param originString 要进行散列的字符串
* @return 二进制(byte数组)格式的 sha256 散列值
*/
@Nonnull
public static byte[] hashSha256 (String originString) {
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
}
/**
* 取得数据的 sha512 散列值.
*
* @param data byte 数组形式的数据体
* @return 二进制(byte数组)格式的数据的 sha512 散列值
*/
@Nonnull
public static byte[] hashSha512 (@Nonnull byte[] data) {
return hashAsJavaMessageDigest("md5", data);
}
/**
* 取得一个字符串的 sha512 散列值.
* <p>
* 输入的字符串将会以 {@link #ENCRYPT_STANDARD_CHARSET 默认的 UTF-8} 编码进行解析
*
* @param originString 要进行散列的字符串
* @return 二进制(byte数组)格式的 sha512 散列值
*/
@Nonnull
public static byte[] hashSha512 (String originString) {
return hashMd5(originString.getBytes(ENCRYPT_STANDARD_CHARSET));
}
@Nonnull
public static String base64FilenameLint (String inputName) {
if (inputName.endsWith(".b64")) {
return inputName.substring(0, inputName.length()-".b64".length());
} else if (inputName.endsWith(".b64.txt")) {
return inputName.substring(0, inputName.length()-".b64.txt".length());
} else if (inputName.endsWith(".base64")) {
return inputName.substring(0, inputName.length()-".base64".length());
} else if (inputName.endsWith(".base64.txt")) {
return inputName.substring(0, inputName.length()-".base64.txt".length());
} else {
return inputName;
}
}
}

View File

@ -6,10 +6,12 @@ import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class CommonFormatUtils {
public class CommonFormat {
public static final String DATE_TIME_PATTERN_FULL_MILLIS = "yyyy-MM-dd HH:mm:ss:SSS";
public static String formatDate (long timestamp, int utcOffset) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS").format(LocalDateTime.ofInstant(
return DateTimeFormatter.ofPattern(DATE_TIME_PATTERN_FULL_MILLIS).format(LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamp),
ZoneId.ofOffset("UTC", ZoneOffset.ofHours(utcOffset))
));

View File

@ -1,62 +0,0 @@
package cc.sukazyo.cono.morny.util;
import javax.annotation.Nonnull;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 用于数据加密或编码的工具类<br>
* <s>显然大部分代码是抄来的</s><br>
* <ul>
* <li><a href="https://blog.csdn.net/yu540135101/article/details/86765457">{@link #encryptByMD5} & {@link #byteToHex}
* & {@link #byteArrayToHex} 来源</a></li>
* </ul>
*/
public class EncryptUtils {
private final static String[] hexArray = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
/***
* 对指定的字符串进行MD5加密
*/
@Nonnull
public static String encryptByMD5(String originString) {
try {
//创建具有MD5算法的信息摘要
MessageDigest md = MessageDigest.getInstance("MD5");
//使用指定的字节数组对摘要进行最后更新然后完成摘要计算
byte[] bytes = md.digest(originString.getBytes());
//将得到的字节数组变成字符串返回
String s = byteArrayToHex(bytes);
return s.toUpperCase();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException();
}
}
/**
* 将字节数组转换成十六进制并以字符串的形式返回
* 128位是指二进制位二进制太长所以一般都改写成16进制
* 每一位16进制数可以代替4位二进制数所以128位二进制数写成16进制就变成了128/4=32位
*/
private static String byteArrayToHex(byte[] b){
StringBuilder sb = new StringBuilder();
for (byte value : b) {
sb.append(byteToHex(value));
}
return sb.toString();
}
/**
* 将一个字节转换成十六进制并以字符串的形式返回
*/
public static String byteToHex(byte b) {
int n = b;
if (n < 0)
n = n + 256;
int d1 = n / 16;
int d2 = n % 16;
return hexArray[d1]+hexArray[d2];
}
}

View File

@ -0,0 +1,47 @@
package cc.sukazyo.cono.morny.util;
import javax.annotation.Nonnull;
import java.util.ArrayList;
public class UniversalCommand {
@Nonnull
public static String[] format (@Nonnull String com) {
final ArrayList<String> arr = new ArrayList<>();
final StringBuilder tmp = new StringBuilder();
final char[] coma = com.toCharArray();
for (int i = 0; i < coma.length; i++) {
if (coma[i] == ' ') {
if (!tmp.toString().equals("")) { arr.add(tmp.toString()); }
tmp.setLength(0);
} else if (coma[i] == '"') {
while (true) {
i++;
if (coma[i] == '"') {
break;
} else if (coma[i] == '\\' && (coma[i+1] == '"' || coma[i+1] == '\\')) {
i++;
tmp.append(coma[i]);
} else {
tmp.append(coma[i]);
}
}
} else if (coma[i] == '\\' && (coma[i+1] == ' ' || coma[i+1] == '"' || coma[i+1] == '\\')) {
i++;
tmp.append(coma[i]);
} else {
tmp.append(coma[i]);
}
}
if (!tmp.toString().equals("")) { arr.add(tmp.toString()); }
tmp.setLength(0);
final String[] out = new String[arr.size()];
arr.toArray(out);
return out;
}
}

View File

@ -0,0 +1,88 @@
package cc.sukazyo.cono.morny.util.tgapi;
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException;
import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.ChatMember;
import com.pengrad.telegrambot.model.User;
import com.pengrad.telegrambot.request.BaseRequest;
import com.pengrad.telegrambot.request.GetChatMember;
import com.pengrad.telegrambot.response.BaseResponse;
public class ExtraAction {
private final TelegramBot bot;
public ExtraAction (TelegramBot bot) {
this.bot = bot;
}
public static ExtraAction as (TelegramBot bot) {
return new ExtraAction(bot);
}
public boolean isUserInGroup (User user, Chat chat) {
return isUserInGroup(user.id(), chat.id());
}
public <T extends BaseRequest<T, R>, R extends BaseResponse> R exec (T req) {
return exec(req, "");
}
public <T extends BaseRequest<T, R>, R extends BaseResponse> R exec (T req, String errorMessage) {
final R resp = bot.execute(req);
if (!resp.isOk()) throw new EventRuntimeException.ActionFailed(
(errorMessage.equals("") ? String.valueOf(resp.errorCode()) : errorMessage),
resp
);
return resp;
}
public boolean isUserInGroup (User user, Chat chat, ChatMember.Status permissionLevel) {
return isUserInGroup(user.id(), chat.id(), permissionLevel);
}
public boolean isUserInGroup (long userId, long chatId) {
return isUserInGroup(userId, chatId, ChatMember.Status.restricted);
}
public boolean isUserInGroup (long userId, long chatId, ChatMember.Status permissionLevel) {
final ChatMember chatMember = exec(new GetChatMember(chatId, userId)).chatMember();
return
chatMember != null &&
UserPermissionLevel.as(chatMember.status()).hasPermission(UserPermissionLevel.as(permissionLevel));
}
}
enum UserPermissionLevel {
CREATOR(3),
ADMINISTRATOR(2),
MEMBER(1),
RESTRICTED(0),
LEFT(-1),
KICKED(-2);
final int permissionLevel;
UserPermissionLevel (int permissionLevel) {
this.permissionLevel = permissionLevel;
}
static UserPermissionLevel as (ChatMember.Status status) {
return switch (status) {
case creator -> CREATOR;
case administrator -> ADMINISTRATOR;
case member -> MEMBER;
case restricted -> RESTRICTED;
case left -> LEFT;
case kicked -> KICKED;
};
}
boolean hasPermission (UserPermissionLevel required) {
return this.permissionLevel >= required.permissionLevel;
}
}

View File

@ -0,0 +1,66 @@
package cc.sukazyo.cono.morny.util.tgapi;
import cc.sukazyo.cono.morny.util.UniversalCommand;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
public class InputCommand {
private final String target;
private final String command;
private final String[] args;
private InputCommand (@Nullable String target, @Nonnull String command, @Nonnull String[] args) {
this.target = target;
this.command = command;
this.args = args;
}
public InputCommand (@Nonnull String[] inputArray) {
this(parseInputArray(inputArray));
}
public InputCommand (@Nonnull String input) {
this(UniversalCommand.format(input));
}
public InputCommand (@Nonnull InputCommand source) {
this(source.target, source.command, source.args);
}
public static InputCommand parseInputArray (@Nonnull String[] inputArray) {
final String[] cx = inputArray[0].split("@", 2);
final String[] args = new String[inputArray.length-1];
System.arraycopy(inputArray, 1, args, 0, inputArray.length - 1);
return new InputCommand(cx.length == 1 ? null : cx[1], cx[0], args);
}
@Nullable
public String getTarget () {
return target;
}
@Nonnull
public String getCommand () {
return command;
}
@Nonnull
public String[] getArgs () {
return args;
}
public boolean hasArgs () {
return args.length != 0;
}
@Override
@Nonnull
public String toString() {
return String.format("{{%s}@{%s}#{%s}}", command, target, Arrays.toString(args));
}
}

View File

@ -0,0 +1,35 @@
package cc.sukazyo.cono.morny.util.tgapi.event;
import com.pengrad.telegrambot.response.BaseResponse;
public class EventRuntimeException extends RuntimeException {
public EventRuntimeException () {
super();
}
public EventRuntimeException (String message) {
super(message);
}
public static class ActionFailed extends EventRuntimeException {
private final BaseResponse response;
public ActionFailed (BaseResponse response) {
super();
this.response = response;
}
public ActionFailed (String message, BaseResponse response) {
super(message);
this.response = response;
}
public BaseResponse getResponse() {
return response;
}
}
}

View File

@ -0,0 +1,15 @@
package cc.sukazyo.cono.morny.util.tgapi.formatting;
import javax.annotation.Nonnull;
public class MsgEscape {
@Nonnull
public static String escapeHtml (@Nonnull String raw) {
raw = raw.replaceAll("&", "&amp;");
raw = raw.replaceAll("<", "&lt;");
raw = raw.replaceAll(">", "&gt;");
return raw;
}
}

View File

@ -0,0 +1,18 @@
package cc.sukazyo.cono.morny.util.tgapi.formatting;
import cc.sukazyo.cono.morny.util.CommonConvert;
import cc.sukazyo.cono.morny.util.CommonEncrypt;
import javax.annotation.Nonnull;
public class NamedUtils {
public static String inlineIds (@Nonnull String tag) {
return inlineIds(tag, "");
}
public static String inlineIds (@Nonnull String tag, @Nonnull String taggedData) {
return CommonConvert.byteArrayToHex(CommonEncrypt.hashMd5(tag+taggedData));
}
}

View File

@ -0,0 +1,21 @@
package cc.sukazyo.cono.morny.util.tgapi.formatting;
import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.User;
public class TGToString {
public static TGToStringFromChat as (Chat chat) {
return new TGToStringFromChat(chat);
}
public static TGToStringFromUser as (User user) {
return new TGToStringFromUser(user);
}
public static TGToStringFromMessage as (Message message) {
return new TGToStringFromMessage(message);
}
}

View File

@ -0,0 +1,22 @@
package cc.sukazyo.cono.morny.util.tgapi.formatting;
import com.pengrad.telegrambot.model.Chat;
public class TGToStringFromChat {
private final Chat data;
public TGToStringFromChat(Chat chat) {
this.data = chat;
}
public String toStringFullNameId() {
if (data.title() == null) {
throw new IllegalArgumentException("Cannot format private chat to group Name+Id format.");
}
return (data.username() == null) ?
(String.format("%s [%d]", data.title(), data.id())) :
(String.format("%s {%s}[%d]", data.title(), data.username(), data.id()));
}
}

View File

@ -1,7 +1,5 @@
package cc.sukazyo.cono.morny.util.tgapi;
package cc.sukazyo.cono.morny.util.tgapi.formatting;
import cc.sukazyo.untitled.telegram.api.formatting.TGToString;
import cc.sukazyo.untitled.util.telegram.formatting.MsgEscape;
import com.pengrad.telegrambot.model.Message;
import javax.annotation.Nonnull;
@ -12,7 +10,6 @@ public class TGToStringFromMessage extends TGToString {
private final Message message;
public TGToStringFromMessage (@Nonnull Message message) { this.message = message; }
public static TGToStringFromMessage as (@Nonnull Message message) { return new TGToStringFromMessage(message); }
@Nonnull
public String getSenderFirstNameRefHtml () {

View File

@ -0,0 +1,53 @@
package cc.sukazyo.cono.morny.util.tgapi.formatting;
import com.pengrad.telegrambot.model.User;
public class TGToStringFromUser {
private final User data;
public TGToStringFromUser (User user) {
this.data = user;
}
public String fullname () {
return data.firstName() + (data.lastName()==null ? "" : " "+data.lastName());
}
public String fullnameRefHtml () {
return String.format(
"<a href='tg://user?id=%d'>%s</a>",
data.id(),
MsgEscape.escapeHtml(fullname())
);
}
public String fullnameRefMarkdown () {
return String.format(
"[%s](tg://user?id=%d)",
fullname(),
data.id()
);
}
public String firstnameRefHtml () {
return String.format(
"<a href='tg://user?id=%d'>%s</a>",
data.id(),
MsgEscape.escapeHtml(data.firstName())
);
}
public String firstnameRefMarkdown () {
return String.format(
"[%s](tg://user?id=%d)",
data.firstName(),
data.id()
);
}
public String toStringLogTag () {
return (data.username()==null ? fullname()+" " : "@"+data.username()) + "[" + data.id() + "]";
}
}

View File

@ -1,4 +1,4 @@
package cc.sukazyo.cono.morny.util;
package cc.sukazyo.cono.morny.util.tgapi.formatting;
import com.pengrad.telegrambot.model.User;
import okhttp3.OkHttpClient;
@ -11,7 +11,7 @@ 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.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml;
public class TelegramUserInformation {

View File

@ -0,0 +1,18 @@
package cc.sukazyo.cono.morny;
import cc.sukazyo.cono.morny.util.UniversalCommand;
import java.util.*;
public class MornyCLI {
public static void main (String[] args) {
Scanner line = new Scanner(System.in);
System.out.print("$ java -jar morny-coeur-"+GradleProjectConfigures.VERSION+".jar " );
String x = line.nextLine();
ServerMain.main(UniversalCommand.format(x));
}
}

View File

@ -0,0 +1,30 @@
package cc.sukazyo.cono.morny.util;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static cc.sukazyo.cono.morny.util.BiliTool.*;
public class TestBiliTool {
private static final String AV_BV_DATA_CSV = """
17x411w7KC, 170001
1Q541167Qg, 455017605
1mK4y1C7Bz, 882584971
1T24y197V2, 688730800
""";
@ParameterizedTest
@CsvSource(textBlock = AV_BV_DATA_CSV)
void testAvToBv (String bv, int av) {
Assertions.assertEquals(bv, toBv(av));
}
@ParameterizedTest
@CsvSource(textBlock = AV_BV_DATA_CSV)
void testBvToAv (String bv, int av) {
Assertions.assertEquals(av, toAv(bv));
}
}

View File

@ -0,0 +1,50 @@
package cc.sukazyo.cono.morny.util;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static cc.sukazyo.cono.morny.util.CommonConvert.byteArrayToHex;
import static cc.sukazyo.cono.morny.util.CommonConvert.byteToHex;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.params.provider.Arguments.arguments;
public class TestCommonConvert {
@ParameterizedTest
@CsvSource(textBlock = """
0x00, 00
0x01, 01
0x20, 20
0x77, 77
-0x60, a0
0x0a, 0a
-0x01, ff
-0x05, fb
"""
)
void testByteToHex(byte source, String expected) {
assertEquals(expected, byteToHex(source));
}
public static Stream<Arguments> testByteArrayToHexProvider () {
return Stream.of(
arguments(new byte[]{0x00}, "00"),
arguments(new byte[]{(byte)0xff}, "ff"),
arguments(new byte[]{(byte)0xc3}, "c3"),
arguments(new byte[]{}, ""),
arguments(new byte[]{0x30,0x0a,0x00,0x04,(byte)0xb0,0x00}, "300a0004b000"),
arguments(new byte[]{0x00,0x00,0x0a,(byte)0xff,(byte)0xfc,(byte)0xab,(byte)0x00,0x04}, "00000afffcab0004"),
arguments(new byte[]{0x00,0x7c,0x11,0x28,(byte)0x88,(byte)0xa6,(byte)0xfc,0x30}, "007c112888a6fc30")
);
}
@ParameterizedTest
@MethodSource("testByteArrayToHexProvider")
void testByteArrayToHex (byte[] raw, String expected) {
assertEquals(expected, byteArrayToHex(raw));
}
}

View File

@ -0,0 +1,23 @@
package cc.sukazyo.cono.morny.util;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static cc.sukazyo.cono.morny.util.CommonConvert.byteArrayToHex;
import static cc.sukazyo.cono.morny.util.CommonEncrypt.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestCommonEncrypt {
@ParameterizedTest
@SuppressWarnings("UnnecessaryStringEscape")
@CsvSource(textBlock = """
28be57d368b75051da76c068a6733284, '莲子'
9644c5cbae223013228cd528817ba4f5, '莲子\n'
d41d8cd98f00b204e9800998ecf8427e, ''
""")
void testHashMd5_String (String md5, String text) {
assertEquals(md5, byteArrayToHex(hashMd5(text)));
}
}

View File

@ -0,0 +1,36 @@
package cc.sukazyo.cono.morny.util;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static cc.sukazyo.cono.morny.util.CommonFormat.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestCommonFormat {
@ParameterizedTest
@CsvSource(textBlock = """
1664646870402, 8, 2022-10-02 01:54:30:402
1, 8, 1970-01-01 08:00:00:001
0, -1, 1969-12-31 23:00:00:000
"""
)
void testFormatDate (long timestamp, int utfOffset, String expectedHumanReadableTime) {
assertEquals(expectedHumanReadableTime, formatDate(timestamp, utfOffset));
}
@ParameterizedTest
@CsvSource(textBlock = """
100, '100ms'
3000, '3s 0ms'
326117522, '3d 18h 35min 17s 522ms'
53373805, 14h 49min 33s 805ms
""")
// -1, '-1ms' // WARN: maybe sometime an unexpected usage
// -194271974291, '-291ms' //
// """) //
void testFormatDuration (long durationMillis, String humanReadableDuration) {
assertEquals(humanReadableDuration, formatDuration(durationMillis));
}
}