mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-21 18:54:54 +08:00
Update the algorithm for 2^51 ranges AV/BV conversion.
This commit is contained in:
parent
9814b3ccab
commit
35c9eeb9a4
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,7 +9,9 @@
|
||||
/build/
|
||||
/bin/
|
||||
/out/
|
||||
target/
|
||||
.metals/
|
||||
.bsp/
|
||||
.bloop/
|
||||
.project
|
||||
lcoal.properties
|
||||
|
@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur
|
||||
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
||||
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
||||
|
||||
VERSION = 1.3.1
|
||||
VERSION = 1.3.2
|
||||
|
||||
USE_DELTA = false
|
||||
VERSION_DELTA =
|
||||
|
@ -7,7 +7,6 @@ import com.pengrad.telegrambot.model.Update
|
||||
import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode}
|
||||
|
||||
import scala.language.postfixOps
|
||||
import scala.util.matching.Regex
|
||||
|
||||
class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
|
||||
|
||||
@ -16,7 +15,6 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
|
||||
private val ID_PREFIX_BILI_AV = "[morny/share/bili/av]"
|
||||
private val ID_PREFIX_BILI_BV = "[morny/share/bili/bv]"
|
||||
private val LINK_PREFIX = "https://bilibili.com/video/"
|
||||
private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:www\\.)?bilibili\\.com(?:/s)?/video/((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(\\?(?:p=(\\d+))?.*)?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$"r
|
||||
private val SHARE_FORMAT_HTML = "<a href='%s'>%s</a>"
|
||||
|
||||
override def query (event: Update): List[InlineQueryUnit[_]] | Null = {
|
||||
|
@ -13,9 +13,9 @@ object BilibiliForms {
|
||||
|
||||
case class BiliVideoId (av: Long, bv: String, part: Int|Null = null)
|
||||
|
||||
private val REGEX_BILI_ID = "^((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$"r
|
||||
private val REGEX_BILI_ID = "^((?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))$"r
|
||||
private val REGEX_BILI_V_PART_IN_URL_PARAM = "(?:&|^)p=(\\d+)"r
|
||||
private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:(?:www\\.)?bilibili\\.com(?:/s)?/video/|b23\\.tv/)((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(?:\\?((?:p=(\\d+))?.*))?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$" r
|
||||
private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:(?:www\\.)?bilibili\\.com(?:/s)?/video/|b23\\.tv/)((?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))/?(?:\\?((?:p=(\\d+))?.*))?|(?:av|AV)(\\d{1,16})|(?:bv1|BV1)([A-HJ-NP-Za-km-z1-9]{9}))$" r
|
||||
|
||||
/** parse a Bilibili video link to a [[BiliVideoId]] format Bilibili Video Id.
|
||||
*
|
||||
@ -34,7 +34,7 @@ object BilibiliForms {
|
||||
case REGEX_BILI_VIDEO(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) =>
|
||||
|
||||
val av = select(_url_av, _raw_av)
|
||||
val bv = select(_url_bv, _raw_bv)
|
||||
val bv = "1" + select(_url_bv, _raw_bv)
|
||||
|
||||
val part_part = if (_url_param == null) null else
|
||||
REGEX_BILI_V_PART_IN_URL_PARAM.findFirstMatchIn(_url_param) match
|
||||
|
@ -2,8 +2,6 @@ package cc.sukazyo.cono.morny.util
|
||||
|
||||
import cc.sukazyo.cono.morny.util.UseMath.**
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/** Utils about $Bilibili
|
||||
*
|
||||
* contains utils:
|
||||
@ -15,29 +13,35 @@ import scala.collection.mutable
|
||||
*
|
||||
* @define AvBvFormat
|
||||
* === About AV/BV id format ===
|
||||
* the AV id is a number; the BV id is a special 10 digits base58 number, it shows as String
|
||||
* in programming.
|
||||
* the AV id is a int64 number, the max value is 2<sup>51</sup>, and should not be smaller that `1`; the BV
|
||||
* id is a special 10 digits base58 number with the first character is constant `1`, it shows as String in
|
||||
* programming.
|
||||
*
|
||||
* e.g. while the link ''`https://www.bilibili.com/video/BV17x411w7KC/`'' shows
|
||||
* the same with ''`https://www.bilibili.com/video/av170001/`'', the AV id
|
||||
* is __`170001`__, the BV id is __`BV17x411w7KC`__.
|
||||
*
|
||||
* @define AvBvSeeAlso [[https://www.zhihu.com/question/381784377/answer/1099438784 mcfx的回复: 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」?]]
|
||||
* @todo Maybe make a class `AV`/`BV` and implement the parse in the class
|
||||
* These algorithms accept and return a AV id as a [[Long]] number, and BV id as a 10 digits base58 [[String]]
|
||||
* without the `BV` prefix. For example, the `BV17x411w7KC` will be `"17x411w7KC"` [[String]] in this format, and
|
||||
* the `av170001` should be `170001L` [[Long]] val.
|
||||
*
|
||||
* @define AvBvSeeAlso
|
||||
* [bvid说明 - 哔哩哔哩-API收集整理](https://socialsisteryi.github.io/bilibili-API-collect/docs/misc/bvid_desc.html)
|
||||
* [旧版本:mcfx 的回答...](https://www.zhihu.com/question/381784377/answer/1099438784)
|
||||
*
|
||||
*/
|
||||
object BiliTool {
|
||||
|
||||
private val V_CONV_XOR = 177451812L
|
||||
private val V_CONV_ADD = 8728348608L
|
||||
private val V_CONV_XOR: Long = 23442827791579L
|
||||
|
||||
private val X_AV_MAX = Math.pow(2, 30).toLong
|
||||
private val X_AV_ALT = Int.MaxValue.toLong + 1
|
||||
private val X_AV_MAX = 1L << 51
|
||||
private val X_AV_MASK: Long = X_AV_MAX - 1
|
||||
|
||||
private val BB58_TABLE_REV: Map[Char, Int] = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF".toCharArray.zipWithIndex.toMap
|
||||
private val BB58_TABLE_REV: Map[Char, Int] = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf".toCharArray.zipWithIndex.toMap
|
||||
private val BB58_TABLE: Map[Int, Char] = BB58_TABLE_REV.map((k,v) => (v, k))
|
||||
private val BB58_TABLE_SIZE: Long = BB58_TABLE.size
|
||||
private val BV_TEMPLATE = "1 4 1 7 "
|
||||
private val BV_TEMPLATE_FILTER = Array(9, 8, 1, 6, 2, 4)
|
||||
private val BB58_BASE: Long = BB58_TABLE.size
|
||||
private val BV_TEMPLATE: String = "1---------"
|
||||
private val BV_TEMPLATE_FILTER: Array[Int] = Array(9, 8, 1, 6, 2, 4, 3, 5, 7)
|
||||
|
||||
/** Error of illegal BV id.
|
||||
*
|
||||
@ -45,7 +49,7 @@ object BiliTool {
|
||||
* @param bv the source illegal BV id.
|
||||
* @param reason why it is illegal.
|
||||
*/
|
||||
class IllegalFormatException private (bv: String, reason: String)
|
||||
class IllegalBVFormatException private (bv: String, reason: String)
|
||||
extends RuntimeException (s"`$bv is not a valid 10 digits base58 BV id: $reason`") {
|
||||
|
||||
/** Error of illegal BV id, where the reason is the BV id is not 10 digits.
|
||||
@ -64,57 +68,81 @@ object BiliTool {
|
||||
*/
|
||||
def this (bv: String, c: Char, location: Int) =
|
||||
this(bv, s"char `$c` is not in base58 char table (in position $location)")
|
||||
|
||||
/** Error of illegal BV id, where the reason is the BV id is not started with `1`.
|
||||
*
|
||||
* @param bv the source of illegal BV id.
|
||||
*/
|
||||
def this (bv: String) =
|
||||
this(bv, s"given BV id $bv is not started with 1 which is required in current version.")
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Error of illegal AV id.
|
||||
*
|
||||
* @constructor Build a error with illegal AV details.
|
||||
* @param av the source illegal AV id.
|
||||
* @param reason why it is illegal.
|
||||
*/
|
||||
class IllegalAVFormatException private (av: Long, reason: String)
|
||||
extends RuntimeException(s"`$av is not a valid AV id: $reason`")
|
||||
object IllegalAVFormatException:
|
||||
/** Error of illegal AV id, where the reason is the AV id is too large. */
|
||||
def thusTooLarge (av: Long) =
|
||||
new IllegalAVFormatException(av, s"Given AV id $av is too large, should not be larger than 2^51($X_AV_MAX)")
|
||||
/** Error of illegal AV id, where the reason is the AV id is too small. */
|
||||
def thusTooSmall (av: Long) =
|
||||
new IllegalAVFormatException(av, s"Given AV id $av is too small, should not be smaller than 1")
|
||||
|
||||
/** Convert an AV video id format to BV video id format for $Bilibili
|
||||
*
|
||||
* $AvBvFormat
|
||||
*
|
||||
* this method '''available while the __av-id < 2^27^__''', while it theoretically
|
||||
* available when the av-id < 2^30^. Meanwhile some digits of the BV id is a fixed
|
||||
* value (like the [[BV_TEMPLATE]] shows) -- input __bv__ can do not follow the format,
|
||||
* but it will almost certainly gives a wrong AV id (because the fixed number is not
|
||||
* processed at all!)
|
||||
*
|
||||
* @see $AvBvSeeAlso
|
||||
*
|
||||
* @param bv a BV id, which should be exactly 10 digits and all chars should be
|
||||
* a legal base58 char (which means can be found in [[BB58_TABLE]]).
|
||||
* otherwise, an [[IllegalFormatException]] will be thrown.
|
||||
* a legal base58 char (which means can be found in [[BB58_TABLE]]) and
|
||||
* the first character should must be 1. BV id in this format does NOT
|
||||
* contains `BV` prefix. Otherwise, an [[IllegalBVFormatException]]
|
||||
* will be thrown.
|
||||
* @return an AV id which will shows the save video of input __bv__ in $Bilibili
|
||||
* @throws IllegalFormatException when the input __bv__ is not a legal 10 digits base58
|
||||
* formatted BV id.
|
||||
* @throws IllegalBVFormatException when the input __bv__ is not a legal 10 digits base58
|
||||
* formatted BV id.
|
||||
*/
|
||||
@throws[IllegalFormatException]
|
||||
@throws[IllegalBVFormatException]
|
||||
def toAv (bv: String): Long = {
|
||||
var av = 0L
|
||||
if (bv.length != 10) throw IllegalFormatException(bv, bv.length)
|
||||
for (i <- BV_TEMPLATE_FILTER.indices) {
|
||||
val _get = BV_TEMPLATE_FILTER(i)
|
||||
val tableToken = BB58_TABLE_REV get bv(_get)
|
||||
if tableToken isEmpty then throw IllegalFormatException(bv, bv(_get), _get)
|
||||
av = av + (tableToken.get.toLong * (BB58_TABLE_SIZE**i).toLong)
|
||||
}
|
||||
av = (av - V_CONV_ADD) ^ V_CONV_XOR
|
||||
if (av < 0)
|
||||
av+ X_AV_ALT
|
||||
else av
|
||||
if (bv.length != 10) throw IllegalBVFormatException(bv, bv.length)
|
||||
if (bv(0) != '1') throw IllegalBVFormatException(bv)
|
||||
val _bv = bv.toCharArray
|
||||
val av =
|
||||
( for (i <- BV_TEMPLATE_FILTER.indices) yield {
|
||||
val _get = BV_TEMPLATE_FILTER(i)
|
||||
val tableToken = BB58_TABLE_REV get _bv(_get)
|
||||
if tableToken isEmpty then throw IllegalBVFormatException(bv, _bv(_get), _get)
|
||||
tableToken.get * (BB58_BASE**i)
|
||||
} ).sum
|
||||
(av & X_AV_MASK) ^ V_CONV_XOR
|
||||
}
|
||||
|
||||
/** Convert an AV video format to a BV video format for $Bilibili.
|
||||
*
|
||||
* this method '''available while the __av-id < 2^27^__''', while it theoretically
|
||||
* available when the av-id < 2^30^.
|
||||
* $AvBvFormat
|
||||
*
|
||||
* @see $AvBvSeeAlso
|
||||
*
|
||||
* @param av an AV id.
|
||||
* @return a BV id which will shows the save video of input __av__ in $Bilibili
|
||||
* @return a BV id which will shows the save video of input __av__ in $Bilibili. A 10 digits
|
||||
* base58 formatted BV id, does NOT contains `BV` prefix.
|
||||
*/
|
||||
def toBv (av: Long): String = {
|
||||
val __av =if (av > X_AV_MAX) av - X_AV_ALT else av
|
||||
val _av = (__av^V_CONV_XOR)+V_CONV_ADD
|
||||
if (av > X_AV_MAX) throw IllegalAVFormatException.thusTooLarge(av)
|
||||
if (av < 1) throw IllegalAVFormatException.thusTooSmall(av)
|
||||
var _av = (X_AV_MAX | av) ^ V_CONV_XOR
|
||||
val bv = Array(BV_TEMPLATE:_*)
|
||||
for (i <- BV_TEMPLATE_FILTER.indices) {
|
||||
bv(BV_TEMPLATE_FILTER(i)) = BB58_TABLE( (_av/(BB58_TABLE_SIZE**i) % BB58_TABLE_SIZE) toInt )
|
||||
bv(BV_TEMPLATE_FILTER(i)) = BB58_TABLE((_av % BB58_BASE).toInt)
|
||||
_av /= BB58_BASE
|
||||
}
|
||||
String copyValueOf bv
|
||||
}
|
||||
|
@ -15,11 +15,12 @@ object UseMath {
|
||||
@targetName("pow")
|
||||
def ** (other: Int): Double = Math.pow(self, other)
|
||||
}
|
||||
|
||||
extension (self: Long) {
|
||||
@targetName("pow")
|
||||
def ** (other: Long): Long =
|
||||
Math.pow(self, other).toLong
|
||||
var x = 1L
|
||||
for (_ <- 0L until other) x *= self
|
||||
x
|
||||
}
|
||||
|
||||
extension (base: Int) {
|
||||
|
@ -17,6 +17,7 @@ class BiliToolTest extends MornyTests with TableDrivenPropertyChecks {
|
||||
("1DB421k7zX", 1350018000L),
|
||||
("19m411D7wx", 1900737470L),
|
||||
("1LQ4y1A7im", 709042411L),
|
||||
("1L9Uoa9EUx", 111298867365120L),
|
||||
)
|
||||
|
||||
forAll (examples) { (bv, av) => s"while using av$av/BV$bv :" - {
|
||||
@ -26,7 +27,7 @@ class BiliToolTest extends MornyTests with TableDrivenPropertyChecks {
|
||||
}}
|
||||
|
||||
"BV with unsupported length :" - {
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toAv, IllegalFormatException}
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toAv, IllegalBVFormatException}
|
||||
val examples = Table(
|
||||
"bv",
|
||||
"12345",
|
||||
@ -39,7 +40,7 @@ class BiliToolTest extends MornyTests with TableDrivenPropertyChecks {
|
||||
)
|
||||
forAll(examples) { bv =>
|
||||
s"length ${bv.length} should throws IllegalFormatException" in:
|
||||
an [IllegalFormatException] should be thrownBy toAv(bv)
|
||||
an [IllegalBVFormatException] should be thrownBy toAv(bv)
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,16 +53,45 @@ class BiliToolTest extends MornyTests with TableDrivenPropertyChecks {
|
||||
("1mK4O1C7Bl", "l"),
|
||||
("1--4O1C7Bl", "[symbols]")
|
||||
)
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toAv, IllegalFormatException}
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toAv, IllegalBVFormatException}
|
||||
forAll(examples) { (bv, with_sp) =>
|
||||
s"'$with_sp' should throws IllegalFormatException" in:
|
||||
an [IllegalFormatException] should be thrownBy toAv(bv)
|
||||
s"BV id with '$with_sp' should throws IllegalBVFormatException" in:
|
||||
an [IllegalBVFormatException] should be thrownBy toAv(bv)
|
||||
}
|
||||
}
|
||||
|
||||
"BV id must started with `1` in current version" in {
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toAv, IllegalBVFormatException}
|
||||
an [IllegalBVFormatException] should be thrownBy toAv("2mK4y1C7Bz")
|
||||
}
|
||||
|
||||
"AV id must not smaller that `1`" in {
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toBv, IllegalAVFormatException}
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(0)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(-826624291)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(-296798903L)
|
||||
}
|
||||
|
||||
s"AV id must not bigger that 2^51 (or ${1L << 51})" in {
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toBv, IllegalAVFormatException}
|
||||
an [IllegalAVFormatException] should (not be thrownBy( toBv(1L << 51) ))
|
||||
an [IllegalAVFormatException] should be thrownBy toBv((1L << 51) + 1)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 52)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 53)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 54)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 55)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 56)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 57)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 58)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 59)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 60)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 61)
|
||||
an [IllegalAVFormatException] should be thrownBy toBv(1L << 62)
|
||||
}
|
||||
|
||||
"av/bv converting should be reversible" in {
|
||||
for (_ <- 1 to 20) {
|
||||
val rand_av = Random.between(0, 999999999L)
|
||||
val rand_av = Random.between(1, (1L<<51)+1)
|
||||
import cc.sukazyo.cono.morny.util.BiliTool.{toAv, toBv}
|
||||
val my_bv = toBv(rand_av)
|
||||
toAv(my_bv) shouldEqual rand_av
|
||||
|
Loading…
Reference in New Issue
Block a user