diff --git a/docs/message/private_msg.md b/docs/message/private_msg.md
index 6d39e42..edc113f 100644
--- a/docs/message/private_msg.md
+++ b/docs/message/private_msg.md
@@ -738,7 +738,7 @@ curl -G 'https://api.vc.bilibili.com/session_svr/v1/session_svr/session_detail'
| ----------------- | ---- | ---------------- | ------ | ------------------------------------------------------ |
| talker_id | num | 聊天对象的id | 必要 | `session_type` 为 `1` 时表示用户 mid,为 `2` 时表示粉丝团 id |
| session_type | num | 聊天对象的类型 | 必要 | 1:用户
2:粉丝团 |
-| size | num | 返回消息数量 | 非必要 | 默认为 20,最大为 200 |
+| size | num | 返回消息数量 | 非必要 | 默认为 0,最大为 200
本参数不存在时,只返回系统提示 |
| begin_seqno | num | 开始的序列号 | 非必要 | 提供本参数时返回以本序列号开始(不包括本序列号)的消息 |
| end_seqno | num | 结束的序列号 | 非必要 | 提供本参数时返回以本序列号结束(不包括本序列号)的消息 |
| sender_device_id | num | 发送者设备 | 非必要 | 默认为 `1` |
@@ -1253,10 +1253,22 @@ curl 'https://api.vc.bilibili.com/session_svr/v1/session_svr/update_ack' \
认证方式:Cookie(SESSDATA)
+鉴权方式:[Wbi 签名](../misc/sign/wbi.md)
+
**仅支持发送 `msg[msg_type]` 为 `1`、`2` 或 `5` 的私信**
调用该接口会将该会话设置为已读
+**URL参数:**
+
+| 参数名 | 类型 | 内容 | 必要性 | 备注 |
+| --- | --- | --- | --- | --- |
+| w_sender_uid | num | 发送者mid | 必要 | 必须为自己的 mid |
+| w_receiver_id | num | 接收者id | 必要 | 请求参数 `msg[receiver_type]` 为 `1` 时表示用户 mid,为 `2` 时表示粉丝团 id |
+| w_dev_id | str | 设备id | 必要 | 实质上即 UUID(版本 4),**生成方式见下** |
+| w_rid | str | Wbi 签名 | 必要 | 参见 [Wbi 签名](../misc/sign/wbi.md) |
+| wts | str | UNIX 秒级时间戳 | 必要 | 参见 [Wbi 签名](../misc/sign/wbi.md) |
+
**正文参数(application/x-www-form-urlencoded):**
| 参数名 | 类型 | 内容 | 必要性 | 备注 |
diff --git a/docs/misc/sign/wbi.md b/docs/misc/sign/wbi.md
index 86311d3..23eae00 100644
--- a/docs/misc/sign/wbi.md
+++ b/docs/misc/sign/wbi.md
@@ -121,7 +121,7 @@
## Demo
-含 [Python](#Python)、[JavaScript](#JavaScript)、[Golang](#Golang)、[C#](#CSharp)、[Java](#Java)、[Kotlin](#Kotlin)、[Swift](#Swift)、[C++](#CPlusPlus)、[Rust](#Rust) 语言编写的 Demo 。
+含 [Python](#python)、[JavaScript](#javascript)、[Golang](#golang)、[C#](#csharp)、[Java](#java)、[Kotlin](#kotlin)、[Swift](#swift)、[C++](#cplusplus)、[Rust](#rust)、[Haskell](#haskell) 语言编写的 Demo 。
### Python
@@ -1341,3 +1341,134 @@ int main() {
```text
avid=1755630705&cid=1574294582&fnval=4048&fnver=0&fourk=1&qn=32&wts=1717922933&w_rid=43571b838a1611fa121189083cfc1784
```
+
+### Haskell
+
+无第三方依赖: `base`, `Cabal-syntax`, `bytestring`, `containers`
+注: 此处使用自写的 URI 编码模块, 实际可用别的第三方库替代
+
+`Main.hs`:
+```hs
+module Main (wbi, main) where
+
+import Data.ByteString.Char8 (pack)
+import qualified Data.Map.Strict as Map
+import Distribution.Utils.MD5 (md5, showMD5)
+import URIEncoder (encodeURIComponent)
+import Data.Time.Clock.System (getSystemTime, systemSeconds)
+
+mixinKeyEncTab :: [Int]
+mixinKeyEncTab = [
+ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
+ 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
+ 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
+ 36, 20, 34, 44, 52
+ ]
+
+getMixinKey :: String -> String -> String
+getMixinKey imgKey subKey =
+ let s = imgKey ++ subKey
+ in map (\i -> s !! (mixinKeyEncTab !! i)) [0..31]
+
+join :: [String] -> String -> String
+join arr ins = concatMap (++ ins) (init arr) ++ last arr
+
+wbi :: String -> String -> Integer -> Map.Map String String -> String
+wbi imgKey subKey wts params =
+ let orig = join (map (\(k, v) -> encodeURIComponent k ++ "=" ++ encodeURIComponent v) (Map.toList $ Map.insert "wts" (show wts) params)) "&"
+ in orig ++ "&w_rid=" ++ showMD5 (md5 $ pack $ orig ++ getMixinKey imgKey subKey)
+
+main :: IO ()
+main = do -- hard encode for test
+ let params1 = Map.fromList [("aid", "2")]
+ params2 = Map.fromList [("foo", "114")
+ ,("bar", "514")
+ ,("hello", "世 界")
+ ]
+ imgKey = "7cd084941338484aae1ad9425b84077c"
+ subKey = "4932caff0ff746eab6f01bf08b70ac45"
+ wts1 <- getSystemTime
+ putStrLn $ wbi imgKey subKey (toInteger $ systemSeconds wts1) params1
+ wts2 <- getSystemTime
+ putStrLn $ wbi imgKey subKey (toInteger $ systemSeconds wts2) params2
+```
+
+`URIEncoder.hs`:
+```hs
+module URIEncoder (encodeURIComponent) where
+
+import Data.Char (ord, chr, intToDigit)
+import Data.Bits (shiftL, shiftR, (.&.))
+import Data.List (isInfixOf)
+
+-- ES 19.2.6.4 encodeURIComponent ( uriComponent )
+encodeURIComponent :: String -> String
+encodeURIComponent input = case encode input "" of
+ Right result -> result
+ Left err -> error err
+
+-- ES 19.2.6.5 Encode ( string, extraUnescaped )
+encode :: String -> String -> Either String String
+encode string extraUnescaped = loop 0 string
+ where
+ alwaysUnescaped = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "-.!~*'()"
+ unescapedSet = alwaysUnescaped ++ extraUnescaped
+
+ loop k str
+ | k >= length str = Right []
+ | otherwise = case codePointAt str k of
+ (Nothing, _) -> Left "Unpaired surrogate"
+ (Just (cp, _), newK) ->
+ if [str !! k] `isInfixOf` unescapedSet
+ then (str !! k :) <$> loop (k + 1) str
+ else do
+ bytes <- utf8Encode cp
+ let escaped = concatMap percentEncode bytes
+ rest <- loop newK str
+ Right (escaped ++ rest)
+
+codePointAt :: String -> Int -> (Maybe (Int, Int), Int)
+codePointAt s k
+ | k >= length s = (Nothing, k)
+ | otherwise =
+ let c1 = ord (s !! k)
+ in if 0xD800 <= c1 && c1 <= 0xDBFF && k+1 < length s
+ then let c2 = ord (s !! (k+1))
+ in if 0xDC00 <= c2 && c2 <= 0xDFFF
+ then ( Just (0x10000 + ((c1 - 0xD800) `shiftL` 10) + (c2 - 0xDC00), 2)
+ , k + 2 )
+ else (Just (c1, 1), k + 1)
+ else (Just (c1, 1), k + 1)
+
+utf8Encode :: Int -> Either String [Int]
+utf8Encode cp
+ | cp < 0 = Left "Invalid code point"
+ | cp <= 0x007F = Right [cp]
+ | cp <= 0x07FF = Right
+ [ 0xC0 + (cp `shiftR` 6)
+ , 0x80 + (cp .&. 0x3F) ]
+ | cp <= 0xFFFF = Right
+ [ 0xE0 + (cp `shiftR` 12)
+ , 0x80 + ((cp `shiftR` 6) .&. 0x3F)
+ , 0x80 + (cp .&. 0x3F) ]
+ | cp <= 0x10FFFF = Right
+ [ 0xF0 + (cp `shiftR` 18)
+ , 0x80 + ((cp `shiftR` 12) .&. 0x3F)
+ , 0x80 + ((cp `shiftR` 6) .&. 0x3F)
+ , 0x80 + (cp .&. 0x3F) ]
+ | otherwise = Left "Code point out of range"
+
+percentEncode :: Int -> String
+percentEncode byte = '%' : toHex byte
+ where
+ toHex n = [hexDigit (n `div` 16), hexDigit (n `mod` 16)]
+ hexDigit x
+ | x < 10 = intToDigit x
+ | otherwise = chr (x - 10 + ord 'A')
+```
+
+输出:
+```text
+aid=2&wts=1744823207&w_rid=a3cd246bd42c066932752b24694eaf0d
+bar=514&foo=114&hello=%E4%B8%96%20%E7%95%8C&wts=1744823207&w_rid=93acf59d85f74453e40cea00056c3daf
+```
diff --git a/docs/wallet/info.md b/docs/wallet/info.md
index e15fafe..24f02c3 100644
--- a/docs/wallet/info.md
+++ b/docs/wallet/info.md
@@ -38,13 +38,13 @@
| mid | num | 用户 mid | |
| totalBp | num | 总计 B 币 | |
| defaultBp | num | 默认 B 币? | |
-| isoBp | num | iOS B 币? | |
+| iosBp | num | iOS B 币? | |
| couponBalance | num | 优惠券余额 | |
| availableBp | num | 可用 B 币 | |
| unavailableBp | num | 不可用 B 币 | |
| unavailableReason | str | 不可用原因 | |
| tip | str | 请XXXXX | 请投币?? |
-| needShowClassBalance | num | 需要显示类平衡?? | 1 |
+| needShowClassBalance | num | 需要显示类余额?? | 1 |
**示例:**