mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
优化代码
This commit is contained in:
parent
ac182bd791
commit
e196b5b141
@ -36,7 +36,7 @@ dependencies {
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
freeCompilerArgs = ["-Xjvm-default=enable"]
|
||||
freeCompilerArgs = ["-Xjvm-default=enable", "-Xuse-experimental=kotlin.Experimental"]
|
||||
}
|
||||
}
|
||||
compileTestKotlin {
|
||||
|
@ -0,0 +1,10 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
|
||||
@UseExperimental(ExperimentalTypeInference::class)
|
||||
internal inline fun <T> list(@BuilderInference block: MutableList<T>.() -> Unit): List<T> {
|
||||
val list = ArrayList<T>()
|
||||
block(list)
|
||||
return list
|
||||
}
|
@ -31,12 +31,11 @@ class DanmakuParser {
|
||||
* 解析弹幕文件
|
||||
*
|
||||
* @param inputStream 输入流, 可以指向任何位置
|
||||
* @param autoClose 是否在解析后自动关闭流
|
||||
*
|
||||
* @return 返回 flags map 与 弹幕列表. 注意, 原始的弹幕顺序是按发送时间来排的, 而非播放器时间.
|
||||
* @return 返回 flags map 与 弹幕序列. 注意, 原始的弹幕顺序是按发送时间来排的, 而非播放器时间.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun parser(inputStream: InputStream, autoClose: Boolean = true): Pair<Map<Long, Int>, List<Danmaku>> {
|
||||
fun parse(inputStream: InputStream): Pair<Map<Long, Int>, Sequence<Danmaku>> {
|
||||
//Json 的长度
|
||||
val jsonLength = inputStream.readUInt()
|
||||
|
||||
@ -74,48 +73,47 @@ class DanmakuParser {
|
||||
|
||||
//json 解析完毕后, 剩下的内容是一个 gzip 压缩过的 xml
|
||||
val reader = GZIPInputStream(inputStream).reader()
|
||||
val danmakus = LinkedList<Danmaku>()
|
||||
//流式解析 xml
|
||||
val xmlEventReader = XMLInputFactory.newInstance().createXMLEventReader(reader)
|
||||
var startD = false //之前解析到的 element 是否是 d
|
||||
var p: String? = null //之前解析到的 p 的值
|
||||
while (xmlEventReader.hasNext()) {
|
||||
val event = xmlEventReader.nextEvent()
|
||||
when (event.eventType) {
|
||||
XMLStreamConstants.START_ELEMENT -> {
|
||||
with(event.asStartElement()) {
|
||||
startD = name.localPart == "d"
|
||||
if (startD) {
|
||||
p = getAttributeByName(P).value
|
||||
//lazy sequence
|
||||
val danmakus = sequence {
|
||||
var startD = false //之前解析到的 element 是否是 d
|
||||
var p: String? = null //之前解析到的 p 的值
|
||||
while (xmlEventReader.hasNext()) {
|
||||
val event = xmlEventReader.nextEvent()
|
||||
when (event.eventType) {
|
||||
XMLStreamConstants.START_ELEMENT -> {
|
||||
with(event.asStartElement()) {
|
||||
startD = name.localPart == "d"
|
||||
if (startD) {
|
||||
p = getAttributeByName(P).value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
XMLStreamConstants.CHARACTERS -> {
|
||||
//如果前一个解析到的是 d 标签, 那么此处得到的一定是 d 标签的 body
|
||||
if (startD) {
|
||||
val danmaku = with(StringTokenizer(p, ",")) {
|
||||
Danmaku(
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken().toLong(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken(),
|
||||
event.asCharacters().data
|
||||
)
|
||||
XMLStreamConstants.CHARACTERS -> {
|
||||
//如果前一个解析到的是 d 标签, 那么此处得到的一定是 d 标签的 body
|
||||
if (startD) {
|
||||
val danmaku = with(StringTokenizer(p, ",")) {
|
||||
Danmaku(
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken().toLong(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken(),
|
||||
event.asCharacters().data
|
||||
)
|
||||
}
|
||||
yield(danmaku)
|
||||
}
|
||||
danmakus.add(danmaku)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//自动关闭流
|
||||
if (autoClose) inputStream.close()
|
||||
|
||||
return danmakuFlags to danmakus
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,7 @@ package com.hiczp.bilibili.api.main
|
||||
|
||||
import com.hiczp.bilibili.api.main.model.*
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.*
|
||||
|
||||
/**
|
||||
* 这也是总站 API
|
||||
@ -138,4 +137,28 @@ interface MainAPI {
|
||||
@Query("size") size: Int = 10,
|
||||
@Query("wid") wid: String? = "78,79,80,81,59"
|
||||
): Deferred<BangumiMore>
|
||||
|
||||
/**
|
||||
* 发送评论
|
||||
* 如果发送根评论则 root 和 parent 为 null
|
||||
* 如果发送子评论则 root 和 parent 均为根评论的 id
|
||||
* 如果在子评论中 at 别人(即对子评论进行评论), 那么 root 为所属根评论的 id, parent 为所 at 的那个评论的 id
|
||||
* at 别人时, 评论的内容必须符合以下格式 "回复 @$username :$message"
|
||||
*
|
||||
* @param message 发送的内容
|
||||
* @param oid aid
|
||||
* @param parent 父评论 id
|
||||
* @param root 根评论 id
|
||||
*/
|
||||
@POST("/x/v2/reply/add")
|
||||
@FormUrlEncoded
|
||||
fun sendReply(
|
||||
@Field("from") from: Int? = null,
|
||||
@Field("message") message: String,
|
||||
@Field("oid") oid: Long,
|
||||
@Field("parent") parent: Long? = null,
|
||||
@Field("plat") plat: Int = 2,
|
||||
@Field("root") root: Long? = null,
|
||||
@Field("type") type: Int = 1
|
||||
): Deferred<SendReplyResponse>
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package com.hiczp.bilibili.api.main.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SendReplyResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String, // 0
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int // 1
|
||||
) {
|
||||
data class Data(
|
||||
@SerializedName("dialog")
|
||||
var dialog: Long, // 0
|
||||
@SerializedName("dialog_str")
|
||||
var dialogStr: String, // 0
|
||||
@SerializedName("parent")
|
||||
var parent: Long, // 0
|
||||
@SerializedName("parent_str")
|
||||
var parentStr: String, // 0
|
||||
@SerializedName("root")
|
||||
var root: Long, // 0
|
||||
@SerializedName("root_str")
|
||||
var rootStr: String, // 0
|
||||
@SerializedName("rpid")
|
||||
var rpid: Long, // 1422858564
|
||||
@SerializedName("rpid_str")
|
||||
var rpidStr: String // 1422858564
|
||||
)
|
||||
}
|
@ -5,15 +5,16 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DanmakuTest {
|
||||
//339.6kib 的弹幕文件在 0.5s 内解析完毕, 通常视频的弹幕不会超过这个容量
|
||||
//339.6kib 的弹幕文件在 0.5s 内解析完毕(i5-4200H), 通常视频的弹幕不会超过这个容量
|
||||
@Test
|
||||
fun fetchAndParseDanmaku() {
|
||||
runBlocking {
|
||||
//著名的炮姐视频 你指尖跃动的电光是我此生不变的信仰
|
||||
bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await().let {
|
||||
DanmakuParser.parser(it.byteStream())
|
||||
}.second.forEach {
|
||||
println("[${it.time}] ${it.content}")
|
||||
val responseBody = bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await()
|
||||
timer {
|
||||
DanmakuParser.parse(responseBody.byteStream()).second.forEach {
|
||||
println("[${it.time}] ${it.content}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
import com.hiczp.bilibili.api.BilibiliClient
|
||||
import com.hiczp.bilibili.api.main.model.ChildReply
|
||||
import com.hiczp.bilibili.api.main.model.Reply
|
||||
import kotlinx.coroutines.Deferred
|
||||
import com.hiczp.bilibili.api.list
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class FetchReplyTest {
|
||||
@ -27,59 +26,61 @@ class FetchReplyTest {
|
||||
//打印一个视频下全部的评论
|
||||
@Test
|
||||
fun printAllReplies() {
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
val aid = 150998L
|
||||
val bilibiliClient = BilibiliClient()
|
||||
val bilibiliClient = BilibiliClient(logLevel = HttpLoggingInterceptor.Level.BASIC)
|
||||
|
||||
var total: Int? = null
|
||||
var next: Long = 0
|
||||
runBlocking {
|
||||
//pageSize=1 来获得评论总楼层数量
|
||||
val reply = bilibiliClient.mainAPI.reply(oid = aid, pageSize = 1).await()
|
||||
//得到评论总数(根评论+子评论)
|
||||
total = reply.data.cursor.allCount
|
||||
//最后一楼
|
||||
next = reply.data.cursor.next
|
||||
}
|
||||
timer {
|
||||
var total: Int? = null
|
||||
var next: Long = 0
|
||||
runBlocking {
|
||||
//pageSize=1 来获得评论总楼层数量
|
||||
val reply = bilibiliClient.mainAPI.reply(oid = aid, pageSize = 1).await()
|
||||
//得到评论总数(根评论+子评论)
|
||||
total = reply.data.cursor.allCount
|
||||
//最后一楼
|
||||
next = reply.data.cursor.next
|
||||
}
|
||||
|
||||
//如果没有评论则不做进一步操作
|
||||
if (total == null) {
|
||||
println("<NoReply>")
|
||||
} else {
|
||||
val results = ArrayList<Deferred<List<Pair<Reply.Data.Reply, Deferred<ChildReply>?>>>>()
|
||||
//访问每个页
|
||||
//如果根评论数量刚好能被 50 整除, 那么最后一次访问时的 next 为 1, 这会导致 replies 为 null
|
||||
//因此 downTo 2
|
||||
for (i in next + 1 downTo 2 step 50) {
|
||||
GlobalScope.async {
|
||||
val replies = bilibiliClient.mainAPI.reply(oid = aid, next = i, pageSize = 50).await().data.replies
|
||||
//获取该页的评论(复数)的子评论
|
||||
replies!!.map {
|
||||
it to if (it.rcount == 0) {
|
||||
null
|
||||
} else {
|
||||
bilibiliClient.mainAPI.childReply(oid = aid, root = it.rpid, size = Int.MAX_VALUE)
|
||||
//如果没有评论则不做进一步操作
|
||||
if (total == null) {
|
||||
println("<NoReply>")
|
||||
return@timer
|
||||
}
|
||||
|
||||
val pages = list {
|
||||
//访问每个页
|
||||
//如果根评论数量刚好能被 50 整除, 那么最后一次访问时的 next 为 1, 这会导致 replies 为 null
|
||||
//因此 downTo 2
|
||||
for (i in next + 1 downTo 2 step 50) {
|
||||
GlobalScope.async {
|
||||
//一个页下的所有根评论
|
||||
val replies = bilibiliClient.mainAPI.reply(oid = aid, next = i, pageSize = 50).await().data.replies
|
||||
//获取根评论(复数)的子评论
|
||||
replies!!.map {
|
||||
it to if (it.rcount == 0) {
|
||||
null
|
||||
} else {
|
||||
bilibiliClient.mainAPI.childReply(oid = aid, root = it.rpid, size = Int.MAX_VALUE).await().data.root.replies
|
||||
}
|
||||
}
|
||||
}.let {
|
||||
add(it)
|
||||
}
|
||||
}.let {
|
||||
results.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
//join
|
||||
runBlocking {
|
||||
results.forEach { deferred ->
|
||||
deferred.await().forEach { (reply, childReplyResponse) ->
|
||||
println("#${reply.floor} [${reply.member.uname}] ${reply.content.message}")
|
||||
childReplyResponse?.await()?.data?.root?.replies?.forEach {
|
||||
pages.forEach { page ->
|
||||
page.await().forEach { (rootReply, childReplies) ->
|
||||
//输出这一页的评论
|
||||
println("#${rootReply.floor} [${rootReply.member.uname}] ${rootReply.content.message}")
|
||||
childReplies?.forEach {
|
||||
println("└──#${it.floor} [${it.member.uname}] ${it.content.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val end = System.currentTimeMillis()
|
||||
println("Done in ${end - start} ms")
|
||||
}
|
||||
}
|
||||
|
11
src/test/kotlin/com/hiczp/bilibili/api/test/TestExtension.kt
Normal file
11
src/test/kotlin/com/hiczp/bilibili/api/test/TestExtension.kt
Normal file
@ -0,0 +1,11 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
/**
|
||||
* 土制切面
|
||||
*/
|
||||
inline fun timer(block: () -> Unit) {
|
||||
val start = System.currentTimeMillis()
|
||||
block()
|
||||
val end = System.currentTimeMillis()
|
||||
println("Done in ${end - start} ms")
|
||||
}
|
Loading…
Reference in New Issue
Block a user