yamibo-api
1.1.16indexedPurely functional, highly asynchronous forum client handling network requests, session management and HTML parsing into strongly-typed DTOs, with async-first API and safe sealed-result handling.
Purely functional, highly asynchronous forum client handling network requests, session management and HTML parsing into strongly-typed DTOs, with async-first API and safe sealed-result handling.
| Dependency | Supported Version |
|---|---|
| Kotlin | 2.4.10+ |
| Gradle | 8.14.3+ |
Add the dependency to your build.gradle.kts:
dependencies {
implementation("io.github.littlesurvival:yamibo-api:$version")
}
Include the following in your pom.xml:
<dependency>
<groupId>io.github.littlesurvival</groupId>
<artifactId>yamibo-api</artifactId>
<version>$version</version>
</dependency>
The entry point of the library is the YamiboClient. Most Yamibo routes require authentication, so you must supply the user's cookie.
import io.github.littlesurvival.YamiboClient
// 1. Create a client instance
val client = YamiboClient(
timeoutMillis = 30_000L // 30 seconds timeout
)
// 2. Set the user authentication cookie
client.setCookie("your_user_cookie_here")
All methods return a YamiboResult<T>, ensuring you handle all edge cases elegantly.
Fetching a Forum Page:
import io.github.littlesurvival.dto.value.ForumId
suspend fun loadForum() {
// Fetch forum layout and threads on page 1
val forumResult = client.fetchForumById(ForumId(5), page = 1)
}
Fetching a Thread Context:
import io.github.littlesurvival.dto.value.ThreadId
suspend fun loadThread() {
// Fetch posts and details within a specific thread
val threadResult = client.fetchThreadById(ThreadId(12345), page = 1)
}
Fetching User Profile:
suspend fun loadProfile() {
// Requires a valid cookie to parse current user details
val profileResult = client.fetchProfileInfo()
}
Searching the Forum:
import io.github.littlesurvival.dto.value.FormHash
suspend fun doSearch() {
val searchResult = client.fetchSearch(
query = "Yuri",
forumId = null, // Optional: Search within a specific forum
formHash = FormHash("your_form_hash") // Security token
)
}
Interactions (Replying, Rating, Favorites):
import io.github.littlesurvival.dto.value.PostId
suspend fun interact() {
// Add thread to favorites
client.fetchAddFavorite(ThreadId(12345), FormHash("your_form_hash"))
client.fetchReplyPost(ThreadId(), PostId(), , FormHash())
client.fetchRatePost(ThreadId(), PostId(), score = , reason = , FormHash())
}
Use one shared YamiboClient for API calls and mount one host behind the app content. The host creates a platform WebView only while recovering an observed Baidu NOX challenge; it never presents WAF-specific UI.
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
androidx.compose.runtime.remember
androidx.compose.ui.Modifier
io.github.littlesurvival.YamiboClient
io.github.littlesurvival.waf.YamiboWafChallengeHost
{
client = remember { YamiboClient(timeoutMillis = ) }
DisposableEffect(client) { onDispose { client.close() } }
Box(Modifier.fillMaxSize()) {
YamiboWafChallengeHost(client, isForeground, Modifier.fillMaxSize())
AppContent(client)
}
}
Successful recovery only makes the current load take longer: the host polls the platform cookie store without waiting for the destination page to finish, verifies nox_jst_v1, and replays an eligible request at most once. The client keeps one composed Cookie header and replaces only its entry; WAF-host guest cookies are never imported. Routine calls preserve the current NOX entry. When an app-owned trusted login or sign-in WebView produces a complete cookie snapshot, call so a valid newer NOX entry can replace it explicitly. If silent recovery fails, the API returns ; keep the existing failure UI and let the user refresh to fetch again. Headless/background clients never create a WebView. Call during logout to clear authentication state while preserving NOX. Use only for a full network-cookie reset. disables automatic recovery.
| 依賴項目 | 支援版本 |
|---|---|
| Kotlin | 2.4.10+ |
| Gradle | 8.14.3+ |
| Java JVM 目標 |
請在模組內的 build.gradle.kts 新增依賴:
dependencies {
implementation("io.github.littlesurvival:yamibo-api:$version")
}
如果你的專案使用 Maven 建置環境,請在 pom.xml 加入配置:
<dependency>
<groupId>io.github.littlesurvival</groupId>
<artifactId>yamibo-api</artifactId>
<version>$version</version>
</dependency>
操作本函式庫最核心的點位就是 YamiboClient。由於大多數的 Yamibo 網站路由都需要論壇使用者權限,你必須利用方法設定有效的使用者 Cookie。
import io.github.littlesurvival.YamiboClient
// 1. 建立 Client 實體物件
val client = YamiboClient(
timeoutMillis = 30_000L // 設定超時時間為 30 秒鐘
)
// 2. 寫入使用者的認證 Cookie
client.setCookie("請輸入你的_user_cookie")
所有牽涉到網路的 API 方法都會回傳 YamiboResult<T>,這項類別會幫助你應對各種網路和未知的解析情況。
讀取特定看板 (Forum):
import io.github.littlesurvival.dto.value.ForumId
suspend fun loadForum() {
// 取得版塊編號為 5 的第一頁主題與貼文列表
val forumResult = client.fetchForumById(ForumId(5), page = 1)
}
讀取討論串內容 (Thread):
import io.github.littlesurvival.dto.value.ThreadId
suspend fun loadThread() {
// 取得特定討論串的內容與它的各樓層回覆列表
val threadResult = client.fetchThreadById(ThreadId(12345), page = 1)
}
取得當前使用者資訊 (Profile):
suspend fun loadProfile() {
// 發出請求,解析並獲得當前登入使用者的個人資訊、頭像與相關統計數據
val profileResult = client.fetchProfileInfo()
}
全站或特定看板內搜尋 (Search):
import io.github.littlesurvival.dto.value.FormHash
suspend fun doSearch() {
val searchResult = client.fetchSearch(
query = "百合",
forumId = null, // 選填項目:可指定要搜尋的看板 ID
formHash = FormHash("請填入你的_form_hash") // 必要的安全驗證碼
)
}
各類使用者網頁操作 (發出回覆、給予評分、收藏文章):
import io.github.littlesurvival.dto.value.PostId
suspend fun interact() {
// 將文章加入至我的使用者收藏內
client.fetchAddFavorite(ThreadId(12345), FormHash("你的_form_hash"))
client.fetchReplyPost(ThreadId(), PostId(), , FormHash())
client.fetchRatePost(ThreadId(), PostId(), score = , reason = , FormHash())
}
API 呼叫與 Host 必須共用同一個 YamiboClient,並把 Host 排在 App 內容後方。Host 只在處理已觀察到的百度 NOX challenge 時建立系統 WebView,全程不顯示 WAF 專用 UI。
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
androidx.compose.ui.Modifier
io.github.littlesurvival.YamiboClient
io.github.littlesurvival.waf.YamiboWafChallengeHost
{
client = remember { YamiboClient(timeoutMillis = ) }
DisposableEffect(client) { onDispose { client.close() } }
Box(Modifier.fillMaxSize()) {
YamiboWafChallengeHost(client, isForeground, Modifier.fillMaxSize())
AppContent(client)
}
}
恢復成功時,使用者只會感覺本次載入比較久:Host 不等待目的頁面載入完成,會持續讀取平台 Cookie store,驗證 nox_jst_v1 後最多重送一次符合政策的請求。Client 內只維護一份合成 Cookie header,恢復時只替換其中的 nox_jst_v1,不會匯入 WAF Host 產生的 guest cookies。一般 setCookie() 會保留 Client 現有 NOX;若 App 自己的可信登入/簽到 WebView 剛產生完整 cookie snapshot,則呼叫 client.setCookie(cookie, importNox = true),讓其中有效的新 NOX 明確覆蓋。若 silent recovery 失敗,API 回傳 ;前端維持原本的失敗畫面,讓使用者 refresh 後重新 fetch 即可。背景或無 UI client 不會建立 WebView。登出時呼叫 ,只清除登入狀態並保留 NOX;只有完整重設網路 Cookie 時才使用 。 可停用自動恢復。
ProfilePage, ThreadPage, and ForumPage data immediately.YamiboResult sealed class wrapping Success, Failure, Maintenance, and NotLoggedIn states.| 11+ |
import io.github.littlesurvival.core.YamiboResult
suspend fun fetchYamiboHome() {
when (val result = client.fetchHomePage()) {
is YamiboResult.Success -> {
val homePage = result.value
println("Welcome! Found ${homePage.forums.size} forums.")
}
is YamiboResult.NotLoggedIn -> {
println("Error: Cookie expired or invalid.")
}
is YamiboResult.Maintenance -> {
println("Yamibo is currently under maintenance.")
}
is YamiboResult.NoPermission -> {
println("Permission denied: ${result.message()}")
}
is YamiboResult.WafChallenge -> {
println("Connection verification failed; refresh to retry: ${result.message()}")
}
is YamiboResult.Failure -> {
println("Network or parsing failed: ${result.message()}")
}
}
}
nox_jst_v1setCookie()client.setCookie(cookie, importNox = true)YamiboResult.WafChallengeclient.clearCookies()client.clearCookies(clearNox = true)WafRecoveryConfig(enabled = false)ProfilePage、ThreadPage 與 ForumPage 等豐富的結構化資料表。YamiboResult 封裝,妥善且優雅地處理 Success (成功)、Failure (失敗)、Maintenance (維護中) 以及 NotLoggedIn (未登入) 等各種極端狀態。| 11+ |
import io.github.littlesurvival.core.YamiboResult
suspend fun fetchYamiboHome() {
when (val result = client.fetchHomePage()) {
is YamiboResult.Success -> {
val homePage = result.value
println("歡迎回來!目前論壇共有 ${homePage.forums.size} 個看板。")
}
is YamiboResult.NotLoggedIn -> {
println("錯誤:Cookie 已過期或是你尚未登入。")
}
is YamiboResult.Maintenance -> {
println("當前 Yamibo 正在進行系統維護中。")
}
is YamiboResult.NoPermission -> {
println("權限不足:${result.message()}")
}
is YamiboResult.WafChallenge -> {
println("連線驗證失敗,請 refresh 後重試:${result.message()}")
}
is YamiboResult.Failure -> {
println("網路連線錯誤或是資料解析異常:${result.message()}")
}
}
}
YamiboResult.WafChallengeclient.clearCookies()client.clearCookies(clearNox = true)WafRecoveryConfig(enabled = false)Surfaced from shared tags and platforms — no rankings paid for.