diff --git a/gradle.properties b/gradle.properties index 9f8614b..1f88aa7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ ## Core -VERSION = 0.8.0.9 +VERSION = 0.8.0.10 CODENAME = putian diff --git a/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java b/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java index 4d26476..d4b2afc 100644 --- a/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java +++ b/src/main/java/cc/sukazyo/cono/morny/GradleProjectConfigures.java @@ -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.8.0.9"; + public static final String VERSION = "0.8.0.10"; public static final String CODENAME = "putian"; - public static final long COMPILE_TIMESTAMP = 1666081545158L; + public static final long COMPILE_TIMESTAMP = 1666096021418L; } diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/query/MornyQueries.java b/src/main/java/cc/sukazyo/cono/morny/bot/query/MornyQueries.java index a561303..b0dc135 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/query/MornyQueries.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/query/MornyQueries.java @@ -15,6 +15,7 @@ public class MornyQueries { queryInstances.add(new RawText()); queryInstances.add(new MyInformation()); queryInstances.add(new ShareToolTwitter()); + queryInstances.add(new ShareToolBilibili()); } @Nonnull diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.java b/src/main/java/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.java new file mode 100644 index 0000000..a5e1ced --- /dev/null +++ b/src/main/java/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.java @@ -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 = "%s"; + + @Nullable + @Override + public List> 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> 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; + } + +} diff --git a/src/main/java/cc/sukazyo/cono/morny/util/BiliTool.java b/src/main/java/cc/sukazyo/cono/morny/util/BiliTool.java new file mode 100644 index 0000000..ef36993 --- /dev/null +++ b/src/main/java/cc/sukazyo/cono/morny/util/BiliTool.java @@ -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 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 Bilibili AV video id format to BV id format. + *

+ * the AV id is a number; the BV id is a special base58 number, it shows as String in programming.
+ * eg:
+ * while the link {@code https://www.bilibili.com/video/BV17x411w7KC/} + * shows the same with {@code https://www.bilibili.com/video/av170001/}, + * the AV id is {@code 170001}, the BV id is {@code 17x411w7KC} + *

+ * for now , the BV id has 10 digits. + * the method available while the av-id < 2^27, while it theoretically available when the av-id < 2^30. + * + * @see mcfx的回复: 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」? + * + * @param bv the BV id, a string in (a special) base58 number format, without "BV" prefix. + * @return the AV id corresponding to this bv id in Bilibili, 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 Bilibili BV video id format to AV id format. + *

+ * the AV id is a number; the BV id is a special base58 number, it shows as String in programming.
+ * eg:
+ * while the link {@code https://www.bilibili.com/video/BV17x411w7KC/} + * shows the same with {@code https://www.bilibili.com/video/av170001/}, + * the AV id is {@code 170001}, the BV id is {@code 17x411w7KC} + *

+ * for now , the BV id has 10 digits. + * the method available while the av-id < 2^27, while it theoretically available when the av-id < 2^30. + * + * @see mcfx的回复: 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」? + * + * @param av the (base10) AV id. + * @return the AV id corresponding to this bv id in Bilibili, + * as a (special) base 58 number format without "BV" prefix. + */ + @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); + } + +} diff --git a/src/test/java/cc/sukazyo/cono/morny/util/TestBiliTool.java b/src/test/java/cc/sukazyo/cono/morny/util/TestBiliTool.java new file mode 100644 index 0000000..125d9c6 --- /dev/null +++ b/src/test/java/cc/sukazyo/cono/morny/util/TestBiliTool.java @@ -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)); + } + +}