├── .github └── ISSUE_TEMPLATE │ ├── --api.md │ ├── api--.md │ └── bug--.md ├── .gitignore ├── API.md ├── LICENSE ├── README.md ├── api.go ├── bili_client.go ├── bili_client_test.go ├── comm_client.go ├── comm_client_test.go ├── config.go ├── example ├── README.md └── heartbeat │ └── main.go ├── go.mod ├── go.sum ├── internal └── util │ ├── base.go │ ├── string.go │ ├── string_test.go │ └── time.go ├── logo └── bilibili.png ├── proto └── dm │ ├── dm.pb.go │ └── dm.proto ├── types.go ├── util.go └── util_test.go /.github/ISSUE_TEMPLATE/--api.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 新的API 3 | about: 请提供充足的信息,而不是扔一个API让别人去分析 4 | title: "[Feat] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 一切与新API有关的能直接成代码的信息 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/api--.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: API变更 3 | about: URL变更;响应变更;参数变更;... 4 | title: "[Update]" 5 | labels: update 6 | assignees: '' 7 | 8 | --- 9 | 10 | API名称: **GetMe** 11 | 变更: 响应参数中的xxx从xxx类型变为xxx类型 12 | 发现时间: 2021.10.15 13 | 示例JSON: 14 | ```json 15 | {} 16 | ``` 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug--.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: BUG提交 3 | about: 代码错误 4 | title: "[BUG] BUG的简单描述" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | API名称:**GetMe** 11 | 错误原因: 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | test_code 3 | dist -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## BiliClient 2 |
3 | 查看API 4 | 5 | ``` 6 | AudioGetInfo 7 | AudioGetMyFavLists 8 | AudioGetPlayURL 9 | AudioIsCoined 10 | AudioIsFavored 11 | ChanAdd 12 | ChanAddVideo 13 | ChanDel 14 | ChanDelVideo 15 | ChanEdit 16 | ChanGetMy 17 | ChanGetMyVideo 18 | ChanHasInvalidVideo 19 | ChanSetVideoSort 20 | ChargeSetMessage 21 | ChargeTradeCheckQrCode 22 | ChargeTradeCreateBp 23 | ChargeTradeCreateQrCode 24 | DanmakuCommandPost 25 | DanmakuEditPool 26 | DanmakuEditState 27 | DanmakuGetHistory 28 | DanmakuGetHistoryIndex 29 | DanmakuGetLikes 30 | DanmakuLike 31 | DanmakuPost 32 | DanmakuRecall 33 | DanmakuReport 34 | DanmakuSetConfig 35 | DynaCreateDraft 36 | DynaCreateDraw 37 | DynaCreatePlain 38 | DynaDel 39 | DynaDelDraft 40 | DynaGetDrafts 41 | DynaLike 42 | DynaModifyDraft 43 | DynaPublishDraft 44 | DynaRepost 45 | DynaUploadPics 46 | EmotePackAdd 47 | EmotePackGetAll 48 | EmotePackGetMy 49 | EmotePackRemove 50 | FavAdd 51 | FavCleanRes 52 | FavCopyRes 53 | FavDel 54 | FavDelRes 55 | FavEdit 56 | FavGetDetail 57 | FavGetMy 58 | FavGetRes 59 | FavGetResDetail 60 | FavMoveRes 61 | FollowingsGetMy 62 | FollowingsGetMyDetail 63 | FollowUser 64 | GetAccountSafetyStat 65 | GetCoinLogs 66 | GetCookieAuth 67 | GetExpCoinReward 68 | GetExpRewardStat 69 | GetMe 70 | GetMsgUnread 71 | GetNavInfo 72 | GetNavStat 73 | GetRealNameInfo 74 | GetRealNameStat 75 | GetRelationStat 76 | GetUpStat 77 | GetVipStat 78 | Raw 79 | RawParse 80 | SetClient 81 | SetUA 82 | SignUpdate 83 | SpaceAddMasterpieces 84 | SpaceCancelMasterpiece 85 | SpaceCancelTopArchive 86 | SpaceGetMyLastPlayGame 87 | SpaceGetMyLastVideoCoin 88 | SpaceSetNotice 89 | SpaceSetTags 90 | SpaceSetTopArchive 91 | Upload 92 | UploadParse 93 | VideoAddCoins 94 | VideoAddLike 95 | VideoGetInfo 96 | VideoGetPlayURL 97 | VideoGetTags 98 | VideoHateTag 99 | VideoHeartBeat 100 | VideoIsAddedCoins 101 | VideoIsFavoured 102 | VideoIsLiked 103 | VideoLikeTag 104 | VideoReportProgress 105 | VideoSetFavour 106 | VideoShare 107 | VideoTriple 108 | ``` 109 |
110 | 111 | ## CommClient 112 | 113 |
114 | 查看API 115 | 116 | ``` 117 | AudioGetInfo 118 | AudioGetLyric 119 | AudioGetMembers 120 | AudioGetPlayURL 121 | AudioGetStat 122 | AudioGetTags 123 | ChanGet 124 | ChanGetVideo 125 | ChargeSpaceGetList 126 | ChargeVideoGetList 127 | DanmakuGetByPb 128 | DanmakuGetLikes 129 | DanmakuGetShot 130 | EmoteGetFreePack 131 | EmoteGetPackDetail 132 | FavGet 133 | FavGetDetail 134 | FavGetRes 135 | FavGetResDetail 136 | FollowingsGetDetail 137 | GetDailyNum 138 | GetGeoInfo 139 | GetRelationStat 140 | GetUnixNow 141 | LiveGetAreaInfo 142 | LiveGetGuardList 143 | LiveGetMedalRank 144 | LiveGetPlayURL 145 | LiveGetRoomInfoByID 146 | LiveGetRoomInfoByMID 147 | LiveGetWsConf 148 | Raw 149 | RawParse 150 | SetClient 151 | SetUA 152 | SpaceGetLastPlayGame 153 | SpaceGetLastVideoCoin 154 | SpaceGetMasterpieces 155 | SpaceGetNotice 156 | SpaceGetTags 157 | SpaceGetTopArchive 158 | SpaceSearchVideo 159 | VideoGetDescription 160 | VideoGetInfo 161 | VideoGetOnlineNum 162 | VideoGetPageList 163 | VideoGetPlayURL 164 | VideoGetRecommend 165 | VideoGetStat 166 | VideoShot 167 | VideoTags 168 | ``` 169 | 170 |
-------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://socialify.git.ci/iyear/biligo/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fiyear%2Fbiligo%2Fv0%2Flogo%2Fbilibili.png&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light) 2 | 3 | ## 简介 4 | ![](https://img.shields.io/github/go-mod/go-version/iyear/biligo?style=flat-square) 5 | ![](https://img.shields.io/badge/license-GPL-lightgrey.svg?style=flat-square) 6 | ![](https://img.shields.io/github/v/release/iyear/biligo?color=red&style=flat-square) 7 | ![](https://img.shields.io/github/last-commit/iyear/biligo?style=flat-square) 8 | [![Go Reference](https://pkg.go.dev/badge/github.com/iyear/biligo.svg)](https://pkg.go.dev/github.com/iyear/biligo) 9 | 10 | **v0版本不保证对外函数、结构的不变性,请勿大规模用于生产环境** 11 | 12 | `BiliBili API` 的 `Golang` 实现,目前已经实现了 100+ API,还在进一步更新中 13 | 14 | ### 特性 15 | 16 | - 良好的设计,支持自定义 `client` 与 `UA` 17 | - 完善的单元测试,易懂的函数命名,极少的第三方库依赖 18 | - 代码、结构体注释完善,无需文档开箱即用 19 | - 其他功能性代码,例如 `AV/BV`互转,`GetVideoZone()`获取分区信息... 20 | - 配套工具 [biligo-live](https://github.com/iyear/biligo-live) 封装直播 `WebSocket` 协议 21 | ### 说明 22 | 23 | - 该项目永远不会编写直接涉及滥用的接口 24 | - 该项目仅供学习,请勿用于商业用途。任何使用该项目造成的后果由开发者自行承担 25 | 26 | ### 参考 27 | 28 | > 感谢以下所有项目的贡献者 29 | 30 | https://github.com/SocialSisterYi/bilibili-API-collect 31 | 32 | https://github.com/MoyuScript/bilibili-api 33 | 34 | ## 快速开始 35 | 36 | ### 安装 37 | 请让 `biligo` 永远保持在最新版本 38 | 39 | ```shell 40 | go get -u github.com/iyear/biligo 41 | ``` 42 | 43 | ```go 44 | import "github.com/iyear/biligo" 45 | ``` 46 | 47 | ### 使用 48 | 49 | ```go 50 | package main 51 | 52 | import ( 53 | "fmt" 54 | bg "github.com/iyear/biligo" 55 | "log" 56 | ) 57 | 58 | func main() { 59 | b, err := bg.NewBiliClient(&bg.BiliSetting{ 60 | // 均来自Web登录的Cookie 61 | Auth: &bg.CookieAuth{ 62 | // DedeUserID 63 | DedeUserID: "YOUR_DedeUserID", 64 | // SESSDATA 65 | SESSDATA: "YOUR_SESSDATA", 66 | // bili_jct 67 | BiliJCT: "YOUR_BiliJCT", 68 | // DedeUserID__ckMd5 69 | DedeUserIDCkMd5: "YOUR_DedeUserIdCkMd5", 70 | }, 71 | // DEBUG 模式将输出请求和响应 72 | DebugMode: true, 73 | // Client: myClient, 74 | // UserAgent: "My UA", 75 | }) 76 | 77 | if err != nil { 78 | log.Fatal("failed to make new bili client; error: ", err) 79 | return 80 | } 81 | 82 | fmt.Printf("mid: %d, uname: %s,userID: %s,rank: %s\n", b.Me.MID, b.Me.UName, b.Me.UserID, b.Me.Rank) 83 | fmt.Printf("birthday: %s,sex: %s\n", b.Me.Birthday, b.Me.Sex) 84 | fmt.Printf("sign: %s\n", b.Me.Sign) 85 | } 86 | ``` 87 | 88 | ### 例子 89 | 90 | 同目录下的 `example` 文件夹 91 | 92 | ### 说明 93 | 94 | 共有两种 `Client` 95 | 96 | 一种为 `BiliClient`,必须提供有效的 `Auth` 信息,公共接口不在此提供 97 | 98 | 另一种为 `CommClient` ,只提供公共接口 99 | 100 | 一些接口同时在两种 `Client` 中,因为其中某些字段在登录与非登录状态下有所区别 101 | 102 | ## 约定 103 | 104 | > 除了维护API列表、 `example` 目录、测试文件,不会有任何其他的专用文档。请利用好注释和测试。 105 | 106 | > 常规的函数命名方式 107 | > 108 | > 由于API众多,函数名显得冗长。只能尽可能简化,但不会失去原有语义,这是无文档的基础 109 | 110 | 除无对应分类的API外,其余对应分类API均以 `分类名+动作+描述` 命名 111 | 112 | 无对应分类的API以 `动作+描述` 命名 例如: `VideoGetTags` `VideoIsFavoured` `VideoSetFavour` 113 | 114 | - `Video` - `视频` 115 | - `Audio` - `音频` 116 | - `Fav` - `收藏夹` 117 | - `Chan` - `频道` 118 | - `Space` - `个人空间` 119 | - `Danmaku` - `弹幕` 120 | - `Emote` - `表情` 121 | - `Charge` - `充电` 122 | - `Dyna` - `动态` 123 | - `Live` - `直播` 124 | - `Followings` - `关注` 125 | 126 | 127 | > 结构体编写规范 128 | 129 | 嵌套可分离可不分离,结构体和数组用指针。 130 | 131 | 具体看 `types.go` 即可 132 | 133 | > 只要以下名词出现在该库的任何地方,可能将不做任何解释,请提交PR时尽可能遵守以下约定 134 | 135 | - `aid` - `稿件av` (库中所有参数均使用 `aid` , `bvid` 请开发者自行使用 `BV2AV()`) 136 | - `cid` - `稿件分P的ID` (单P视频中`aid` ≠ `cid`) 或 `频道ID` 137 | - `mid` - `B站ID` 138 | - `mlid` - `收藏夹ID` 139 | - `auid` - `音频ID` 140 | - `dmid` - `弹幕ID` 141 | - `dyid` - `动态ID` 142 | - `dfid` - `定时发布动态ID` 143 | - `oid` - 根据上下文不同分别指代以上不同的ID,具体看注释 144 | - `ID` 而不是 `Id` 145 | - `URL` 而不是 `Url` 146 | 147 | > 关于大接口 148 | 149 | 对于评论、搜索这种超大的响应,可能不会做对应的接口,一方面这种接口经常变更,另一方面大多数的响应字段是无用的,所以请开发者自行使用 `Raw() or RawParse() + gjson` 获取需要的数据 150 | 151 | 示例: 152 | 153 | ```go 154 | package main 155 | 156 | import ( 157 | "fmt" 158 | bg "github.com/iyear/biligo" 159 | "github.com/tidwall/gjson" 160 | "log" 161 | ) 162 | 163 | func GetComments() { 164 | resp, err := bg.NewCommClient(&bg.CommSetting{}).RawParse( 165 | bg.BiliApiURL, 166 | "x/v2/reply/main", 167 | "GET", 168 | map[string]string{ 169 | "next": "0", 170 | "type": "1", 171 | "oid": "591018280", 172 | "mode": "3", 173 | "plat": "1", 174 | }, 175 | ) 176 | if err != nil { 177 | log.Println(err) 178 | return 179 | } 180 | for i, r := range gjson.Get(string(resp.Data), "replies.#.content.message").Array() { 181 | fmt.Printf("[%d] %s\n\n", i+1, r.String()) 182 | } 183 | } 184 | ``` 185 | 186 | ## 维护与贡献 187 | - 这个库的日常维护基本都是体力活 188 | - 安全水平有限,部分新接口我无法分析 189 | - 欢迎 `typo` 和 完善注释、文档的PR 190 | - 不接受无脑无贡献的催更 191 | - 尽量遵守约定提交PR 192 | - 由于项目特殊性,随时可能删库,随时可能弃坑 193 | ## 已实现API 194 | 195 | [v0](https://github.com/iyear/biligo/blob/v0/API.md) 196 | 197 | ## 留言 198 | ### 1 199 | 这个库是在暑假开始写的,其实写到一半已经不想写了,`90%`的代码都是在重复着查接口、写响应结构体、测试的过程,只是看到 `python` 有一套还在更新的 `SDK`,想着还是为 `Golang` 也写一套 200 | 201 | 后来某天突然想把这个项目给弄完 ,所以目前动态与直播的部分基本没写 202 | 203 | ### 2 204 | 205 | `map[string]string` 而不是 `map[string]interface{}` 因为在这个项目里 `interface{}` 的无约束很容易在一些特定的参数下忘记转换。 206 | 207 | ## LICENSE 208 | 209 | GPL 210 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | package biligo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "math/rand" 11 | "mime/multipart" 12 | "net/http" 13 | "net/url" 14 | "os" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | type baseClient struct { 20 | debug bool 21 | client *http.Client 22 | ua string 23 | logger *log.Logger 24 | } 25 | type baseSetting struct { 26 | // 自定义http client 27 | // 28 | // 默认为 http.http.DefaultClient 29 | Client *http.Client 30 | // Debug模式 true将输出请求信息 false不输出 31 | // 32 | // 默认false 33 | DebugMode bool 34 | // 自定义UserAgent 35 | // 36 | // 默认Chrome随机Agent 37 | UserAgent string 38 | // Logger 的输出前缀,区分Client 39 | Prefix string 40 | } 41 | 42 | func newBaseClient(setting *baseSetting) *baseClient { 43 | client := setting.Client 44 | if client == nil { 45 | client = http.DefaultClient 46 | } 47 | 48 | ua := setting.UserAgent 49 | if ua == "" { 50 | rand.Seed(time.Now().UnixNano()) 51 | ua = userAgent[rand.Intn(len(userAgent))] 52 | } 53 | 54 | return &baseClient{ 55 | debug: setting.DebugMode, 56 | client: client, 57 | ua: ua, 58 | logger: log.New(os.Stdout, setting.Prefix, log.LstdFlags), 59 | } 60 | } 61 | 62 | // request v为携带的参数,用于debug输出 63 | func (h *baseClient) request(req *http.Request, v interface{}) ([]byte, error) { 64 | resp, err := h.client.Do(req) 65 | if err != nil { 66 | return nil, err 67 | } 68 | resp.Close = true 69 | defer resp.Body.Close() 70 | 71 | raw, err := ioutil.ReadAll(resp.Body) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | if h.debug { 77 | h.logger.Printf("%s %s %+v", req.Method, req.URL, v) 78 | h.logger.Printf("%s", string(raw)) 79 | } 80 | 81 | return raw, nil 82 | } 83 | func (h *baseClient) raw(base, endpoint, method string, payload map[string]string, dAfter func(d *url.Values), reqAfter func(r *http.Request)) ([]byte, error) { 84 | var ( 85 | req *http.Request 86 | err error 87 | ) 88 | 89 | data := url.Values{} 90 | for k, v := range payload { 91 | data.Add(k, v) 92 | } 93 | 94 | // 侵入处理values 95 | if dAfter != nil { 96 | dAfter(&data) 97 | } 98 | 99 | link := base + endpoint 100 | switch method { 101 | case http.MethodGet: 102 | if req, err = http.NewRequest(method, link, nil); err != nil { 103 | return nil, err 104 | } 105 | req.URL.RawQuery = data.Encode() 106 | case http.MethodPost: 107 | if req, err = http.NewRequest(method, link, strings.NewReader(data.Encode())); err != nil { 108 | return nil, err 109 | } 110 | } 111 | 112 | req.Header.Add("Origin", "https://www.bilibili.com") 113 | req.Header.Add("Referer", "https://www.bilibili.com") 114 | req.Header.Add("Content-type", "application/x-www-form-urlencoded") 115 | req.Header.Add("User-Agent", h.ua) 116 | 117 | // 侵入处理req 118 | if reqAfter != nil { 119 | reqAfter(req) 120 | } 121 | 122 | return h.request(req, payload) 123 | } 124 | func (h *baseClient) parse(raw []byte) (*Response, error) { 125 | var result = &Response{} 126 | if err := json.Unmarshal(raw, &result); err != nil { 127 | return nil, err 128 | } 129 | if result.Code != 0 { 130 | return nil, fmt.Errorf("(%d) %s", result.Code, result.Message) 131 | } 132 | return result, nil 133 | } 134 | func (h *baseClient) upload(base, endpoint string, payload map[string]string, files []*FileUpload, mAfter func(m *multipart.Writer) error, reqAfter func(r *http.Request)) ([]byte, error) { 135 | var ( 136 | req *http.Request 137 | err error 138 | ) 139 | link := base + endpoint 140 | 141 | body := new(bytes.Buffer) 142 | mp := multipart.NewWriter(body) 143 | 144 | for _, f := range files { 145 | var ff io.Writer 146 | if ff, err = mp.CreateFormFile(f.Field, f.Name); err != nil { 147 | return nil, err 148 | } 149 | if _, err = io.Copy(ff, f.File); err != nil { 150 | return nil, err 151 | } 152 | } 153 | 154 | for k, v := range payload { 155 | if err = mp.WriteField(k, v); err != nil { 156 | return nil, err 157 | } 158 | } 159 | 160 | // 侵入处理field 161 | if mAfter != nil { 162 | if err = mAfter(mp); err != nil { 163 | return nil, err 164 | } 165 | } 166 | 167 | // 为mp添加结束符 168 | if err = mp.Close(); err != nil { 169 | return nil, err 170 | } 171 | 172 | // 只支持POST 173 | if req, err = http.NewRequest(http.MethodPost, link, body); err != nil { 174 | return nil, err 175 | } 176 | 177 | req.Header.Add("Origin", "https://www.bilibili.com") 178 | req.Header.Add("Referer", "https://www.bilibili.com") 179 | req.Header.Add("Content-type", mp.FormDataContentType()) 180 | req.Header.Add("User-Agent", h.ua) 181 | 182 | // 侵入处理req 183 | if reqAfter != nil { 184 | reqAfter(req) 185 | } 186 | 187 | // 文件不输出,否则全是乱码 188 | return h.request(req, payload) 189 | } 190 | -------------------------------------------------------------------------------- /bili_client_test.go: -------------------------------------------------------------------------------- 1 | package biligo 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "os" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | var testBiliClient *BiliClient 12 | 13 | func init() { 14 | testBiliClient = newTestBiliClient() 15 | } 16 | func newTestBiliClient() *BiliClient { 17 | c, _ := NewBiliClient(&BiliSetting{ 18 | Auth: &CookieAuth{ 19 | DedeUserID: os.Getenv("DedeUserID"), 20 | SESSDATA: os.Getenv("SESSDATA"), 21 | BiliJCT: os.Getenv("BiliJCT"), 22 | DedeUserIDCkMd5: os.Getenv("DedeUserIDCkMd5"), 23 | }, 24 | DebugMode: true, 25 | }) 26 | return c 27 | } 28 | 29 | func TestNewBiliClient(t *testing.T) { 30 | 31 | if _, err := NewBiliClient(&BiliSetting{ 32 | Auth: &CookieAuth{ 33 | DedeUserID: "", 34 | SESSDATA: "", 35 | BiliJCT: "", 36 | }, 37 | }); err != nil { 38 | t.Error(err) 39 | t.FailNow() 40 | } 41 | } 42 | func TestBili_GetMe(t *testing.T) { 43 | me, err := testBiliClient.GetMe() 44 | if err != nil { 45 | t.Error(err) 46 | t.FailNow() 47 | } 48 | t.Logf("id: %d,sign: %s,uname: %s\n", me.MID, me.Sign, me.UName) 49 | } 50 | 51 | func TestBiliClient_GetNavInfo(t *testing.T) { 52 | info, err := testBiliClient.GetNavInfo() 53 | if err != nil { 54 | t.Error(err) 55 | t.FailNow() 56 | } 57 | t.Logf( 58 | "level: %d,curExp: %d,nextExp: %d,mobileVerified: %d,money: %.2f,role: %d,pendantID: %d,bcoin: %.2f", 59 | info.LevelInfo.CurrentLevel, 60 | info.LevelInfo.CurrentExp, 61 | info.LevelInfo.NextExp, 62 | info.MobileVerified, 63 | info.Money, 64 | info.Official.Role, 65 | info.Pendant.PID, 66 | info.Wallet.BcoinBalance, 67 | ) 68 | } 69 | func TestBiliClient_GetNavStat(t *testing.T) { 70 | stat, err := testBiliClient.GetNavStat() 71 | if err != nil { 72 | t.Error(err) 73 | t.FailNow() 74 | } 75 | t.Logf("following: %d,follower: %d,dynamic_count: %d", stat.Following, stat.Follower, stat.DynamicCount) 76 | } 77 | func TestBiliClient_GetExpRewardStat(t *testing.T) { 78 | stat, err := testBiliClient.GetExpRewardStat() 79 | if err != nil { 80 | t.Error(err) 81 | t.FailNow() 82 | } 83 | t.Logf( 84 | "login:%t,watch: %t,share: %t,coins: %d,tel: %t,email: %t,identifyCard: %t,safeQuestion: %t", 85 | stat.Login, 86 | stat.Watch, 87 | stat.Share, 88 | stat.Coins, 89 | stat.Tel, 90 | stat.Email, 91 | stat.IdentifyCard, 92 | stat.SafeQuestion, 93 | ) 94 | } 95 | func TestBiliClient_GetExpCoinReward(t *testing.T) { 96 | n, err := testBiliClient.GetExpCoinReward() 97 | if err != nil { 98 | t.Error(err) 99 | t.FailNow() 100 | } 101 | t.Logf("exp: %d", n) 102 | } 103 | func TestBiliClient_GetVipStat(t *testing.T) { 104 | info, err := testBiliClient.GetVipStat() 105 | if err != nil { 106 | t.Error(err) 107 | t.FailNow() 108 | } 109 | t.Logf( 110 | "mid: %d,vipType: %d,vipStatus: %d,vipDueDate: %d,vipPayType: %d,themeType: %d", 111 | info.MID, 112 | info.VipType, 113 | info.VipStatus, 114 | info.VipDueDate, 115 | info.VipPayType, 116 | info.ThemeType, 117 | ) 118 | } 119 | func TestBiliClient_GetAccountSafetyStat(t *testing.T) { 120 | info, err := testBiliClient.GetAccountSafetyStat() 121 | if err != nil { 122 | t.Error(err) 123 | t.FailNow() 124 | } 125 | t.Logf( 126 | "mail: %s,tel: %s,hasPwd: %t,readNameVerified: %t,score: %d,pwdLevel: %d,qqBind: %d", 127 | info.AccountInfo.HideMail, 128 | info.AccountInfo.HideTel, 129 | !info.AccountInfo.UnneededCheck, 130 | info.AccountInfo.RealnameCertified, 131 | info.AccountSafe.Score, 132 | info.AccountSafe.PwdLevel, 133 | info.AccountSNS.QQBind, 134 | ) 135 | } 136 | func TestBiliClient_GetMsgUnread(t *testing.T) { 137 | unread, err := testBiliClient.GetMsgUnread() 138 | if err != nil { 139 | t.Error(err) 140 | t.FailNow() 141 | } 142 | t.Logf("like: %d,chat: %d,at: %d,reply: %d,sysMsg: %d,up: %d", unread.Like, unread.Chat, unread.At, unread.Reply, unread.SysMsg, unread.Up) 143 | } 144 | func TestBiliClient_GetRealNameStat(t *testing.T) { 145 | rn, err := testBiliClient.GetRealNameStat() 146 | if err != nil { 147 | t.Error(err) 148 | t.FailNow() 149 | } 150 | t.Logf("realNameStatus: %t", rn) 151 | } 152 | func TestBiliClient_GetRealNameInfo(t *testing.T) { 153 | info, err := testBiliClient.GetRealNameInfo() 154 | if err != nil { 155 | t.Error(err) 156 | t.FailNow() 157 | } 158 | t.Logf("status: %d,name: %s,remark: %s,card: %s,type: %d", info.Status, info.Realname, info.Remark, info.Card, info.CardType) 159 | } 160 | func TestBiliClient_GetCoinLogs(t *testing.T) { 161 | logs, err := testBiliClient.GetCoinLogs() 162 | if err != nil { 163 | t.Error(err) 164 | t.FailNow() 165 | } 166 | for _, log := range logs { 167 | t.Logf("time: %s,delta: %.2f,reason: %s", log.Time, log.Delta, log.Reason) 168 | } 169 | 170 | } 171 | func TestBiliClient_GetRelationStat(t *testing.T) { 172 | stat, err := testBiliClient.GetRelationStat(546195) 173 | if err != nil { 174 | t.Error(err) 175 | t.FailNow() 176 | } 177 | t.Logf("mid: %d,following: %d,follower: %d,black: %d,whisper: %d", stat.MID, stat.Following, stat.Follower, stat.Black, stat.Whisper) 178 | } 179 | func TestBiliClient_GetUpStat(t *testing.T) { 180 | stat, err := testBiliClient.GetUpStat(546195) 181 | if err != nil { 182 | t.Error(err) 183 | t.FailNow() 184 | } 185 | t.Logf("archive: %d,article: %d,likes: %d", stat.Archive.View, stat.Article.View, stat.Likes) 186 | } 187 | func TestBili_SignUpdate(t *testing.T) { 188 | if err := testBiliClient.SignUpdate("除了自己的无知"); err != nil { 189 | t.Error(err) 190 | t.FailNow() 191 | } 192 | } 193 | func TestBiliClient_SpaceTagsSet(t *testing.T) { 194 | if err := testBiliClient.SpaceSetTags([]string{"test1", "test1", "test2", "test3", "test4", "test5"}); err != nil { 195 | t.Error(err) 196 | t.FailNow() 197 | } 198 | } 199 | func TestBiliClient_SpaceTagsSet2(t *testing.T) { 200 | // 测试TAG个数上限 201 | var tags []string 202 | for i := 0; i < 54; i++ { 203 | tags = append(tags, strconv.Itoa(i)) 204 | } 205 | if err := testBiliClient.SpaceSetTags(tags); err != nil { 206 | t.Error(err) 207 | t.FailNow() 208 | } 209 | } 210 | func TestBiliClient_SpaceNoticeSet(t *testing.T) { 211 | if err := testBiliClient.SpaceSetNotice("testtesttesttesttest"); err != nil { 212 | t.Error(err) 213 | t.FailNow() 214 | } 215 | } 216 | func TestBili_LikeVideo(t *testing.T) { 217 | if err := testBiliClient.VideoAddLike(759937808, true); err != nil { 218 | t.Error(err) 219 | t.FailNow() 220 | } 221 | } 222 | func TestBili_VideoIsLiked(t *testing.T) { 223 | liked, err := testBiliClient.VideoIsLiked(759937808) 224 | if err != nil { 225 | t.Error(err) 226 | t.FailNow() 227 | } 228 | t.Logf("liked: %t", liked) 229 | } 230 | func TestBili_VideoAddCoinsWithLike(t *testing.T) { 231 | if err := testBiliClient.VideoAddCoins(759937808, 1, true); err != nil { 232 | t.Error(err) 233 | t.FailNow() 234 | } 235 | } 236 | func TestBili_VideoIsAddedCoins(t *testing.T) { 237 | a, err := testBiliClient.VideoIsAddedCoins(759937808) 238 | if err != nil { 239 | t.Error(err) 240 | t.FailNow() 241 | } 242 | t.Logf("coins: %d", a) 243 | } 244 | func TestBili_VideoFavourAdd(t *testing.T) { 245 | p, err := testBiliClient.VideoSetFavour(759937808, []int64{492144694, 213103794}, nil) 246 | if err != nil { 247 | t.Error(err) 248 | t.FailNow() 249 | } 250 | t.Logf("prompt: %t", p) 251 | } 252 | func TestBili_VideoFavourDel(t *testing.T) { 253 | p, err := testBiliClient.VideoSetFavour(759937808, nil, []int64{492144694, 213103794}) 254 | if err != nil { 255 | t.Error(err) 256 | t.FailNow() 257 | } 258 | t.Logf("prompt: %t", p) 259 | } 260 | func TestBili_VideoIsFavoured(t *testing.T) { 261 | favoured, err := testBiliClient.VideoIsFavoured(759937808) 262 | if err != nil { 263 | t.Error(err) 264 | t.FailNow() 265 | } 266 | t.Logf("favoured: %t", favoured) 267 | } 268 | func TestBili_VideoTriple(t *testing.T) { 269 | like, coin, favour, multiply, err := testBiliClient.VideoTriple(377389267) 270 | if err != nil { 271 | t.Error(err) 272 | t.FailNow() 273 | } 274 | t.Logf("like: %t,coin: %t,favour: %t,multiply: %d", like, coin, favour, multiply) 275 | } 276 | func TestBili_VideoShare(t *testing.T) { 277 | share, err := testBiliClient.VideoShare(377389267) 278 | if err != nil { 279 | t.Error(err) 280 | t.FailNow() 281 | } 282 | t.Logf("shareNum: %d", share) 283 | } 284 | func TestBiliClient_VideoProgressReport(t *testing.T) { 285 | if err := testBiliClient.VideoReportProgress(13502509, 66445301, 100); err != nil { 286 | t.Error(err) 287 | t.FailNow() 288 | } 289 | } 290 | func TestBiliClient_VideoTags(t *testing.T) { 291 | tags, err := testBiliClient.VideoGetTags(759949922) 292 | if err != nil { 293 | t.Error(err) 294 | t.FailNow() 295 | } 296 | for _, tag := range tags { 297 | t.Logf("name: %s,use: %d,liked: %d,hated: %d,Isatten: %d", tag.TagName, tag.Count.Use, tag.Liked, tag.Hated, tag.IsAtten) 298 | } 299 | } 300 | func TestBiliClient_VideoTagLike(t *testing.T) { 301 | if err := testBiliClient.VideoLikeTag(759949922, 16230013); err != nil { 302 | t.Error(err) 303 | t.FailNow() 304 | } 305 | } 306 | func TestBiliClient_VideoTagHate(t *testing.T) { 307 | if err := testBiliClient.VideoHateTag(759949922, 16230013); err != nil { 308 | t.Error(err) 309 | t.FailNow() 310 | } 311 | } 312 | func TestBiliClient_DanmakuPost(t *testing.T) { 313 | result, err := testBiliClient.DanmakuPost(1, 292592903, 397011525, "bilitest6", 5000, 16777215, 25, 0, 1) 314 | if err != nil { 315 | t.Error(err) 316 | t.FailNow() 317 | } 318 | t.Logf("dmid: %d", result.Dmid) 319 | } 320 | func TestBiliClient_DanmakuCommandPost(t *testing.T) { 321 | var d struct { 322 | Msg string `json:"msg,omitempty"` 323 | } 324 | d.Msg = "bili~" 325 | data, _ := json.Marshal(d) 326 | result, err := testBiliClient.DanmakuCommandPost(1, 292592903, 397011525, 10000, 1, string(data), 0) 327 | if err != nil { 328 | t.Error(err) 329 | t.FailNow() 330 | } 331 | t.Logf("dmid: %d,json: %d", result.ID, result.Extra) 332 | } 333 | func TestBiliClient_DanmakuRecall(t *testing.T) { 334 | msg, err := testBiliClient.DanmakuRecall(397011525, 56335865687920640) 335 | if err != nil { 336 | t.Error(err) 337 | t.FailNow() 338 | } 339 | t.Logf("msg: %s", msg) 340 | } 341 | func TestBiliClient_DanmakuGetLikes(t *testing.T) { 342 | result, err := testBiliClient.DanmakuGetLikes(397011525, []uint64{54109805459813888, 54109892081901568}) 343 | if err != nil { 344 | t.Error(err) 345 | t.FailNow() 346 | } 347 | for _, r := range result { 348 | t.Logf("likes: %d,isLiked: %d,dmid: %s", r.Likes, r.UserLike, r.IDStr) 349 | } 350 | } 351 | func TestBiliClient_DanmakuLike(t *testing.T) { 352 | err := testBiliClient.DanmakuLike(397011525, 54109805459813888, 1) 353 | if err != nil { 354 | t.Error(err) 355 | t.FailNow() 356 | } 357 | } 358 | func TestBiliClient_DanmakuReport(t *testing.T) { 359 | err := testBiliClient.DanmakuReport(397011525, 54109805459813888, 11, "无效弹幕") 360 | if err != nil { 361 | t.Error(err) 362 | t.FailNow() 363 | } 364 | } 365 | func TestBiliClient_DanmakuEditState(t *testing.T) { 366 | err := testBiliClient.DanmakuEditState(1, 397011525, []uint64{54109805459813888}, 1) 367 | if err != nil { 368 | t.Error(err) 369 | t.FailNow() 370 | } 371 | } 372 | func TestBiliClient_DanmakuEditPool(t *testing.T) { 373 | err := testBiliClient.DanmakuEditPool(1, 397011525, []uint64{54109805459813888}, 1) 374 | if err != nil { 375 | t.Error(err) 376 | t.FailNow() 377 | } 378 | } 379 | func TestBiliClient_DanmakuHistoryIndexGet(t *testing.T) { 380 | r, err := testBiliClient.DanmakuGetHistoryIndex(1176840, 2020, 5) 381 | if err != nil { 382 | t.Error(err) 383 | t.FailNow() 384 | } 385 | t.Log(r) 386 | } 387 | func TestBiliClient_DanmakuHistoryGet(t *testing.T) { 388 | r, err := testBiliClient.DanmakuGetHistory(1176840, "2020-05-05") 389 | if err != nil { 390 | t.Error(err) 391 | t.FailNow() 392 | } 393 | t.Logf("num: %d", len(r.Danmaku)) 394 | } 395 | func TestBiliClient_ChannelAdd(t *testing.T) { 396 | cid, err := testBiliClient.ChanAdd("test", "testtest") 397 | if err != nil { 398 | t.Error(err) 399 | t.FailNow() 400 | } 401 | t.Logf("cid: %d", cid) 402 | } 403 | func TestBiliClient_ChannelEdit(t *testing.T) { 404 | err := testBiliClient.ChanEdit(200444, "test1", "1111") 405 | if err != nil { 406 | t.Error(err) 407 | t.FailNow() 408 | } 409 | } 410 | func TestBiliClient_ChannelDel(t *testing.T) { 411 | err := testBiliClient.ChanDel(200444) 412 | if err != nil { 413 | t.Error(err) 414 | t.FailNow() 415 | } 416 | } 417 | func TestBiliClient_MyFavoritesList(t *testing.T) { 418 | list, err := testBiliClient.FavGetMy() 419 | if err != nil { 420 | t.Error(err) 421 | t.FailNow() 422 | } 423 | for _, info := range list.List { 424 | t.Logf("mlid: %d,fid: %d,title: %s,count: %d", info.ID, info.FID, info.Title, info.MediaCount) 425 | } 426 | } 427 | func TestBiliClient_VideoHeartBeat(t *testing.T) { 428 | err := testBiliClient.VideoHeartBeat(13662970, 126654047, 1000) 429 | if err != nil { 430 | t.Error(err) 431 | t.FailNow() 432 | } 433 | } 434 | func TestBiliClient_FavoritesAdd(t *testing.T) { 435 | d, err := testBiliClient.FavAdd("test", "ttttt", true, "") 436 | if err != nil { 437 | t.Error(err) 438 | t.FailNow() 439 | } 440 | t.Logf("id: %d,title: %s,intro: %s,cover: %s", d.ID, d.Title, d.Intro, d.Cover) 441 | } 442 | func TestBiliClient_FavoritesEdit(t *testing.T) { 443 | d, err := testBiliClient.FavEdit(1342341894, "test222", "test333", true, "") 444 | if err != nil { 445 | t.Error(err) 446 | t.FailNow() 447 | } 448 | t.Logf("id: %d,title: %s,intro: %s,cover: %s", d.ID, d.Title, d.Intro, d.Cover) 449 | } 450 | func TestBiliClient_FavoritesDel(t *testing.T) { 451 | err := testBiliClient.FavDel([]int64{1342341894}) 452 | if err != nil { 453 | t.Error(err) 454 | t.FailNow() 455 | } 456 | } 457 | func TestBiliClient_FavoritesResGet(t *testing.T) { 458 | d, err := testBiliClient.FavGetRes(504229694) 459 | if err != nil { 460 | t.Error(err) 461 | t.FailNow() 462 | } 463 | for _, r := range d { 464 | t.Logf("id: %d,type: %d", r.ID, r.Type) 465 | } 466 | } 467 | func TestBiliClient_FavoritesResCopy(t *testing.T) { 468 | err := testBiliClient.FavCopyRes(504229694, 492144694, 25422594, []string{"626370388:2", "457308975:2", "90919730:2"}) 469 | if err != nil { 470 | t.Error(err) 471 | t.FailNow() 472 | } 473 | } 474 | func TestBiliClient_FavoritesResMove(t *testing.T) { 475 | err := testBiliClient.FavMoveRes(492144694, 213103794, 25422594, []string{"626370388:2", "457308975:2", "90919730:2"}) 476 | if err != nil { 477 | t.Error(err) 478 | t.FailNow() 479 | } 480 | } 481 | func TestBiliClient_FavoritesResDel(t *testing.T) { 482 | err := testBiliClient.FavDelRes(213103794, []string{"626370388:2", "457308975:2", "90919730:2"}) 483 | if err != nil { 484 | t.FailNow() 485 | } 486 | } 487 | func TestBiliClient_FavoritesResClean(t *testing.T) { 488 | err := testBiliClient.FavCleanRes(213103794) 489 | if err != nil { 490 | t.Error(err) 491 | t.FailNow() 492 | } 493 | } 494 | func TestBiliClient_VideoPlayURLGet(t *testing.T) { 495 | // flv 496 | r, err := testBiliClient.VideoGetPlayURL(99999999, 171776208, 112, 128) 497 | if err != nil { 498 | t.Error(err) 499 | t.FailNow() 500 | } 501 | t.Logf("qn: %d,duration: %d", r.Quality, r.TimeLength) 502 | t.Logf("acceptDesc: %v", r.AcceptDescription) 503 | for _, u := range r.DURL { 504 | t.Logf("order: %d,size: %d,url: %s", u.Order, u.Size, u.URL) 505 | } 506 | } 507 | func TestBiliClient_VideoPlayURLGet2(t *testing.T) { 508 | // dash 509 | r, err := testBiliClient.VideoGetPlayURL(717935322, 406422412, 112, 128|16) 510 | if err != nil { 511 | t.Error(err) 512 | t.FailNow() 513 | } 514 | t.Logf("qn: %d,duration: %d", r.Quality, r.TimeLength) 515 | t.Logf("acceptDesc: %v", r.AcceptDescription) 516 | for _, v := range r.Dash.Video { 517 | t.Logf("id: %d,codecs: %s,baseURL: %s", v.ID, v.Codecs, v.BaseURL) 518 | } 519 | for _, a := range r.Dash.Audio { 520 | t.Logf("id: %d,codecs: %s,baseURL: %s", a.ID, a.Codecs, a.BaseURL) 521 | } 522 | } 523 | func TestBiliClient_EmotePackageAll(t *testing.T) { 524 | packs, err := testBiliClient.EmotePackGetAll("reply") 525 | if err != nil { 526 | t.Error(err) 527 | t.FailNow() 528 | } 529 | for _, pack := range packs { 530 | t.Logf("pack id: %d,text: %s,url: %s", pack.ID, pack.Text, pack.URL) 531 | for i := 0; i < 5; i++ { 532 | t.Logf(" emote id: %d,text: %s", pack.Emote[i].ID, pack.Emote[i].Text) 533 | } 534 | } 535 | } 536 | func TestBiliClient_EmotePackageAll2(t *testing.T) { 537 | packs, err := testBiliClient.EmotePackGetAll("dynamic") 538 | if err != nil { 539 | t.Error(err) 540 | t.FailNow() 541 | } 542 | for _, pack := range packs { 543 | t.Logf("pack id: %d,text: %s,url: %s", pack.ID, pack.Text, pack.URL) 544 | for i := 0; i < 5; i++ { 545 | t.Logf(" emote id: %d,text: %s", pack.Emote[i].ID, pack.Emote[i].Text) 546 | } 547 | } 548 | } 549 | func TestBiliClient_EmotePackageAdd(t *testing.T) { 550 | err := testBiliClient.EmotePackAdd(1, "reply") 551 | if err != nil { 552 | t.Error(err) 553 | t.FailNow() 554 | } 555 | } 556 | func TestBiliClient_AudioMyCollections(t *testing.T) { 557 | coll, err := testBiliClient.AudioGetMyFavLists(1, 2) 558 | // coll, err := testBiliClient.AudioGetMyFavLists(1, 5) 559 | if err != nil { 560 | t.Error(err) 561 | t.FailNow() 562 | } 563 | t.Logf("total: %d,pageCount: %d,cur: %d,pageSize: %d", coll.TotalSize, coll.PageCount, coll.CurPage, coll.PageSize) 564 | for _, a := range coll.Data { 565 | t.Logf("\tid: %d,uid: %d,uname: %s", a.ID, a.UID, a.Uname) 566 | t.Logf("\tsid: %d,play: %d,collect: %d,comment: %d,share: %d", a.Statistic.SID, a.Statistic.Play, a.Statistic.Collect, a.Statistic.Comment, a.Statistic.Share) 567 | t.Logf("\ttitle: %s,ctime: %d,type: %d,published: %d", a.Title, a.Ctime, a.Type, a.Published) 568 | t.Logf("\tcover: %s", a.Cover) 569 | for _, id := range a.Sids { 570 | t.Logf("\t\t%d", id) 571 | } 572 | } 573 | } 574 | func TestBiliClient_AudioPlayURLGet(t *testing.T) { 575 | data, err := testBiliClient.AudioGetPlayURL(2478206, 3) 576 | if err != nil { 577 | t.Error(err) 578 | t.FailNow() 579 | } 580 | t.Logf("auid: %d,type: %d,size: %d", data.SID, data.Type, data.Size) 581 | t.Logf("timeout: %d,title: %s,cover: %s", data.Timeout, data.Title, data.Cover) 582 | for _, u := range data.CDNs { 583 | t.Logf("\turl: %s", u) 584 | } 585 | for _, q := range data.Qualities { 586 | t.Logf( 587 | "type: %d,size: %d,bps: %s,desc: %s,tag: %s,require: %d,requireDesc: %s", 588 | q.Type, 589 | q.Size, 590 | q.Bps, 591 | q.Desc, 592 | q.Tag, 593 | q.Require, 594 | q.RequireDesc, 595 | ) 596 | } 597 | } 598 | func TestBiliClient_AudioIsCollected(t *testing.T) { 599 | is, err := testBiliClient.AudioIsFavored(2478206) 600 | if err != nil { 601 | t.Error(err) 602 | t.FailNow() 603 | } 604 | t.Log(is) 605 | } 606 | func TestBiliClient_AudioIsCoined(t *testing.T) { 607 | coin, err := testBiliClient.AudioIsCoined(2478206) 608 | if err != nil { 609 | t.Error(err) 610 | t.FailNow() 611 | } 612 | t.Log(coin) 613 | } 614 | func TestBiliClient_ChargeTrade(t *testing.T) { 615 | // 没钱,穷,没测试 616 | r, err := testBiliClient.ChargeTradeCreateBp(20, 293793435, "up", 293793435) 617 | if err != nil { 618 | t.Error(err) 619 | t.FailNow() 620 | } 621 | t.Logf( 622 | "mid: %d,upMID: %d,orderNo: %s,bpNum: %s,exp: %d,status: %d,msg: %s", 623 | r.MID, 624 | r.UpMID, 625 | r.OrderNo, 626 | r.BpNum, 627 | r.Exp, 628 | r.Status, 629 | r.Msg, 630 | ) 631 | } 632 | func TestBiliClient_ChargeTradeQrCodeCreate(t *testing.T) { 633 | // 空间充电 634 | qr, err := testBiliClient.ChargeTradeCreateQrCode(2, false, 23215368, "up", 23215368) 635 | if err != nil { 636 | t.Error(err) 637 | t.FailNow() 638 | } 639 | t.Logf("url: %s", qr.QrCodeURL) 640 | t.Logf("exp: %d", qr.Exp) 641 | t.Logf("token: %s", qr.QrToken) 642 | } 643 | func TestBiliClient_ChargeTradeQrCodeCreate2(t *testing.T) { 644 | // 视频充电 645 | qr, err := testBiliClient.ChargeTradeCreateQrCode(2, true, 480366389, "archive", 378015891) 646 | if err != nil { 647 | t.Error(err) 648 | t.FailNow() 649 | } 650 | t.Logf("url: %s", qr.QrCodeURL) 651 | t.Logf("exp: %d", qr.Exp) 652 | t.Logf("token: %s", qr.QrToken) 653 | } 654 | func TestBiliClient_ChargeTradeQrCodeCheck(t *testing.T) { 655 | stat, err := testBiliClient.ChargeTradeCheckQrCode("efe50b495b864c3e9cf5b74b0ae4c482") 656 | if err != nil { 657 | t.Error(err) 658 | t.FailNow() 659 | } 660 | t.Logf("status: %d,mid: %d,order: %s,token: %s", stat.Status, stat.MID, stat.OrderNo, stat.QrToken) 661 | } 662 | func TestBiliClient_SetFollowUser(t *testing.T) { 663 | err := testBiliClient.FollowUser(12876334, false) 664 | if err != nil { 665 | t.Error(err) 666 | t.FailNow() 667 | } 668 | } 669 | func TestBiliClient_FollowingsGetMy(t *testing.T) { 670 | list, err := testBiliClient.FollowingsGetMy() 671 | if err != nil { 672 | t.Error(err) 673 | t.FailNow() 674 | } 675 | t.Log(list) 676 | t.Logf("count: %d", len(list)) 677 | } 678 | func TestBiliClient_FollowingsGetMyDetail(t *testing.T) { 679 | detail, err := testBiliClient.FollowingsGetMyDetail(1, 5, 1) 680 | if err != nil { 681 | t.Error(err) 682 | t.FailNow() 683 | } 684 | t.Logf("count: %d", detail.Total) 685 | for _, l := range detail.List { 686 | t.Logf("mid: %d,uname: %s", l.MID, l.Uname) 687 | } 688 | } 689 | func TestParseDynaAt(t *testing.T) { 690 | ctrls := parseDynaAt( 691 | 1, 692 | "aaaa[doge][doge][热词系列_好耶]sssss[热词系列_再来亿遍][热词系列_好活][tv_惊吓]aa[tv_惊吓]dsaaa[tv_惊吓]@刘庸干净又卫生 @刘庸干净又卫生 @锤子啊二条 #入站必刷# aaaa", 693 | map[string]int64{ 694 | "刘庸干净又卫生": 533459953, 695 | "锤子啊二条": 473056459, 696 | }, 697 | ) 698 | for _, c := range ctrls { 699 | t.Logf("loc: %d,len: %d,data: %s", c.Location, c.Length, c.Data) 700 | } 701 | } 702 | func TestBiliClient_DynaCreatePlain(t *testing.T) { 703 | r := ` 704 | da 705 | das 706 | 707 | saddsdaa[doge][doge][doge][doge][doge][doge] 708 | asdas#入站必刷##入站必刷##入站必刷##入站必刷# 709 | [doge][doge][doge][doge]@刘庸干净又卫生 @刘庸干净又卫生 710 | sada 711 | @刘庸干净又卫生 @刘庸干净又卫生 @刘庸干净又卫生 712 | das 713 | d 714 | @锤子啊二条 715 | @锤子啊二条 @锤子啊二条 @锤子啊二条 @锤子啊二条 716 | [doge][doge][doge][doge][doge] 717 | [doge] 718 | asddasddad 719 | 720 | 721 | sdas 722 | ` 723 | id, err := testBiliClient.DynaCreatePlain( 724 | r, 725 | map[string]int64{ 726 | "刘庸干净又卫生": 533459953, 727 | "锤子啊二条": 473056459, 728 | }) 729 | if err != nil { 730 | t.Error(err) 731 | t.FailNow() 732 | } 733 | // 586276367485776990 734 | t.Log(id) 735 | } 736 | func TestBiliClient_DynaLike(t *testing.T) { 737 | err := testBiliClient.DynaLike(586467484938029595, true) 738 | if err != nil { 739 | t.Error(err) 740 | t.FailNow() 741 | } 742 | } 743 | func TestBiliClient_UploadParse(t *testing.T) { 744 | f1, _ := os.Open("./test_code/1.jpg") 745 | f2, _ := os.Open("./test_code/2.jpg") 746 | f3, _ := os.Open("./test_code/3.png") 747 | 748 | r, err := testBiliClient.UploadParse( 749 | "http://localhost:9999", 750 | "/up", 751 | map[string]string{ 752 | "biz": "dyn", 753 | "category": "daily", 754 | }, 755 | []*FileUpload{ 756 | { 757 | "files", 758 | "1.jpg", 759 | f1, 760 | }, 761 | { 762 | "files", 763 | "2.jpg", 764 | f2, 765 | }, 766 | { 767 | "files", 768 | "3.png", 769 | f3, 770 | }, 771 | }, 772 | ) 773 | if err != nil { 774 | t.Error(err) 775 | t.FailNow() 776 | } 777 | t.Log(r.Data) 778 | } 779 | func TestUploadPic(t *testing.T) { 780 | f, _ := os.Open("./test_code/5.png") 781 | _, err := testBiliClient.UploadParse( 782 | BiliApiURL, 783 | "x/dynamic/feed/draw/upload_bfs", 784 | map[string]string{ 785 | "biz": "dyn", 786 | "category": "daily", 787 | }, 788 | []*FileUpload{{ 789 | Field: "file_up", 790 | Name: "1.gif", 791 | File: f, 792 | }}, 793 | ) 794 | if err != nil { 795 | t.Error(err) 796 | t.FailNow() 797 | } 798 | } 799 | func TestBiliClient_DynaUploadPics(t *testing.T) { 800 | f1, _ := os.Open("./test_code/1.jpg") 801 | f2, _ := os.Open("./test_code/2.jpg") 802 | f3, _ := os.Open("./test_code/3.png") 803 | results, err := testBiliClient.DynaUploadPics([]io.Reader{f1, f2, f3}) 804 | if err != nil { 805 | t.Error(err) 806 | t.FailNow() 807 | } 808 | for _, r := range results { 809 | t.Logf("w: %d,h: %d,url: %s", r.ImageWidth, r.ImageHeight, r.ImageURL) 810 | } 811 | } 812 | func TestBiliClient_DynaCreateDraw(t *testing.T) { 813 | id, err := testBiliClient.DynaCreateDraw("aaaa[doge][doge][doge]@刘庸干净又卫生 ", 814 | map[string]int64{ 815 | "刘庸干净又卫生": 533459953, 816 | "锤子啊二条": 473056459, 817 | }, []*DynaUploadPic{ 818 | { 819 | ImageURL: "http://i0.hdslb.com/bfs/album/062c99566c380bc0c4e5246a5e823791c7800b67.jpg", 820 | ImageWidth: 4300, 821 | ImageHeight: 3040, 822 | }, 823 | }) 824 | if err != nil { 825 | t.Error(err) 826 | t.FailNow() 827 | } 828 | t.Log(id) 829 | } 830 | func TestBiliClient_DynaRepost(t *testing.T) { 831 | err := testBiliClient.DynaRepost(374730265881334172, 832 | "aaaa[doge][doge][doge]@刘庸干净又卫生 ", 833 | map[string]int64{ 834 | "刘庸干净又卫生": 533459953, 835 | "锤子啊二条": 473056459, 836 | }) 837 | if err != nil { 838 | t.Error(err) 839 | t.FailNow() 840 | } 841 | } 842 | func TestBiliClient_DynaDel(t *testing.T) { 843 | err := testBiliClient.DynaDel(586918142975022698) 844 | if err != nil { 845 | t.Error(err) 846 | t.FailNow() 847 | } 848 | } 849 | func TestBiliClient_DynaCreateDraft(t *testing.T) { 850 | r := ` 851 | da 852 | das 853 | 854 | saddsdaa[doge][doge][doge][doge][doge][doge] 855 | asdas#入站必刷##入站必刷##入站必刷##入站必刷# 856 | [doge][doge][doge][doge]@刘庸干净又卫生 @刘庸干净又卫生 857 | sada 858 | @刘庸干净又卫生 @刘庸干净又卫生 @刘庸干净又卫生 859 | das 860 | d 861 | @锤子啊二条 862 | @锤子啊二条 @锤子啊二条 @锤子啊二条 @锤子啊二条 863 | [doge][doge][doge][doge][doge] 864 | [doge] 865 | asddasddad 866 | 867 | 868 | sdas 869 | ` 870 | id, err := testBiliClient.DynaCreateDraft(r, map[string]int64{ 871 | "刘庸干净又卫生": 533459953, 872 | "锤子啊二条": 473056459, 873 | }, []*DynaUploadPic{ 874 | { 875 | ImageURL: "http://i0.hdslb.com/bfs/album/062c99566c380bc0c4e5246a5e823791c7800b67.jpg", 876 | ImageWidth: 4300, 877 | ImageHeight: 3040, 878 | }}, 1635989184) 879 | if err != nil { 880 | t.Error(err) 881 | t.FailNow() 882 | } 883 | t.Log(id) 884 | } 885 | func TestBiliClient_DynaModifyDraft(t *testing.T) { 886 | r := ` 887 | #入站必刷##入站必刷##入站必刷# 888 | [doge][doge][doge][doge]@刘庸干净又卫生 @刘庸干净又卫生 889 | sada 890 | @刘庸干净又卫生 @刘庸干净又卫生 @刘庸干净又卫生 891 | @锤子啊二条 892 | @锤子啊二条 @锤子啊二条 @锤子啊二条 @锤子啊二条 893 | [doge][doge][doge][doge][doge] 894 | [doge] 895 | asddas 896 | ` 897 | err := testBiliClient.DynaModifyDraft(380558, r, map[string]int64{ 898 | "刘庸干净又卫生": 533459953, 899 | "锤子啊二条": 473056459, 900 | }, []*DynaUploadPic{ 901 | { 902 | ImageURL: "http://i0.hdslb.com/bfs/album/062c99566c380bc0c4e5246a5e823791c7800b67.jpg", 903 | ImageWidth: 4300, 904 | ImageHeight: 3040, 905 | }}, 1635714684) 906 | if err != nil { 907 | t.Error(err) 908 | t.FailNow() 909 | } 910 | } 911 | func TestBiliClient_DynaDelDraft(t *testing.T) { 912 | err := testBiliClient.DynaDelDraft(380555) 913 | if err != nil { 914 | t.Error(err) 915 | t.FailNow() 916 | } 917 | } 918 | func TestBiliClient_DynaPublishDraft(t *testing.T) { 919 | id, err := testBiliClient.DynaPublishDraft(380073) 920 | if err != nil { 921 | t.Error(err) 922 | t.FailNow() 923 | } 924 | t.Log(id) 925 | } 926 | func TestBiliClient_DynaGetDrafts(t *testing.T) { 927 | drafts, err := testBiliClient.DynaGetDrafts() 928 | if err != nil { 929 | t.Error(err) 930 | t.FailNow() 931 | } 932 | 933 | for _, df := range drafts.Drafts { 934 | t.Logf("uid: %d,uname: %s", df.UID, df.UserProfile.Info.Uname) 935 | t.Logf("dfid: %d,status: %d,publish: %d", df.DraftID, df.PublishStatus, df.PublishTime) 936 | } 937 | } 938 | func TestBiliClient_LiveSendDanmaku(t *testing.T) { 939 | err := testBiliClient.LiveSendDanmaku(23713127, 16777215, 25, 1, "bilibili[]~( ̄▽ ̄)~", 0) 940 | if err != nil { 941 | t.Error(err) 942 | t.FailNow() 943 | } 944 | } 945 | func TestBiliClient_CommentSend(t *testing.T) { 946 | r, err := testBiliClient.CommentSend(676583423, 1, "bil[OK]ibi[OK]litest22[OK]", 1, 0, 0) 947 | if err != nil { 948 | t.Error(err) 949 | t.FailNow() 950 | } 951 | t.Logf("toast: %s,rpid: %d", r.SuccessToast, r.RPID) 952 | rp := r.Reply 953 | t.Logf("msg: %s,emote: %v,time: %d,rpid: %d", rp.Content.Message, rp.Content.Emote, rp.Ctime, rp.RPID) 954 | } 955 | func TestBiliClient_CommentLike(t *testing.T) { 956 | err := testBiliClient.CommentLike(634118491, 1, 5730547919, true) 957 | if err != nil { 958 | t.Error(err) 959 | t.FailNow() 960 | } 961 | } 962 | func TestBiliClient_CommentHate(t *testing.T) { 963 | err := testBiliClient.CommentHate(634118491, 1, 5730547919, true) 964 | if err != nil { 965 | t.Error(err) 966 | t.FailNow() 967 | } 968 | } 969 | func TestBiliClient_CommentDel(t *testing.T) { 970 | err := testBiliClient.CommentDel(634118491, 1, 5730605988) 971 | if err != nil { 972 | t.Error(err) 973 | t.FailNow() 974 | } 975 | } 976 | func TestBiliClient_CommentSetTop(t *testing.T) { 977 | err := testBiliClient.CommentSetTop(13914715, 12, 5730810134, true) 978 | if err != nil { 979 | t.Error(err) 980 | t.FailNow() 981 | } 982 | } 983 | func TestBiliClient_CommentSetTop2(t *testing.T) { 984 | err := testBiliClient.CommentSetTop(13914715, 12, 5730810134, false) 985 | if err != nil { 986 | t.Error(err) 987 | t.FailNow() 988 | } 989 | } 990 | func TestBiliClient_CommentReport(t *testing.T) { 991 | err := testBiliClient.CommentReport(634118491, 1, 5730216001, 4, "") 992 | if err != nil { 993 | t.Error(err) 994 | t.FailNow() 995 | } 996 | } 997 | func TestBiliClient_CommentReport2(t *testing.T) { 998 | err := testBiliClient.CommentReport(634118491, 1, 5730221753, 0, "test") 999 | if err != nil { 1000 | t.Error(err) 1001 | t.FailNow() 1002 | } 1003 | } 1004 | func TestBiliClient_UserGetInfo(t *testing.T) { 1005 | r, err := testBiliClient.UserGetInfo(2206456) 1006 | if err != nil { 1007 | t.Error(err) 1008 | t.FailNow() 1009 | } 1010 | t.Logf("mid: %d,name: %s,sex: %s,level: %d,sign: %s", r.MID, r.Name, r.Sex, r.Level, r.Sign) 1011 | t.Logf("live: %d,officialDesc: %s,nameplateName: %s,pendantName: %s,vip: %s", r.LiveRoom.LiveStatus, r.Official.Title, r.Nameplate.Name, r.Pendant.Name, r.Vip.Label.Text) 1012 | } 1013 | -------------------------------------------------------------------------------- /comm_client.go: -------------------------------------------------------------------------------- 1 | package biligo 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/golang/protobuf/proto" 6 | "github.com/iyear/biligo/internal/util" 7 | "github.com/iyear/biligo/proto/dm" 8 | "github.com/tidwall/gjson" 9 | "net/http" 10 | "strconv" 11 | ) 12 | 13 | type CommClient struct { 14 | *baseClient 15 | } 16 | type CommSetting struct { 17 | 18 | // 自定义http client 19 | // 20 | // 默认为 http.http.DefaultClient 21 | Client *http.Client 22 | 23 | // Debug模式 true将输出请求信息 false不输出 24 | // 25 | // 默认false 26 | DebugMode bool 27 | 28 | // 自定义UserAgent 29 | // 30 | // 默认Chrome随机Agent 31 | UserAgent string 32 | } 33 | 34 | // NewCommClient 35 | // 36 | // Setting的Auth属性可以随意填写或传入nil,Auth不起到作用,用于访问公共API 37 | func NewCommClient(setting *CommSetting) *CommClient { 38 | return &CommClient{baseClient: newBaseClient(&baseSetting{ 39 | Client: setting.Client, 40 | DebugMode: setting.DebugMode, 41 | UserAgent: setting.UserAgent, 42 | Prefix: "CommClient ", 43 | })} 44 | } 45 | 46 | // SetClient 47 | // 48 | // 设置Client,可以用来更换代理等操作 49 | func (c *CommClient) SetClient(client *http.Client) { 50 | c.client = client 51 | } 52 | 53 | // SetUA 54 | // 55 | // 设置UA 56 | func (c *CommClient) SetUA(ua string) { 57 | c.ua = ua 58 | } 59 | 60 | // Raw 61 | // 62 | // base末尾带/ 63 | func (c *CommClient) Raw(base, endpoint, method string, payload map[string]string) ([]byte, error) { 64 | // 不用侵入处理则传入nil 65 | raw, err := c.raw(base, endpoint, method, payload, nil, nil) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return raw, nil 70 | } 71 | 72 | // RawParse 73 | // 74 | // base末尾带/ 75 | func (c *CommClient) RawParse(base, endpoint, method string, payload map[string]string) (*Response, error) { 76 | raw, err := c.Raw(base, endpoint, method, payload) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return c.parse(raw) 81 | } 82 | 83 | // GetGeoInfo 调用哔哩哔哩API获取地理位置等信息 84 | // 85 | // 会受到自定义 http.Client 代理的影响 86 | func (c *CommClient) GetGeoInfo() (*GeoInfo, error) { 87 | resp, err := c.RawParse(BiliApiURL, 88 | "x/web-interface/zone", 89 | "GET", 90 | nil, 91 | ) 92 | if err != nil { 93 | return nil, err 94 | } 95 | var info *GeoInfo 96 | if err = json.Unmarshal(resp.Data, &info); err != nil { 97 | return nil, err 98 | } 99 | return info, nil 100 | } 101 | 102 | // FollowingsGetDetail 获取个人详细的关注列表 103 | // 104 | // pn 页码 105 | // 106 | // ps 每页项数,最大50 107 | func (c *CommClient) FollowingsGetDetail(mid int64, pn int, ps int) (*FollowingsDetail, error) { 108 | resp, err := c.RawParse( 109 | BiliApiURL, 110 | "x/relation/followings", 111 | "GET", 112 | map[string]string{ 113 | "vmid": strconv.FormatInt(mid, 10), 114 | "pn": strconv.Itoa(pn), 115 | "ps": strconv.Itoa(ps), 116 | }, 117 | ) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | var detail = &FollowingsDetail{} 123 | if err = json.Unmarshal(resp.Data, &detail); err != nil { 124 | return nil, err 125 | } 126 | return detail, nil 127 | } 128 | 129 | // VideoGetStat 130 | // 131 | // 获取稿件状态数 132 | func (c *CommClient) VideoGetStat(aid int64) (*VideoSingleStat, error) { 133 | resp, err := c.RawParse(BiliApiURL, 134 | "x/web-interface/archive/stat", 135 | "GET", 136 | map[string]string{ 137 | "aid": strconv.FormatInt(aid, 10), 138 | }, 139 | ) 140 | if err != nil { 141 | return nil, err 142 | } 143 | var stat *VideoSingleStat 144 | if err = json.Unmarshal(resp.Data, &stat); err != nil { 145 | return nil, err 146 | } 147 | return stat, nil 148 | } 149 | 150 | // VideoGetInfo 返回视频详细信息,数据较多,可以使用单独的接口获取部分数据 151 | // 152 | // 限制游客访问的视频会返回错误,请使用 BiliClient 发起请求 153 | func (c *CommClient) VideoGetInfo(aid int64) (*VideoInfo, error) { 154 | resp, err := c.RawParse(BiliApiURL, 155 | "x/web-interface/view", 156 | "GET", 157 | map[string]string{ 158 | "aid": strconv.FormatInt(aid, 10), 159 | }, 160 | ) 161 | if err != nil { 162 | return nil, err 163 | } 164 | var info *VideoInfo 165 | if err = json.Unmarshal(resp.Data, &info); err != nil { 166 | return nil, err 167 | } 168 | return info, nil 169 | } 170 | 171 | // VideoGetDescription 172 | // 173 | // 获取稿件简介 174 | func (c *CommClient) VideoGetDescription(aid int64) (string, error) { 175 | resp, err := c.RawParse(BiliApiURL, 176 | "x/web-interface/archive/desc", 177 | "GET", 178 | map[string]string{ 179 | "aid": strconv.FormatInt(aid, 10), 180 | }, 181 | ) 182 | if err != nil { 183 | return "", err 184 | } 185 | var desc string 186 | if err = json.Unmarshal(resp.Data, &desc); err != nil { 187 | return "", err 188 | } 189 | return desc, nil 190 | } 191 | 192 | // VideoGetPageList 193 | // 194 | // 获取分P列表 195 | func (c *CommClient) VideoGetPageList(aid int64) ([]*VideoPage, error) { 196 | resp, err := c.RawParse(BiliApiURL, 197 | "x/player/pagelist", 198 | "GET", 199 | map[string]string{ 200 | "aid": strconv.FormatInt(aid, 10), 201 | }, 202 | ) 203 | if err != nil { 204 | return nil, err 205 | } 206 | var list []*VideoPage 207 | if err = json.Unmarshal(resp.Data, &list); err != nil { 208 | return nil, err 209 | } 210 | return list, nil 211 | } 212 | 213 | // VideoGetOnlineNum 214 | // 215 | // 返回所有终端总计在线观看人数和WEB端在线观看人数 (用类似10万+的文字表示) cid用于分P标识 216 | func (c *CommClient) VideoGetOnlineNum(aid int64, cid int64) (total string, web string, e error) { 217 | resp, err := c.RawParse(BiliApiURL, 218 | "x/player/online/total", 219 | "GET", 220 | map[string]string{ 221 | "aid": strconv.FormatInt(aid, 10), 222 | "cid": strconv.FormatInt(cid, 10), 223 | }, 224 | ) 225 | if err != nil { 226 | return "", "", err 227 | } 228 | var num struct { 229 | Total string `json:"total,omitempty"` 230 | Count string `json:"count,omitempty"` 231 | } 232 | if err = json.Unmarshal(resp.Data, &num); err != nil { 233 | return "", "", err 234 | } 235 | return num.Total, num.Count, nil 236 | } 237 | 238 | // VideoTags 239 | // 240 | // 未登录无法获取 IsAtten,Liked,Hated 字段 241 | func (c *CommClient) VideoTags(aid int64) ([]*VideoTag, error) { 242 | resp, err := c.RawParse(BiliApiURL, 243 | "x/tag/archive/tags", 244 | "GET", 245 | map[string]string{ 246 | "aid": strconv.FormatInt(aid, 10), 247 | }, 248 | ) 249 | if err != nil { 250 | return nil, err 251 | } 252 | var tags []*VideoTag 253 | if err = json.Unmarshal(resp.Data, &tags); err != nil { 254 | return nil, err 255 | } 256 | return tags, nil 257 | } 258 | 259 | // VideoGetRecommend 获取视频的相关视频推荐 260 | // 261 | // 最多获取40条推荐视频 262 | func (c *CommClient) VideoGetRecommend(aid int64) ([]*VideoRecommendInfo, error) { 263 | resp, err := c.RawParse(BiliApiURL, 264 | "x/web-interface/archive/related", 265 | "GET", 266 | map[string]string{ 267 | "aid": strconv.FormatInt(aid, 10), 268 | }, 269 | ) 270 | if err != nil { 271 | return nil, err 272 | } 273 | var videos []*VideoRecommendInfo 274 | if err = json.Unmarshal(resp.Data, &videos); err != nil { 275 | return nil, err 276 | } 277 | return videos, nil 278 | } 279 | 280 | // VideoGetPlayURL 获取视频取流地址 281 | // 282 | // 所有参数、返回信息和取流方法的说明请直接前往:https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/video/videostream_url.md 283 | func (c *CommClient) VideoGetPlayURL(aid int64, cid int64, qn int, fnval int) (*VideoPlayURLResult, error) { 284 | resp, err := c.RawParse( 285 | BiliApiURL, 286 | "x/player/playurl", 287 | "GET", 288 | map[string]string{ 289 | "avid": strconv.FormatInt(aid, 10), 290 | "cid": strconv.FormatInt(cid, 10), 291 | "qn": strconv.Itoa(qn), 292 | "fnval": strconv.Itoa(fnval), 293 | "fnver": "0", 294 | "fourk": "1", 295 | }, 296 | ) 297 | if err != nil { 298 | return nil, err 299 | } 300 | var r *VideoPlayURLResult 301 | if err = json.Unmarshal(resp.Data, &r); err != nil { 302 | return nil, err 303 | } 304 | return r, nil 305 | } 306 | 307 | // VideoShot 获取视频快照 308 | // 309 | // cid属性非必须 传入0表示1P 310 | // 311 | // index为JSON数组截取时间表 true:需要 false:不需要 传入false则Index属性为空 312 | func (c *CommClient) VideoShot(aid int64, cid int64, index bool) (*VideoShot, error) { 313 | resp, err := c.RawParse(BiliApiURL, 314 | "x/player/videoshot", 315 | "GET", 316 | map[string]string{ 317 | "aid": strconv.FormatInt(aid, 10), 318 | "cid": util.IF(cid == 0, "", strconv.FormatInt(cid, 10)).(string), 319 | "index": util.IF(index, "1", "0").(string), 320 | }, 321 | ) 322 | if err != nil { 323 | return nil, err 324 | } 325 | var shot *VideoShot 326 | if err = json.Unmarshal(resp.Data, &shot); err != nil { 327 | return nil, err 328 | } 329 | return shot, nil 330 | } 331 | 332 | // DanmakuGetLikes 获取弹幕点赞数,一次可以获取多条弹幕 333 | // 334 | // Link:https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/danmaku/action.md#%E6%9F%A5%E8%AF%A2%E5%BC%B9%E5%B9%95%E7%82%B9%E8%B5%9E%E6%95%B0 335 | // 336 | // 返回一个map,key为dmid,value为相关信息 337 | // 未登录时UserLike属性恒为0 338 | func (c *CommClient) DanmakuGetLikes(cid int64, dmids []uint64) (map[uint64]*DanmakuGetLikesResult, error) { 339 | resp, err := c.RawParse( 340 | BiliApiURL, 341 | "x/v2/dm/thumbup/stats", 342 | "GET", 343 | map[string]string{ 344 | "oid": strconv.FormatInt(cid, 10), 345 | "ids": util.Uint64SliceToString(dmids, ","), 346 | }, 347 | ) 348 | if err != nil { 349 | return nil, err 350 | } 351 | var result = make(map[uint64]*DanmakuGetLikesResult) 352 | for _, dmid := range dmids { 353 | var r *DanmakuGetLikesResult 354 | if err = json.Unmarshal([]byte(gjson.Get(string(resp.Data), strconv.FormatUint(dmid, 10)).Raw), &r); err != nil { 355 | return nil, err 356 | } 357 | result[dmid] = r 358 | } 359 | return result, nil 360 | } 361 | 362 | // GetRelationStat 363 | // 364 | // 获取关系状态数,Whisper和Black恒为0 365 | func (c *CommClient) GetRelationStat(mid int64) (*RelationStat, error) { 366 | resp, err := c.RawParse( 367 | BiliApiURL, 368 | "x/relation/stat", 369 | "GET", 370 | map[string]string{ 371 | "vmid": strconv.FormatInt(mid, 10), 372 | }, 373 | ) 374 | if err != nil { 375 | return nil, err 376 | } 377 | var stat *RelationStat 378 | if err = json.Unmarshal(resp.Data, &stat); err != nil { 379 | return nil, err 380 | } 381 | return stat, nil 382 | } 383 | 384 | // SpaceGetTopArchive 385 | // 386 | // 获取空间置顶稿件 387 | func (c *CommClient) SpaceGetTopArchive(mid int64) (*SpaceVideo, error) { 388 | resp, err := c.RawParse( 389 | BiliApiURL, 390 | "x/space/top/arc", 391 | "GET", 392 | map[string]string{ 393 | "vmid": strconv.FormatInt(mid, 10), 394 | }, 395 | ) 396 | if err != nil { 397 | return nil, err 398 | } 399 | var top *SpaceVideo 400 | if err = json.Unmarshal(resp.Data, &top); err != nil { 401 | return nil, err 402 | } 403 | return top, nil 404 | } 405 | 406 | // SpaceGetMasterpieces 407 | // 408 | // 获取UP代表作 409 | func (c *CommClient) SpaceGetMasterpieces(mid int64) ([]*SpaceVideo, error) { 410 | resp, err := c.RawParse( 411 | BiliApiURL, 412 | "x/space/masterpiece", 413 | "GET", 414 | map[string]string{ 415 | "vmid": strconv.FormatInt(mid, 10), 416 | }, 417 | ) 418 | if err != nil { 419 | return nil, err 420 | } 421 | var mp []*SpaceVideo 422 | if err = json.Unmarshal(resp.Data, &mp); err != nil { 423 | return nil, err 424 | } 425 | return mp, nil 426 | } 427 | 428 | // SpaceGetTags 429 | // 430 | // 获取空间用户个人TAG 上限5条,且内容由用户自定义 带有转义 431 | func (c *CommClient) SpaceGetTags(mid int64) ([]string, error) { 432 | resp, err := c.RawParse( 433 | BiliApiURL, 434 | "x/space/acc/tags", 435 | "GET", 436 | map[string]string{ 437 | "mid": strconv.FormatInt(mid, 10), 438 | }, 439 | ) 440 | if err != nil { 441 | return nil, err 442 | } 443 | // B站这写的是个啥玩意儿 444 | var tags []string 445 | for _, tag := range gjson.Get(string(resp.Data), "0.tags").Array() { 446 | tags = append(tags, tag.String()) 447 | } 448 | return tags, nil 449 | } 450 | 451 | // SpaceGetNotice 452 | // 453 | // 获取空间公告内容 454 | func (c *CommClient) SpaceGetNotice(mid int64) (string, error) { 455 | resp, err := c.RawParse( 456 | BiliApiURL, 457 | "x/space/notice", 458 | "GET", 459 | map[string]string{ 460 | "mid": strconv.FormatInt(mid, 10), 461 | }, 462 | ) 463 | if err != nil { 464 | return "", err 465 | } 466 | // 新建一个变量再 unmarshal 可以把转义部分转回来 467 | // 直接返回 resp.Data 会带转义符 468 | var notice string 469 | if err = json.Unmarshal(resp.Data, ¬ice); err != nil { 470 | return "", err 471 | } 472 | return notice, nil 473 | } 474 | 475 | // SpaceGetLastPlayGame 476 | // 477 | // 获取用户空间近期玩的游戏 478 | func (c *CommClient) SpaceGetLastPlayGame(mid int64) ([]*SpaceGame, error) { 479 | resp, err := c.RawParse( 480 | BiliApiURL, 481 | "x/space/lastplaygame", 482 | "GET", 483 | map[string]string{ 484 | "mid": strconv.FormatInt(mid, 10), 485 | }, 486 | ) 487 | if err != nil { 488 | return nil, err 489 | } 490 | var games []*SpaceGame 491 | if err = json.Unmarshal(resp.Data, &games); err != nil { 492 | return nil, err 493 | } 494 | return games, nil 495 | } 496 | 497 | // SpaceGetLastVideoCoin 498 | // 499 | // 获取用户最近投币的视频明细 如设置隐私查看自己的使用 BiliClient 访问 500 | func (c *CommClient) SpaceGetLastVideoCoin(mid int64) ([]*SpaceVideoCoin, error) { 501 | resp, err := c.RawParse( 502 | BiliApiURL, 503 | "x/space/coin/video", 504 | "GET", 505 | map[string]string{ 506 | "vmid": strconv.FormatInt(mid, 10), 507 | }, 508 | ) 509 | if err != nil { 510 | return nil, err 511 | } 512 | var info []*SpaceVideoCoin 513 | if err = json.Unmarshal(resp.Data, &info); err != nil { 514 | return nil, err 515 | } 516 | return info, nil 517 | } 518 | 519 | // SpaceSearchVideo 520 | // 521 | // 获取用户投稿视频明细 522 | // 523 | // order 排序方式 默认为pubdate 可留空 524 | // 525 | // 最新发布:pubdate 526 | // 最多播放:click 527 | // 最多收藏:stow 528 | // 529 | // tid 筛选分区 0:不进行分区筛选 530 | // 531 | // keyword 关键词 可留空 532 | // 533 | // pn 页码 534 | // 535 | // ps 每页项数 536 | func (c *CommClient) SpaceSearchVideo(mid int64, order string, tid int, keyword string, pn int, ps int) (*SpaceVideoSearchResult, error) { 537 | resp, err := c.RawParse( 538 | BiliApiURL, 539 | "x/space/wbi/arc/search", 540 | "GET", 541 | map[string]string{ 542 | "mid": strconv.FormatInt(mid, 10), 543 | "order": order, 544 | "tid": strconv.Itoa(tid), 545 | "keyword": keyword, 546 | "pn": strconv.Itoa(pn), 547 | "ps": strconv.Itoa(ps), 548 | }, 549 | ) 550 | if err != nil { 551 | return nil, err 552 | } 553 | var result *SpaceVideoSearchResult 554 | if err = json.Unmarshal(resp.Data, &result); err != nil { 555 | return nil, err 556 | } 557 | return result, nil 558 | } 559 | 560 | // ChanGet 561 | // 562 | // 获取用户频道列表 563 | func (c *CommClient) ChanGet(mid int64) (*ChannelList, error) { 564 | resp, err := c.RawParse( 565 | BiliApiURL, 566 | "x/space/channel/list", 567 | "GET", 568 | map[string]string{ 569 | "mid": strconv.FormatInt(mid, 10), 570 | }, 571 | ) 572 | if err != nil { 573 | return nil, err 574 | } 575 | var list *ChannelList 576 | if err = json.Unmarshal(resp.Data, &list); err != nil { 577 | return nil, err 578 | } 579 | return list, nil 580 | } 581 | 582 | // ChanGetVideo 583 | // 584 | // 获取用户频道视频 585 | // 586 | // cid 频道ID 587 | // 588 | // pn 页码 589 | // 590 | // ps 每页项数 591 | func (c *CommClient) ChanGetVideo(mid int64, cid int64, pn int, ps int) (*ChanVideo, error) { 592 | resp, err := c.RawParse( 593 | BiliApiURL, 594 | "x/space/channel/video", 595 | "GET", 596 | map[string]string{ 597 | "mid": strconv.FormatInt(mid, 10), 598 | "cid": strconv.FormatInt(cid, 10), 599 | "pn": strconv.Itoa(pn), 600 | "ps": strconv.Itoa(ps), 601 | }, 602 | ) 603 | if err != nil { 604 | return nil, err 605 | } 606 | var videos *ChanVideo 607 | if err = json.Unmarshal(resp.Data, &videos); err != nil { 608 | return nil, err 609 | } 610 | return videos, nil 611 | } 612 | 613 | // FavGet 614 | // 615 | // 获取用户的公开收藏夹列表 616 | func (c *CommClient) FavGet(mid int64) (*FavoritesList, error) { 617 | resp, err := c.RawParse( 618 | BiliApiURL, 619 | "x/v3/fav/folder/created/list-all", 620 | "GET", 621 | map[string]string{ 622 | "up_mid": strconv.FormatInt(mid, 10), 623 | }, 624 | ) 625 | if err != nil { 626 | return nil, err 627 | } 628 | var list *FavoritesList 629 | if err = json.Unmarshal(resp.Data, &list); err != nil { 630 | return nil, err 631 | } 632 | if list == nil { 633 | return &FavoritesList{}, nil 634 | } 635 | return list, nil 636 | } 637 | 638 | // FavGetDetail 639 | // 640 | // 获取收藏夹详细信息,部分信息需要登录,请使用 BiliClient 请求 641 | func (c *CommClient) FavGetDetail(mlid int64) (*FavDetail, error) { 642 | resp, err := c.RawParse( 643 | BiliApiURL, 644 | "x/v3/fav/folder/info", 645 | "GET", 646 | map[string]string{ 647 | "media_id": strconv.FormatInt(mlid, 10), 648 | }, 649 | ) 650 | if err != nil { 651 | return nil, err 652 | } 653 | var detail *FavDetail 654 | if err = json.Unmarshal(resp.Data, &detail); err != nil { 655 | return nil, err 656 | } 657 | return detail, nil 658 | } 659 | 660 | // FavGetRes 661 | // 662 | // 获取收藏夹全部内容id 查询权限收藏夹时请使用 BiliClient 请求 663 | func (c *CommClient) FavGetRes(mlid int64) ([]*FavRes, error) { 664 | resp, err := c.RawParse( 665 | BiliApiURL, 666 | "x/v3/fav/resource/ids", 667 | "GET", 668 | map[string]string{ 669 | "media_id": strconv.FormatInt(mlid, 10), 670 | }, 671 | ) 672 | if err != nil { 673 | return nil, err 674 | } 675 | var r []*FavRes 676 | if err = json.Unmarshal(resp.Data, &r); err != nil { 677 | return nil, err 678 | } 679 | return r, nil 680 | } 681 | 682 | // FavGetResDetail 获取收藏夹内容详细内容,带过滤功能 683 | // 684 | // 查询权限收藏夹时请使用 BiliClient 请求 685 | // 686 | // tid 分区id,用于筛选,传入0代表所有分区 687 | // 688 | // keyword 关键词筛选 可留空 689 | // 690 | // order 留空默认按收藏时间 691 | // 692 | // 按收藏时间:mtime 693 | // 按播放量: view 694 | // 按投稿时间:pubtime 695 | // 696 | // tp 内容类型 不知道作用,传入0即可 697 | // 698 | // pn 页码 699 | // 700 | // ps 每页项数 ps不能太大,会报错 701 | func (c *CommClient) FavGetResDetail(mlid int64, tid int, keyword string, order string, tp int, pn int, ps int) (*FavResDetail, error) { 702 | resp, err := c.RawParse( 703 | BiliApiURL, 704 | "x/v3/fav/resource/list", 705 | "GET", 706 | map[string]string{ 707 | "media_id": strconv.FormatInt(mlid, 10), 708 | "tid": strconv.Itoa(tid), 709 | "keyword": keyword, 710 | "order": order, 711 | "type": strconv.Itoa(tp), 712 | "ps": strconv.Itoa(ps), 713 | "pn": strconv.Itoa(pn), 714 | }, 715 | ) 716 | if err != nil { 717 | return nil, err 718 | } 719 | var detail *FavResDetail 720 | if err = json.Unmarshal(resp.Data, &detail); err != nil { 721 | return nil, err 722 | } 723 | return detail, nil 724 | } 725 | 726 | // GetDailyNum 727 | // 728 | // 获取每日分区投稿数 729 | func (c *CommClient) GetDailyNum() (map[int]int, error) { 730 | resp, err := c.RawParse(BiliApiURL, "x/web-interface/online", "GET", nil) 731 | if err != nil { 732 | return nil, err 733 | } 734 | var result = make(map[int]int) 735 | gjson.Get(string(resp.Data), "region_count").ForEach(func(key, value gjson.Result) bool { 736 | result[int(key.Int())] = int(value.Int()) 737 | return true 738 | }) 739 | return result, nil 740 | } 741 | 742 | // GetUnixNow 743 | // 744 | // 获取服务器的Unix时间戳 745 | func (c *CommClient) GetUnixNow() (int64, error) { 746 | resp, err := c.RawParse( 747 | BiliApiURL, 748 | "x/report/click/now", 749 | "GET", 750 | nil, 751 | ) 752 | if err != nil { 753 | return -1, err 754 | } 755 | var t struct { 756 | Now int64 `json:"now,omitempty"` 757 | } 758 | if err = json.Unmarshal(resp.Data, &t); err != nil { 759 | return -1, err 760 | } 761 | return t.Now, nil 762 | } 763 | 764 | // DanmakuGetByPb 765 | // 766 | // 获取实时弹幕(protobuf接口) 767 | func (c *CommClient) DanmakuGetByPb(tp int, cid int64, seg int) (*DanmakuResp, error) { 768 | resp, err := c.Raw( 769 | BiliApiURL, 770 | "x/v2/dm/web/seg.so", 771 | "GET", 772 | map[string]string{ 773 | "type": strconv.Itoa(tp), 774 | "oid": strconv.FormatInt(cid, 10), 775 | "segment_index": strconv.Itoa(seg), 776 | }, 777 | ) 778 | if err != nil { 779 | return nil, err 780 | } 781 | var reply dm.DmSegMobileReply 782 | var r = &DanmakuResp{} 783 | if err := proto.Unmarshal(resp, &reply); err != nil { 784 | return nil, err 785 | } 786 | for _, elem := range reply.GetElems() { 787 | r.Danmaku = append(r.Danmaku, &Danmaku{ 788 | ID: uint64(elem.Id), 789 | Progress: int64(elem.Progress), 790 | Mode: int(elem.Mode), 791 | FontSize: int(elem.Fontsize), 792 | Color: int(elem.Color), 793 | MidHash: elem.MidHash, 794 | Content: elem.Content, 795 | Ctime: elem.Ctime, 796 | Weight: int(elem.Weight), 797 | Action: elem.Action, 798 | Pool: int(elem.Pool), 799 | IDStr: elem.IdStr, 800 | Attr: int(elem.Attr), 801 | }) 802 | } 803 | return r, nil 804 | 805 | } 806 | 807 | // DanmakuGetShot 808 | // 809 | // 获取弹幕快照(最新的几条弹幕) 810 | func (c *CommClient) DanmakuGetShot(aid int64) ([]string, error) { 811 | resp, err := c.RawParse( 812 | BiliApiURL, 813 | "x/v2/dm/ajax", 814 | "GET", 815 | map[string]string{ 816 | "aid": strconv.FormatInt(aid, 10), 817 | }, 818 | ) 819 | if err != nil { 820 | return nil, err 821 | } 822 | var strings []string 823 | if err = json.Unmarshal(resp.Data, &strings); err != nil { 824 | return nil, err 825 | } 826 | return strings, nil 827 | } 828 | 829 | // EmoteGetFreePack 获取免费表情包列表 830 | // 831 | // business 使用场景 reply:评论区 dynamic:动态 832 | // 833 | // 全为免费表情包,如需获取个人专属表情包请使用 BiliClient 请求 834 | func (c *CommClient) EmoteGetFreePack(business string) ([]*EmotePack, error) { 835 | resp, err := c.RawParse( 836 | BiliApiURL, 837 | "x/emote/user/panel/web", 838 | "GET", 839 | map[string]string{ 840 | "business": business, 841 | }, 842 | ) 843 | if err != nil { 844 | return nil, err 845 | } 846 | var pack struct { 847 | Packages []*EmotePack `json:"packages,omitempty"` 848 | } 849 | if err = json.Unmarshal(resp.Data, &pack); err != nil { 850 | return nil, err 851 | } 852 | return pack.Packages, nil 853 | } 854 | 855 | // EmoteGetPackDetail 获取指定表情包明细 856 | // 857 | // business 使用场景 reply:评论区 dynamic:动态 858 | // 859 | // ids 多个表情包id的数组 860 | func (c *CommClient) EmoteGetPackDetail(business string, ids []int64) ([]*EmotePack, error) { 861 | resp, err := c.RawParse( 862 | BiliApiURL, 863 | "x/emote/package", 864 | "GET", 865 | map[string]string{ 866 | "business": business, 867 | "ids": util.Int64SliceToString(ids, ","), 868 | }, 869 | ) 870 | if err != nil { 871 | return nil, err 872 | } 873 | var packs struct { 874 | Packages []*EmotePack `json:"packages,omitempty"` 875 | } 876 | if err = json.Unmarshal(resp.Data, &packs); err != nil { 877 | return nil, err 878 | } 879 | return packs.Packages, nil 880 | } 881 | 882 | // AudioGetInfo 883 | // 884 | // 获取音频信息 部分属性需要登录,请使用 BiliClient 请求 885 | func (c *CommClient) AudioGetInfo(auid int64) (*AudioInfo, error) { 886 | resp, err := c.RawParse( 887 | BiliMainURL, 888 | "audio/music-service-c/web/song/info", 889 | "GET", 890 | map[string]string{ 891 | "sid": strconv.FormatInt(auid, 10), 892 | }, 893 | ) 894 | if err != nil { 895 | return nil, err 896 | } 897 | var info *AudioInfo 898 | if err = json.Unmarshal(resp.Data, &info); err != nil { 899 | return nil, err 900 | } 901 | return info, nil 902 | } 903 | 904 | // AudioGetTags 获取音频TAGs 905 | // 906 | // 根据页面显示观察,应该是歌曲分类 907 | func (c *CommClient) AudioGetTags(auid int64) ([]*AudioTag, error) { 908 | resp, err := c.RawParse( 909 | BiliMainURL, 910 | "audio/music-service-c/web/tag/song", 911 | "GET", 912 | map[string]string{ 913 | "sid": strconv.FormatInt(auid, 10), 914 | }, 915 | ) 916 | if err != nil { 917 | return nil, err 918 | } 919 | var tags []*AudioTag 920 | if err = json.Unmarshal(resp.Data, &tags); err != nil { 921 | return nil, err 922 | } 923 | return tags, nil 924 | } 925 | 926 | // AudioGetMembers 927 | // 928 | // 获取音频创作者信息 929 | func (c *CommClient) AudioGetMembers(auid int64) ([]*AudioMember, error) { 930 | resp, err := c.RawParse( 931 | BiliMainURL, 932 | "audio/music-service-c/web/member/song", 933 | "GET", 934 | map[string]string{ 935 | "sid": strconv.FormatInt(auid, 10), 936 | }, 937 | ) 938 | if err != nil { 939 | return nil, err 940 | } 941 | var members []*AudioMember 942 | if err = json.Unmarshal(resp.Data, &members); err != nil { 943 | return nil, err 944 | } 945 | return members, nil 946 | } 947 | 948 | // AudioGetLyric 获取音频歌词 949 | // 950 | // 同 AudioGetInfo 中的lrc歌词 951 | func (c *CommClient) AudioGetLyric(auid int64) (string, error) { 952 | resp, err := c.RawParse( 953 | BiliMainURL, 954 | "audio/music-service-c/web/song/lyric", 955 | "GET", 956 | map[string]string{ 957 | "sid": strconv.FormatInt(auid, 10), 958 | }, 959 | ) 960 | if err != nil { 961 | return "", err 962 | } 963 | var lrc string 964 | if err = json.Unmarshal(resp.Data, &lrc); err != nil { 965 | return "", err 966 | } 967 | return lrc, nil 968 | } 969 | 970 | // AudioGetStat 获取歌曲状态数 971 | // 972 | // 没有投币数 获取投币数请使用 AudioGetInfo 973 | func (c *CommClient) AudioGetStat(auid int64) (*AudioInfoStat, error) { 974 | resp, err := c.RawParse( 975 | BiliMainURL, 976 | "audio/music-service-c/web/stat/song", 977 | "GET", 978 | map[string]string{ 979 | "sid": strconv.FormatInt(auid, 10), 980 | }, 981 | ) 982 | if err != nil { 983 | return nil, err 984 | } 985 | var stat = &AudioInfoStat{} 986 | if err = json.Unmarshal(resp.Data, &stat); err != nil { 987 | return nil, err 988 | } 989 | return stat, nil 990 | } 991 | 992 | // AudioGetPlayURL 获取音频流URL 993 | // 994 | // 最多获取到 995 | // 996 | // qn 音质 997 | // 998 | // 0 流畅 128K 999 | // 1000 | // 1 标准 192K 1001 | // 1002 | // 2 高品质 320K 1003 | // 1004 | // 3 无损 FLAC(大会员) 1005 | // 1006 | // 最高获取到 320K 音质,更高音质请使用 BiliClient 请求 1007 | // 1008 | // 取流:https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/audio/musicstream_url.md#%E9%9F%B3%E9%A2%91%E6%B5%81%E7%9A%84%E8%8E%B7%E5%8F%96 1009 | func (c *CommClient) AudioGetPlayURL(auid int64, qn int) (*AudioPlayURL, error) { 1010 | resp, err := c.RawParse( 1011 | BiliApiURL, 1012 | "audio/music-service-c/url", 1013 | "GET", 1014 | map[string]string{ 1015 | "songid": strconv.FormatInt(auid, 10), 1016 | "quality": strconv.Itoa(qn), 1017 | "privilege": "2", 1018 | "mid": "2", 1019 | "platform": "web", 1020 | }, 1021 | ) 1022 | if err != nil { 1023 | return nil, err 1024 | } 1025 | var play *AudioPlayURL 1026 | if err = json.Unmarshal(resp.Data, &play); err != nil { 1027 | return nil, err 1028 | } 1029 | return play, nil 1030 | } 1031 | 1032 | // ChargeSpaceGetList 1033 | // 1034 | // 获取用户空间充电名单 1035 | func (c *CommClient) ChargeSpaceGetList(mid int64) (*ChargeSpaceList, error) { 1036 | resp, err := c.RawParse( 1037 | BiliElecURL, 1038 | "api/query.rank.do", 1039 | "GET", 1040 | map[string]string{ 1041 | "mid": strconv.FormatInt(mid, 10), 1042 | }, 1043 | ) 1044 | if err != nil { 1045 | return nil, err 1046 | } 1047 | var list *ChargeSpaceList 1048 | if err = json.Unmarshal(resp.Data, &list); err != nil { 1049 | return nil, err 1050 | } 1051 | return list, nil 1052 | } 1053 | 1054 | // ChargeVideoGetList 1055 | // 1056 | // 获取用户视频充电名单 1057 | func (c *CommClient) ChargeVideoGetList(mid int64, aid int64) (*ChargeVideoList, error) { 1058 | resp, err := c.RawParse( 1059 | BiliApiURL, 1060 | "x/web-interface/elec/show", 1061 | "GET", 1062 | map[string]string{ 1063 | "mid": strconv.FormatInt(mid, 10), 1064 | "aid": strconv.FormatInt(aid, 10), 1065 | }, 1066 | ) 1067 | if err != nil { 1068 | return nil, err 1069 | } 1070 | var list *ChargeVideoList 1071 | if err = json.Unmarshal(resp.Data, &list); err != nil { 1072 | return nil, err 1073 | } 1074 | return list, nil 1075 | 1076 | } 1077 | 1078 | // LiveGetRoomInfoByMID 1079 | // 1080 | // 从mid获取直播间信息 1081 | func (c *CommClient) LiveGetRoomInfoByMID(mid int64) (*LiveRoomInfoByMID, error) { 1082 | r, err := c.UserGetInfo(mid) 1083 | if err != nil { 1084 | return nil, err 1085 | } 1086 | return (*LiveRoomInfoByMID)(&r.LiveRoom), nil 1087 | } 1088 | 1089 | // LiveGetRoomInfoByID 从roomID获取直播间信息 1090 | // 1091 | // roomID 可为短号也可以是真实房号 1092 | func (c *CommClient) LiveGetRoomInfoByID(roomID int64) (*LiveRoomInfoByID, error) { 1093 | resp, err := c.RawParse( 1094 | BiliLiveURL, 1095 | "xlive/web-room/v1/index/getRoomPlayInfo", 1096 | "GET", 1097 | map[string]string{ 1098 | "room_id": strconv.FormatInt(roomID, 10), 1099 | }, 1100 | ) 1101 | if err != nil { 1102 | return nil, err 1103 | } 1104 | var r = &LiveRoomInfoByID{} 1105 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1106 | return nil, err 1107 | } 1108 | return r, nil 1109 | } 1110 | 1111 | // LiveGetWsConf 获取直播websocket服务器信息 1112 | // 1113 | // roomID: 真实直播间ID 1114 | func (c *CommClient) LiveGetWsConf(roomID int64) (*LiveWsConf, error) { 1115 | resp, err := c.RawParse( 1116 | BiliLiveURL, 1117 | "room/v1/Danmu/getConf", 1118 | "GET", 1119 | map[string]string{ 1120 | "room_id": strconv.FormatInt(roomID, 10), 1121 | }, 1122 | ) 1123 | if err != nil { 1124 | return nil, err 1125 | } 1126 | var r = &LiveWsConf{} 1127 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1128 | return nil, err 1129 | } 1130 | return r, nil 1131 | } 1132 | 1133 | // LiveGetAreaInfo 1134 | // 1135 | // 获取直播分区信息 1136 | func (c *CommClient) LiveGetAreaInfo() ([]*LiveAreaInfo, error) { 1137 | resp, err := c.RawParse( 1138 | BiliLiveURL, 1139 | "room/v1/Area/getList", 1140 | "GET", 1141 | map[string]string{}, 1142 | ) 1143 | if err != nil { 1144 | return nil, err 1145 | } 1146 | var r []*LiveAreaInfo 1147 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1148 | return nil, err 1149 | } 1150 | return r, nil 1151 | } 1152 | 1153 | // LiveGetGuardList 获取直播间大航海列表 1154 | // 1155 | // roomID: 真实直播间ID 1156 | // 1157 | // mid: 主播mid 1158 | // 1159 | // pn: 页码 1160 | // 1161 | // ps: 每页项数 1162 | func (c *CommClient) LiveGetGuardList(roomID int64, mid int64, pn int, ps int) (*LiveGuardList, error) { 1163 | resp, err := c.RawParse( 1164 | BiliLiveURL, 1165 | "xlive/app-room/v1/guardTab/topList", 1166 | "GET", 1167 | map[string]string{ 1168 | "roomid": strconv.FormatInt(roomID, 10), 1169 | "ruid": strconv.FormatInt(mid, 10), 1170 | "page": strconv.Itoa(pn), 1171 | "page_size": strconv.Itoa(ps), 1172 | }, 1173 | ) 1174 | if err != nil { 1175 | return nil, err 1176 | } 1177 | var r = &LiveGuardList{} 1178 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1179 | return nil, err 1180 | } 1181 | return r, nil 1182 | } 1183 | 1184 | // LiveGetMedalRank 获取直播间粉丝勋章榜 1185 | // 1186 | // roomID: 真实直播间ID 1187 | // 1188 | // mid: 主播mid 1189 | func (c *CommClient) LiveGetMedalRank(roomID int64, mid int64) (*LiveMedalRank, error) { 1190 | resp, err := c.RawParse( 1191 | BiliLiveURL, 1192 | "rankdb/v1/RoomRank/webMedalRank", 1193 | "GET", 1194 | map[string]string{ 1195 | "roomid": strconv.FormatInt(roomID, 10), 1196 | "ruid": strconv.FormatInt(mid, 10), 1197 | }, 1198 | ) 1199 | if err != nil { 1200 | return nil, err 1201 | } 1202 | var r = &LiveMedalRank{} 1203 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1204 | return nil, err 1205 | } 1206 | return r, nil 1207 | } 1208 | 1209 | // LiveGetPlayURL 获取直播流信息 1210 | // 1211 | // qn: 原画:10000 蓝光:400 超清:250 高清:150 流畅:80 1212 | func (c *CommClient) LiveGetPlayURL(roomID int64, qn int) (*LivePlayURL, error) { 1213 | resp, err := c.RawParse( 1214 | BiliLiveURL, 1215 | "xlive/web-room/v1/playUrl/playUrl", 1216 | "GET", 1217 | map[string]string{ 1218 | "cid": strconv.FormatInt(roomID, 10), 1219 | "qn": strconv.Itoa(qn), 1220 | "platform": "web", 1221 | "https_url_req": "1", 1222 | "ptype": "16", 1223 | }, 1224 | ) 1225 | if err != nil { 1226 | return nil, err 1227 | } 1228 | var r = &LivePlayURL{} 1229 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1230 | return nil, err 1231 | } 1232 | return r, nil 1233 | } 1234 | 1235 | // LiveGetAllGiftInfo 获取所有礼物信息 1236 | // 1237 | // areaID: 子分区ID 从 LiveGetAreaInfo 获取 1238 | // 1239 | // areaParentID: 父分区ID 从 LiveGetAreaInfo 获取 1240 | // 1241 | // 三个字段可以不用填,但填了有助于减小返回内容的大小,置空(传入0)返回约 2.7w 行,填了三个对应值返回约 1.4w 行 1242 | func (c *CommClient) LiveGetAllGiftInfo(roomID int64, areaID int, areaParentID int) (*LiveAllGiftInfo, error) { 1243 | resp, err := c.RawParse( 1244 | BiliLiveURL, 1245 | "xlive/web-room/v1/giftPanel/giftConfig", 1246 | "GET", 1247 | map[string]string{ 1248 | "room_id": strconv.FormatInt(roomID, 10), 1249 | "platform": "pc", 1250 | "source": "live", 1251 | "area_id": strconv.Itoa(areaID), 1252 | "area_parent_id": strconv.Itoa(areaParentID), 1253 | }, 1254 | ) 1255 | if err != nil { 1256 | return nil, err 1257 | } 1258 | var r = &LiveAllGiftInfo{} 1259 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1260 | return nil, err 1261 | } 1262 | return r, nil 1263 | } 1264 | 1265 | // CommentGetCount 获取评论总数 1266 | // 1267 | // oid: 对应类型的ID 1268 | // 1269 | // tp: 类型。https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/comment#%E8%AF%84%E8%AE%BA%E5%8C%BA%E7%B1%BB%E5%9E%8B%E4%BB%A3%E7%A0%81 1270 | func (c *CommClient) CommentGetCount(oid int64, tp int) (int, error) { 1271 | resp, err := c.RawParse( 1272 | BiliApiURL, 1273 | "x/v2/reply/count", 1274 | "GET", 1275 | map[string]string{ 1276 | "oid": strconv.FormatInt(oid, 10), 1277 | "type": strconv.Itoa(tp), 1278 | }, 1279 | ) 1280 | if err != nil { 1281 | return -1, err 1282 | } 1283 | var r struct { 1284 | Count int `json:"count"` 1285 | } 1286 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1287 | return -1, err 1288 | } 1289 | return r.Count, nil 1290 | } 1291 | 1292 | // CommentGetMain 获取评论区内容 1293 | // 1294 | // oid: 对应类型的ID 1295 | // 1296 | // tp: 类型。https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/comment#%E8%AF%84%E8%AE%BA%E5%8C%BA%E7%B1%BB%E5%9E%8B%E4%BB%A3%E7%A0%81 1297 | // 1298 | // mode: 排序方式 1299 | // 1300 | // 0 3:仅按热度 1301 | // 1302 | // 1:按热度+按时间 1303 | // 1304 | // 2:仅按时间 1305 | // 1306 | // next: 评论页选择 按热度时:热度顺序页码(0为第一页) 按时间时:时间倒序楼层号 1307 | // 1308 | // ps: 每页项数 1309 | // 1310 | // 具体用法请看测试样例 1311 | func (c *CommClient) CommentGetMain(oid int64, tp int, mode int, next int, ps int) (*CommentMain, error) { 1312 | resp, err := c.RawParse( 1313 | BiliApiURL, 1314 | "x/v2/reply/main", 1315 | "GET", 1316 | map[string]string{ 1317 | "oid": strconv.FormatInt(oid, 10), 1318 | "type": strconv.Itoa(tp), 1319 | "mode": strconv.Itoa(mode), 1320 | "next": strconv.Itoa(next), 1321 | "ps": strconv.Itoa(ps), 1322 | }, 1323 | ) 1324 | if err != nil { 1325 | return nil, err 1326 | } 1327 | var r = &CommentMain{} 1328 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1329 | return nil, err 1330 | } 1331 | return r, nil 1332 | } 1333 | 1334 | // CommentGetReply 获取指定评论和二级回复 1335 | // 1336 | // 1337 | // oid: 对应类型的ID 1338 | // 1339 | // tp: 类型。https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/comment#%E8%AF%84%E8%AE%BA%E5%8C%BA%E7%B1%BB%E5%9E%8B%E4%BB%A3%E7%A0%81 1340 | // 1341 | // root: 目标一级评论rpid 1342 | // 1343 | // pn: 二级评论页码 从1开始 1344 | // 1345 | // ps: 二级评论每页项数 定义域:1-49 1346 | func (c *CommClient) CommentGetReply(oid int64, tp int, root int64, pn int, ps int) (*CommentReply, error) { 1347 | resp, err := c.RawParse( 1348 | BiliApiURL, 1349 | "x/v2/reply/reply", 1350 | "GET", 1351 | map[string]string{ 1352 | "oid": strconv.FormatInt(oid, 10), 1353 | "type": strconv.Itoa(tp), 1354 | "root": strconv.FormatInt(root, 10), 1355 | "pn": strconv.Itoa(pn), 1356 | "ps": strconv.Itoa(ps), 1357 | }, 1358 | ) 1359 | if err != nil { 1360 | return nil, err 1361 | } 1362 | var r = &CommentReply{} 1363 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1364 | return nil, err 1365 | } 1366 | return r, nil 1367 | } 1368 | 1369 | func (c *CommClient) UserGetInfo(mid int64) (*UserInfo, error) { 1370 | resp, err := c.RawParse( 1371 | BiliApiURL, 1372 | "x/space/acc/info", 1373 | "GET", 1374 | map[string]string{ 1375 | "mid": strconv.FormatInt(mid, 10), 1376 | }, 1377 | ) 1378 | if err != nil { 1379 | return nil, err 1380 | } 1381 | var r = &UserInfo{} 1382 | if err = json.Unmarshal(resp.Data, &r); err != nil { 1383 | return nil, err 1384 | } 1385 | return r, nil 1386 | } 1387 | -------------------------------------------------------------------------------- /comm_client_test.go: -------------------------------------------------------------------------------- 1 | package biligo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var testCommClient = newTestCommClient() 8 | 9 | func newTestCommClient() *CommClient { 10 | c := NewCommClient(&CommSetting{ 11 | DebugMode: true, 12 | }) 13 | 14 | return c 15 | } 16 | func TestCommClient_GetGeoInfo(t *testing.T) { 17 | info, err := testCommClient.GetGeoInfo() 18 | if err != nil { 19 | t.Error(err) 20 | t.FailNow() 21 | } 22 | t.Logf("ip: %s,country: %s,province: %s,city: %s,isp: %s,latitude: %.2f,longitude: %.2f,zoneID: %d,countryCode: %d", 23 | info.Addr, info.Country, info.Province, info.City, info.Isp, info.Latitude, info.Longitude, info.ZoneID, info.CountryCode) 24 | } 25 | func TestCommClient_VideoStatus(t *testing.T) { 26 | stat, err := testCommClient.VideoGetStat(759949922) 27 | if err != nil { 28 | t.Error(err) 29 | t.FailNow() 30 | } 31 | t.Logf("aid: %d,bvid: %s,view: %d,danmaku: %d,reply: %d,favorite: %d,coin: %d,share: %d,now_rank: %d,his_rank: %d,like: %d,dislike: %d,no_reprint: %d,copyright: %d,argue_msg: %s,evaluation: %s", 32 | stat.AID, stat.BVID, stat.View, stat.Danmaku, stat.Reply, stat.Favorite, stat.Coin, stat.Share, stat.NowRank, stat.HisRank, stat.Like, stat.Dislike, stat.NoReprint, stat.Copyright, stat.ArgueMsg, stat.Evaluation) 33 | } 34 | func TestCommClient_VideoInformation(t *testing.T) { 35 | info, err := testCommClient.VideoGetInfo(207511956) 36 | if err != nil { 37 | t.Error(err) 38 | t.FailNow() 39 | } 40 | t.Logf( 41 | "bvid: %s,title: %s,duration: %d,ownerName: %s,staff: %v,pubdate: %d,noreprint: %d,view: %d,desc: %s,title: %s", 42 | info.BVID, 43 | info.Title, 44 | info.Duration, 45 | info.Owner.Name, 46 | info.Staff, 47 | info.Pubdate, 48 | info.Rights.NoReprint, 49 | info.Stat.View, 50 | info.DescV2[0].RawText, 51 | info.Pages[0].Part, 52 | ) 53 | } 54 | func TestCommClient_VideoDescription(t *testing.T) { 55 | desc, err := testCommClient.VideoGetDescription(759949922) 56 | if err != nil { 57 | t.Error(err) 58 | t.FailNow() 59 | } 60 | t.Logf("desc: %s", desc) 61 | } 62 | func TestCommClient_VideoPageList(t *testing.T) { 63 | list, err := testCommClient.VideoGetPageList(759949922) 64 | if err != nil { 65 | t.Error(err) 66 | t.FailNow() 67 | } 68 | for _, page := range list { 69 | t.Logf("pageID: %d,title: %s,duration: %d,cid: %d", page.Page, page.Part, page.Duration, page.CID) 70 | } 71 | } 72 | func TestCommClient_VideoOnlineNum(t *testing.T) { 73 | total, web, err := testCommClient.VideoGetOnlineNum(759949922, 392402545) 74 | if err != nil { 75 | t.Error(err) 76 | t.FailNow() 77 | } 78 | t.Logf("total: %s,web: %s", total, web) 79 | } 80 | func TestCommClient_VideoTags(t *testing.T) { 81 | tags, err := testCommClient.VideoTags(759949922) 82 | if err != nil { 83 | t.Error(err) 84 | t.FailNow() 85 | } 86 | for _, tag := range tags { 87 | t.Logf("name: %s,use: %d,short_content: %s,cover: %s,Isatten: %d", tag.TagName, tag.Count.Use, tag.ShortContent, tag.Cover, tag.IsAtten) 88 | } 89 | } 90 | func TestCommClient_VideoRecommend(t *testing.T) { 91 | videos, err := testCommClient.VideoGetRecommend(759949922) 92 | if err != nil { 93 | t.Error(err) 94 | t.FailNow() 95 | } 96 | for _, v := range videos { 97 | t.Logf("aid: %d,duration: %d,title: %s", v.AID, v.Duration, v.Title) 98 | } 99 | } 100 | func TestCommClient_VideoShot(t *testing.T) { 101 | shot, err := testCommClient.VideoShot(759949922, 0, true) 102 | if err != nil { 103 | t.Error(err) 104 | t.FailNow() 105 | } 106 | t.Logf("img_x_len: %d,img_y_len: %d,img_x_size: %d,img_y_size: %d", shot.ImgXLen, shot.ImgYLen, shot.ImgXSize, shot.ImgYSize) 107 | t.Logf("url: %s", shot.Pvdata) 108 | t.Logf("image: %v", shot.Image) 109 | t.Logf("index: %v", shot.Index) 110 | } 111 | func TestCommClient_VideoShot2(t *testing.T) { 112 | shot, err := testCommClient.VideoShot(759949922, 392402545, false) 113 | if err != nil { 114 | t.Error(err) 115 | t.FailNow() 116 | } 117 | t.Logf("img_x_len: %d,img_y_len: %d,img_x_size: %d,img_y_size: %d", shot.ImgXLen, shot.ImgYLen, shot.ImgXSize, shot.ImgYSize) 118 | t.Logf("url: %s", shot.Pvdata) 119 | t.Logf("image: %v", shot.Image) 120 | // index传入false 则Index属性为空 121 | t.Logf("index: %v", shot.Index) 122 | } 123 | func TestCommClient_GetUnixNow(t *testing.T) { 124 | unix, err := testCommClient.GetUnixNow() 125 | if err != nil { 126 | t.Error(err) 127 | t.FailNow() 128 | } 129 | t.Logf("time: %d", unix) 130 | } 131 | func TestCommClient_DanmakuGetLikes(t *testing.T) { 132 | result, err := testCommClient.DanmakuGetLikes(397011525, []uint64{54109805459813888, 54109892081901568}) 133 | if err != nil { 134 | t.Error(err) 135 | t.FailNow() 136 | } 137 | for _, r := range result { 138 | t.Logf("likes: %d,isLiked: %d,dmid: %s", r.Likes, r.UserLike, r.IDStr) 139 | } 140 | } 141 | func TestCommClient_GetDailyNum(t *testing.T) { 142 | result, err := testCommClient.GetDailyNum() 143 | if err != nil { 144 | t.Error(err) 145 | t.FailNow() 146 | } 147 | for k, v := range result { 148 | t.Logf("%d %d", k, v) 149 | } 150 | } 151 | func TestCommClient_SpaceTopArchiveGet(t *testing.T) { 152 | top, err := testCommClient.SpaceGetTopArchive(546195) 153 | if err != nil { 154 | t.Error(err) 155 | t.FailNow() 156 | } 157 | t.Logf("aid: %d,title: %s,reason: %s,inter_video: %t,owner: %s", top.AID, top.Title, top.Reason, top.InterVideo, top.Owner.Name) 158 | } 159 | func TestCommClient_SpaceMasterpiecesGet(t *testing.T) { 160 | mp, err := testCommClient.SpaceGetMasterpieces(546195) 161 | if err != nil { 162 | t.Error(err) 163 | t.FailNow() 164 | } 165 | for _, archive := range mp { 166 | t.Logf( 167 | "aid: %d,title: %s,reason: %s,inter_video: %t,owner: %s,view: %d,height: %d,noreprint: %d", 168 | archive.AID, 169 | archive.Title, 170 | archive.Reason, 171 | archive.InterVideo, 172 | archive.Owner.Name, 173 | archive.Stat.View, 174 | archive.Dimension.Height, 175 | archive.Rights.NoReprint, 176 | ) 177 | 178 | } 179 | 180 | } 181 | func TestCommClient_SpaceTagsGet(t *testing.T) { 182 | tags, err := testCommClient.SpaceGetTags(53456) 183 | if err != nil { 184 | t.Error(err) 185 | t.FailNow() 186 | } 187 | t.Log(tags) 188 | } 189 | func TestCommClient_SpaceNoticeGet(t *testing.T) { 190 | notice, err := testCommClient.SpaceGetNotice(53456) 191 | if err != nil { 192 | t.Error(err) 193 | t.FailNow() 194 | } 195 | t.Log(notice) 196 | } 197 | func TestCommClient_SpaceLastPlayGame(t *testing.T) { 198 | games, err := testCommClient.SpaceGetLastPlayGame(2) 199 | if err != nil { 200 | t.Error(err) 201 | t.FailNow() 202 | } 203 | for _, game := range games { 204 | t.Logf("name: %s,image: %s,website: %s", game.Name, game.Image, game.Website) 205 | } 206 | } 207 | func TestCommClient_DanmakuPBGet(t *testing.T) { 208 | r, err := testCommClient.DanmakuGetByPb(1, 1176840, 1) 209 | if err != nil { 210 | t.Error(err) 211 | t.FailNow() 212 | } 213 | t.Logf("num: %d", len(r.Danmaku)) 214 | for _, dm := range r.Danmaku { 215 | t.Logf("content: %s,midhash: %s,progress: %d,id: %s", dm.Content, dm.MidHash, dm.Progress, dm.IDStr) 216 | } 217 | } 218 | func TestCommClient_DanmakuShotGet(t *testing.T) { 219 | r, err := testCommClient.DanmakuGetShot(759949922) 220 | if err != nil { 221 | t.Error(err) 222 | t.FailNow() 223 | } 224 | t.Log(r) 225 | } 226 | func TestCommClient_SpaceLastVideoCoin(t *testing.T) { 227 | r, err := testCommClient.SpaceGetLastVideoCoin(7190459) 228 | if err != nil { 229 | t.Error(err) 230 | t.FailNow() 231 | } 232 | for _, i := range r { 233 | t.Logf("title: %s,duration: %d,owner: %s", i.Title, i.Duration, i.Owner.Name) 234 | } 235 | } 236 | func TestCommClient_SpaceVideoSearch(t *testing.T) { 237 | r, err := testCommClient.SpaceSearchVideo(546195, "click", 0, "", 1, 10) 238 | if err != nil { 239 | t.Error(err) 240 | t.FailNow() 241 | } 242 | for _, list := range r.List.Vlist { 243 | t.Logf("title: %s,view: %d,duration: %s", list.Title, list.Play, list.Length) 244 | } 245 | for k, v := range r.List.Tlist { 246 | t.Logf("tid: %s,name: %s,count: %d", k, v.Name, v.Count) 247 | } 248 | t.Logf("videos: %d,pn: %d,ps: %d", r.Page.Count, r.Page.PN, r.Page.PS) 249 | t.Logf("text: %s,url: %s", r.EpisodicButton.Text, r.EpisodicButton.Uri) 250 | } 251 | func TestCommClient_SpaceChannelList(t *testing.T) { 252 | list, err := testCommClient.ChanGet(546195) 253 | if err != nil { 254 | t.Error(err) 255 | t.FailNow() 256 | } 257 | for _, c := range list.List { 258 | t.Logf("chanID: %d,mid: %d,name: %s,intro: %s,cover: %s", c.CID, c.MID, c.Name, c.Intro, c.Cover) 259 | } 260 | } 261 | func TestCommClient_ChannelVideoGet(t *testing.T) { 262 | videos, err := testCommClient.ChanGetVideo(546195, 21837, 1, 8) 263 | if err != nil { 264 | t.Error(err) 265 | t.FailNow() 266 | } 267 | for _, v := range videos.List.Archives { 268 | t.Logf("view: %d,title: %s,ctime: %d", v.Stat.View, v.Title, v.Ctime) 269 | } 270 | t.Logf("total: %d,count:%d,name: %s,intro: %s", videos.Page.Count, videos.List.Count, videos.List.Name, videos.List.Intro) 271 | } 272 | func TestCommClient_FavoritesList(t *testing.T) { 273 | list, err := testCommClient.FavGet(451863725) 274 | if err != nil { 275 | t.Error(err) 276 | t.FailNow() 277 | } 278 | for _, info := range list.List { 279 | t.Logf("mlid: %d,fid: %d,title: %s,count: %d", info.ID, info.FID, info.Title, info.MediaCount) 280 | } 281 | } 282 | func TestCommClient_FavoritesDetailGet(t *testing.T) { 283 | d, err := testCommClient.FavGetDetail(25422594) 284 | if err != nil { 285 | t.Error(err) 286 | t.FailNow() 287 | } 288 | t.Logf( 289 | "mlid: %d,title: %s,intro: %s,count: %d,ctime: %d,play: %d,owner: %s", 290 | d.ID, 291 | d.Title, 292 | d.Intro, 293 | d.MediaCount, 294 | d.Ctime, 295 | d.CntInfo.Play, 296 | d.Upper.Name, 297 | ) 298 | } 299 | func TestCommClient_FavoritesResGet(t *testing.T) { 300 | results, err := testCommClient.FavGetRes(1223365625) 301 | if err != nil { 302 | t.Error(err) 303 | t.FailNow() 304 | } 305 | for _, r := range results { 306 | t.Logf("id: %d,type: %d", r.ID, r.Type) 307 | } 308 | } 309 | func TestCommClient_FavoritesResDetail(t *testing.T) { 310 | d, err := testCommClient.FavGetResDetail(1052622027, 0, "", "", 0, 2, 20) 311 | if err != nil { 312 | t.Error(err) 313 | t.FailNow() 314 | } 315 | for _, m := range d.Medias { 316 | t.Logf("title: %s,view: %d,favTime: %d", m.Title, m.CntInfo.Play, m.FavTime) 317 | } 318 | t.Logf("title: %s,count: %d,owner: %s", d.Info.Title, d.Info.MediaCount, d.Info.Upper.Name) 319 | } 320 | func TestCommClient_VideoPlayURLGet(t *testing.T) { 321 | // flv 322 | r, err := testCommClient.VideoGetPlayURL(99999999, 171776208, 112, 128) 323 | if err != nil { 324 | t.Error(err) 325 | t.FailNow() 326 | } 327 | t.Logf("qn: %d,duration: %d", r.Quality, r.TimeLength) 328 | t.Logf("acceptDesc: %v", r.AcceptDescription) 329 | for _, u := range r.DURL { 330 | t.Logf("order: %d,size: %d,url: %s", u.Order, u.Size, u.URL) 331 | } 332 | } 333 | func TestCommClient_VideoPlayURLGet2(t *testing.T) { 334 | // dash+HDR 335 | r, err := testCommClient.VideoGetPlayURL(969628065, 244954665, 112, 16|64) 336 | if err != nil { 337 | t.Error(err) 338 | t.FailNow() 339 | } 340 | t.Logf("qn: %d,duration: %d", r.Quality, r.TimeLength) 341 | t.Logf("acceptDesc: %v", r.AcceptDescription) 342 | for _, v := range r.Dash.Video { 343 | t.Logf("id: %d,codecs: %s,baseURL: %s", v.ID, v.Codecs, v.BaseURL) 344 | } 345 | for _, a := range r.Dash.Audio { 346 | t.Logf("id: %d,codecs: %s,baseURL: %s", a.ID, a.Codecs, a.BaseURL) 347 | } 348 | } 349 | func TestCommClient_EmoteFreePackageGet(t *testing.T) { 350 | packs, err := testCommClient.EmoteGetFreePack("reply") 351 | if err != nil { 352 | t.Error(err) 353 | t.FailNow() 354 | } 355 | for _, pack := range packs { 356 | t.Logf("pack id: %d,text: %s,url: %s", pack.ID, pack.Text, pack.URL) 357 | for i := 0; i < 5; i++ { 358 | t.Logf("\temote id: %d,text: %s", pack.Emote[i].ID, pack.Emote[i].Text) 359 | } 360 | } 361 | } 362 | func TestCommClient_EmotePackageDetail(t *testing.T) { 363 | packs, err := testCommClient.EmoteGetPackDetail("reply", []int64{1, 2, 93}) 364 | if err != nil { 365 | t.Error(err) 366 | t.FailNow() 367 | } 368 | for _, pack := range packs { 369 | t.Logf("pack id: %d,text: %s,url: %s", pack.ID, pack.Text, pack.URL) 370 | for i := 0; i < 5; i++ { 371 | t.Logf("\temote id: %d,text: %s", pack.Emote[i].ID, pack.Emote[i].Text) 372 | } 373 | } 374 | } 375 | func TestCommClient_AudioInformation(t *testing.T) { 376 | info, err := testCommClient.AudioGetInfo(2445151) 377 | if err != nil { 378 | t.Error(err) 379 | t.FailNow() 380 | } 381 | t.Logf("auid: %d,relatedAID: %d,duration: %d", info.ID, info.AID, info.Duration) 382 | t.Logf("author: %s,uname: %s,coin: %d,passTime: %d", info.Author, info.Uname, info.CoinNum, info.PassTime) 383 | t.Logf("play: %d,share: %d,collect: %d,comment: %d,coin: %d", info.Statistic.Play, info.Statistic.Share, info.Statistic.Collect, info.Statistic.Comment, info.CoinNum) 384 | } 385 | func TestCommClient_AudioTags(t *testing.T) { 386 | tags, err := testCommClient.AudioGetTags(2445151) 387 | if err != nil { 388 | t.Error(err) 389 | t.FailNow() 390 | } 391 | for _, tag := range tags { 392 | t.Logf("key: %d,info: %s,type: %s,subType: %d", tag.Key, tag.Info, tag.Type, tag.Subtype) 393 | } 394 | } 395 | func TestCommClient_AudioMembers(t *testing.T) { 396 | members, err := testCommClient.AudioGetMembers(815861) 397 | if err != nil { 398 | t.Error(err) 399 | t.FailNow() 400 | } 401 | for _, m := range members { 402 | t.Logf("type: %d", m.Type) 403 | for _, l := range m.List { 404 | t.Logf("\tname: %s,memberID: %d", l.Name, l.MemberID) 405 | } 406 | } 407 | } 408 | func TestCommClient_AudioLyric(t *testing.T) { 409 | lrc, err := testCommClient.AudioGetLyric(15664) 410 | if err != nil { 411 | t.Error(err) 412 | t.FailNow() 413 | } 414 | t.Log(lrc) 415 | } 416 | func TestCommClient_AudioStat(t *testing.T) { 417 | stat, err := testCommClient.AudioGetStat(15664) 418 | if err != nil { 419 | t.Error(err) 420 | t.FailNow() 421 | } 422 | t.Logf("sid: %d,play: %d,comment: %d,collect: %d,share: %d", stat.SID, stat.Play, stat.Comment, stat.Collect, stat.Share) 423 | } 424 | func TestCommClient_AudioPlayURLGet(t *testing.T) { 425 | data, err := testCommClient.AudioGetPlayURL(2478206, 3) 426 | if err != nil { 427 | t.Error(err) 428 | t.FailNow() 429 | } 430 | t.Logf("auid: %d,type: %d,size: %d", data.SID, data.Type, data.Size) 431 | t.Logf("timeout: %d,title: %s,cover: %s", data.Timeout, data.Title, data.Cover) 432 | for _, u := range data.CDNs { 433 | t.Logf("\turl: %s", u) 434 | } 435 | for _, q := range data.Qualities { 436 | t.Logf("type: %d,size: %d,bps: %s,desc: %s,tag: %s,require: %d,requireDesc: %s", q.Type, q.Size, q.Bps, q.Desc, q.Tag, q.Require, q.RequireDesc) 437 | } 438 | } 439 | func TestCommClient_ChargeSpaceList(t *testing.T) { 440 | list, err := testCommClient.ChargeSpaceGetList(546195) 441 | if err != nil { 442 | t.Error(err) 443 | t.FailNow() 444 | } 445 | t.Logf("count: %d, total: %d,display: %d", list.Count, list.TotalCount, list.DisplayNum) 446 | for _, i := range list.List { 447 | t.Logf("mid: %d,rank: %d,uname: %s,msg: %s", i.PayMID, i.Rank, i.Uname, i.Message) 448 | } 449 | } 450 | func TestCommClient_ChargeVideoList(t *testing.T) { 451 | list, err := testCommClient.ChargeVideoGetList(546195, 250531882) 452 | if err != nil { 453 | t.Error(err) 454 | t.FailNow() 455 | } 456 | t.Logf("count: %d,total: %d,diplay: %d,avCount: %d", list.Count, list.TotalCount, list.DisplayNum, list.AvCount) 457 | t.Logf("list:") 458 | for _, i := range list.List { 459 | t.Logf("\tmid: %d,rank: %d,uname: %s,msg: %s", i.PayMID, i.Rank, i.Uname, i.Message) 460 | } 461 | } 462 | func TestCommClient_LiveGetRoomInfoByMID(t *testing.T) { 463 | info, err := testCommClient.LiveGetRoomInfoByMID(436473455) 464 | if err != nil { 465 | t.Error(err) 466 | t.FailNow() 467 | } 468 | t.Logf("roomID: %d,roomStatus: %d,title: %s", info.RoomID, info.RoomStatus, info.Title) 469 | t.Logf("liveStatus: %d,online: %d,roundStatus: %d", info.LiveStatus, info.Online, info.RoundStatus) 470 | t.Logf("cover: %s", info.Cover) 471 | t.Logf("url: %s", info.URL) 472 | } 473 | func TestCommClient_FollowingsGetDetail(t *testing.T) { 474 | detail, err := testCommClient.FollowingsGetDetail(95706136, 1, 5) 475 | if err != nil { 476 | t.Error(err) 477 | t.FailNow() 478 | } 479 | t.Logf("count: %d", detail.Total) 480 | for _, l := range detail.List { 481 | t.Logf("mid: %d,uname: %s", l.MID, l.Uname) 482 | } 483 | } 484 | func TestCommClient_LiveGetRoomInfoByID(t *testing.T) { 485 | r, err := testCommClient.LiveGetRoomInfoByID(1) 486 | if err != nil { 487 | t.Error(err) 488 | t.FailNow() 489 | } 490 | t.Logf("id: %d,short: %d,uid: %d,status: %d,time: %d", r.RoomID, r.ShortID, r.UID, r.LiveStatus, r.LiveTime) 491 | } 492 | func TestCommClient_LiveGetRoomInfoByID2(t *testing.T) { 493 | r, err := testCommClient.LiveGetRoomInfoByID(287083) 494 | if err != nil { 495 | t.Error(err) 496 | t.FailNow() 497 | } 498 | t.Logf("id: %d,short: %d,uid: %d,status: %d,time: %d", r.RoomID, r.ShortID, r.UID, r.LiveStatus, r.LiveTime) 499 | } 500 | func TestCommClient_LiveGetWsConf(t *testing.T) { 501 | r, err := testCommClient.LiveGetWsConf(287083) 502 | if err != nil { 503 | t.Error(err) 504 | t.FailNow() 505 | } 506 | t.Logf("host: %s,port: %d,delay: %d,rate: %d", r.Host, r.Port, r.MaxDelay, r.RefreshRate) 507 | t.Logf("token: %s", r.Token) 508 | for _, s := range r.ServerList { 509 | t.Logf("\thost: %s,port: %d", s.Host, s.Port) 510 | } 511 | } 512 | func TestCommClient_LiveGetAreaInfo(t *testing.T) { 513 | r, err := testCommClient.LiveGetAreaInfo() 514 | if err != nil { 515 | t.Error(err) 516 | t.FailNow() 517 | } 518 | for _, info := range r { 519 | t.Logf("id: %d,name: %s", info.ID, info.Name) 520 | for _, l := range info.List { 521 | t.Logf("\tid: %s,name: %s,hot: %d,comlex: %s", l.ID, l.Name, l.HotStatus, l.ComplexAreaName) 522 | } 523 | } 524 | } 525 | func TestCommClient_LiveGetGuardList(t *testing.T) { 526 | r, err := testCommClient.LiveGetGuardList(545068, 8739477, 3, 10) 527 | if err != nil { 528 | t.Error(err) 529 | t.FailNow() 530 | } 531 | t.Logf("total: %d,nowPage: %d,totalPage: %d", r.Info.Num, r.Info.Now, r.Info.Page) 532 | for _, l := range r.Top3 { 533 | t.Logf("uid: %d,uname: %s,rank: %d,level: %d", l.UID, l.Username, l.Rank, l.GuardLevel) 534 | } 535 | for _, l := range r.List { 536 | t.Logf("\tuid: %d,uname: %s,rank: %d,level: %d", l.UID, l.Username, l.Rank, l.GuardLevel) 537 | } 538 | } 539 | func TestCommClient_LiveGetMedalRank(t *testing.T) { 540 | r, err := testCommClient.LiveGetMedalRank(545068, 8739477) 541 | if err != nil { 542 | t.Error(err) 543 | t.FailNow() 544 | } 545 | for _, l := range r.List { 546 | t.Logf("rank: %d,uname: %s,medal: %s,guard: %d,level: %d", l.Rank, l.Uname, l.MedalName, l.GuardLevel, l.Level) 547 | } 548 | } 549 | func TestCommClient_LiveGetPlayURL(t *testing.T) { 550 | r, err := testCommClient.LiveGetPlayURL(923833, 10000) 551 | if err != nil { 552 | t.Error(err) 553 | t.FailNow() 554 | } 555 | t.Logf("cur: %d", r.CurrentQn) 556 | for _, q := range r.QualityDescription { 557 | t.Logf("qn: %d,desc: %s", q.Qn, q.Desc) 558 | } 559 | for _, u := range r.DURL { 560 | t.Logf("order: %d,url: %s", u.Order, u.URL) 561 | } 562 | } 563 | func TestCommClient_LiveGetAllGiftInfo(t *testing.T) { 564 | r, err := testCommClient.LiveGetAllGiftInfo(545068, 86, 2) 565 | if err != nil { 566 | t.Error(err) 567 | t.FailNow() 568 | } 569 | for _, l := range r.List { 570 | t.Logf("id: %d,name: %s,price: %d", l.ID, l.Name, l.Price) 571 | } 572 | for _, l := range r.GuardResources { 573 | t.Logf("name: %s,level: %d", l.Name, l.Level) 574 | } 575 | } 576 | func TestCommClient_LiveGetAllGiftInfo2(t *testing.T) { 577 | r, err := testCommClient.LiveGetAllGiftInfo(0, 0, 0) 578 | if err != nil { 579 | t.Error(err) 580 | t.FailNow() 581 | } 582 | for _, l := range r.List { 583 | t.Logf("id: %d,name: %s,price: %d", l.ID, l.Name, l.Price) 584 | } 585 | for _, l := range r.GuardResources { 586 | t.Logf("name: %s,level: %d", l.Name, l.Level) 587 | } 588 | } 589 | func TestCommClient_CommentGetCount(t *testing.T) { 590 | count, err := testCommClient.CommentGetCount(250531882, 1) 591 | if err != nil { 592 | t.Error(err) 593 | t.FailNow() 594 | } 595 | t.Logf("count: %d", count) 596 | } 597 | func TestCommClient_CommentGetMain(t *testing.T) { 598 | r, err := testCommClient.CommentGetMain(806545681, 1, 0, 2, 10) 599 | if err != nil { 600 | t.Error(err) 601 | t.FailNow() 602 | } 603 | t.Logf("mid: %d,total: %d", r.Upper.MID, r.Cursor.AllCount) 604 | t.Log("replies:") 605 | for _, h := range r.Replies { 606 | t.Logf("\tuname: %s,likes: %d,content: %s", h.Member.Uname, h.Like, h.Content.Message) 607 | for _, c := range h.Replies { 608 | t.Logf("\t\tuname: %s,likes: %d,content: %s", c.Member.Uname, c.Like, c.Content.Message) 609 | } 610 | } 611 | } 612 | func TestCommClient_CommentGetMain2(t *testing.T) { 613 | // 按楼层号获取时间排序评论 先传入next=0获取next与prev,再用楼层号来取 614 | // r,err:=testCommClient.CommentGetMain(806545681,1,2,0,10) 615 | r, err := testCommClient.CommentGetMain(806545681, 1, 2, 14238, 20) 616 | if err != nil { 617 | t.Error(err) 618 | t.FailNow() 619 | } 620 | t.Logf("mid: %d,total: %d,prev: %d,next: %d", r.Upper.MID, r.Cursor.AllCount, r.Cursor.Prev, r.Cursor.Next) 621 | t.Log("replies:") 622 | for _, h := range r.Replies { 623 | t.Logf("\tuname: %s,likes: %d,content: %s", h.Member.Uname, h.Like, h.Content.Message) 624 | for _, c := range h.Replies { 625 | t.Logf("\t\tuname: %s,likes: %d,content: %s", c.Member.Uname, c.Like, c.Content.Message) 626 | } 627 | } 628 | } 629 | func TestCommClient_CommentGetReply(t *testing.T) { 630 | r, err := testCommClient.CommentGetReply(806545681, 1, 5740696166, 4, 10) 631 | if err != nil { 632 | t.Error(err) 633 | t.FailNow() 634 | } 635 | t.Logf("count: %d,cur: %d", r.Page.Count, r.Page.Num) 636 | for _, h := range r.Replies { 637 | t.Logf("\tid: %d,uname: %s,likes: %d,content: %s", h.RPID, h.Member.Uname, h.Like, h.Content.Message) 638 | } 639 | } 640 | func TestCommClient_UserGetInfo(t *testing.T) { 641 | r, err := testCommClient.UserGetInfo(2206456) 642 | if err != nil { 643 | t.Error(err) 644 | t.FailNow() 645 | } 646 | t.Logf("mid: %d,name: %s,sex: %s,level: %d,sign: %s", r.MID, r.Name, r.Sex, r.Level, r.Sign) 647 | t.Logf("live: %d,officialDesc: %s,nameplateName: %s,pendantName: %s,vip: %s", r.LiveRoom.LiveStatus, r.Official.Title, r.Nameplate.Name, r.Pendant.Name, r.Vip.Label.Text) 648 | } 649 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package biligo 2 | 3 | const ( 4 | BiliApiURL = "https://api.bilibili.com/" 5 | BiliMainURL = "https://www.bilibili.com/" 6 | BiliPassportURL = "https://passport.bilibili.com/" 7 | BiliElecURL = "https://elec.bilibili.com/" 8 | BiliLiveURL = "https://api.live.bilibili.com/" 9 | BiliVcURL = "https://api.vc.bilibili.com/" 10 | ) 11 | 12 | var userAgent = []string{ 13 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", 14 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36", 15 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", 16 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36", 17 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36", 18 | "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.20) Gecko/20110804 Red Hat/3.6-2.el5 Firefox/3.6.20", 19 | } 20 | 21 | var videoZones = map[int]VideoZone{ 22 | 1: {Name: "动画", Code: "douga", Desc: ""}, 23 | 24: {Name: "MAD·AMV", Code: "mad", Desc: "具有一定制作程度的动画或静画的二次创作视频"}, 24 | 25: {Name: "MMD·3D", Code: "mmd", Desc: "使用MMD(MikuMikuDance)和其他3D建模类软件制作的视频"}, 25 | 47: {Name: "短片·手书·配音", Code: "voice", Desc: "追求创新并具有强烈特色的短片、手书(绘)及ACG相关配音"}, 26 | 210: {Name: "手办·模玩", Code: "garage_kit", Desc: "手办模玩的测评、改造或其他衍生内容"}, 27 | 86: {Name: "特摄", Code: "tokusatsu", Desc: "特摄相关衍生视频"}, 28 | 27: {Name: "综合", Code: "other", Desc: "以动画及动画相关内容为素材,包括但不仅限于音频替换、杂谈、排行榜等内容"}, 29 | 13: {Name: "番剧", Code: "anime", Desc: ""}, 30 | 33: {Name: "连载动画", Code: "serial", Desc: "当季连载的动画番剧"}, 31 | 32: {Name: "完结动画", Code: "finish", Desc: "已完结的动画番剧合集"}, 32 | 51: {Name: "资讯", Code: "information", Desc: "动画番剧相关资讯视频"}, 33 | 152: {Name: "官方延伸", Code: "offical", Desc: "动画番剧为主题的宣传节目、采访视频,及声优相关视频"}, 34 | 167: {Name: "国创", Code: "guochuang", Desc: ""}, 35 | 153: {Name: "国产动画", Code: "chinese", Desc: "我国出品的PGC动画"}, 36 | 168: {Name: "国产原创相关", Code: "original", Desc: ""}, 37 | 169: {Name: "布袋戏", Code: "puppetry", Desc: ""}, 38 | 195: {Name: "动态漫·广播剧", Code: "motioncomic", Desc: ""}, 39 | 170: {Name: "资讯", Code: "information", Desc: ""}, 40 | 3: {Name: "音乐", Code: "music", Desc: ""}, 41 | 28: {Name: "原创音乐", Code: "original", Desc: "个人或团队制作以音乐为主要原创因素的歌曲或纯音乐"}, 42 | 31: {Name: "翻唱", Code: "cover", Desc: "一切非官方的人声再演绎歌曲作品"}, 43 | 30: {Name: "VOCALOID·UTAU", Code: "vocaloid", Desc: "以雅马哈Vocaloid和UTAU引擎为基础,包含其他调教引擎,运用各类音源进行的歌曲创作内容"}, 44 | 194: {Name: "电音", Code: "electronic", Desc: "以电子合成器、音乐软体等产生的电子声响制作的音乐"}, 45 | 59: {Name: "演奏", Code: "perform", Desc: "传统或非传统乐器及器材的演奏作品"}, 46 | 193: {Name: "MV", Code: "mv", Desc: "音乐录影带,为搭配音乐而拍摄的短片"}, 47 | 29: {Name: "音乐现场", Code: "live", Desc: "音乐实况表演视频"}, 48 | 130: {Name: "音乐综合", Code: "other", Desc: "收录无法定义到其他音乐子分区的音乐视频"}, 49 | 129: {Name: "舞蹈", Code: "dance", Desc: ""}, 50 | 20: {Name: "宅舞", Code: "otaku", Desc: "与ACG相关的翻跳、原创舞蹈"}, 51 | 198: {Name: "街舞", Code: "hiphop", Desc: "收录街舞相关内容,包括赛事现场、舞室作品、个人翻跳、FREESTYLE等"}, 52 | 199: {Name: "明星舞蹈", Code: "star", Desc: "国内外明星发布的官方舞蹈及其翻跳内容"}, 53 | 200: {Name: "中国舞", Code: "china", Desc: "传承中国艺术文化的舞蹈内容,包括古典舞、民族民间舞、汉唐舞、古风舞等"}, 54 | 154: {Name: "舞蹈综合", Code: "three_d", Desc: "收录无法定义到其他舞蹈子分区的舞蹈视频"}, 55 | 156: {Name: "舞蹈教程", Code: "demo", Desc: "镜面慢速,动作分解,基础教程等具有教学意义的舞蹈视频"}, 56 | 4: {Name: "游戏", Code: "game", Desc: ""}, 57 | 17: {Name: "单机游戏", Code: "stand_alone", Desc: "以所有平台(PC、主机、移动端)的单机或联机游戏为主的视频内容,包括游戏预告、CG、实况解说及相关的评测、杂谈与视频剪辑等"}, 58 | 171: {Name: "电子竞技", Code: "esports", Desc: "具有高对抗性的电子竞技游戏项目,其相关的赛事、实况、攻略、解说、短剧等视频"}, 59 | 172: {Name: "手机游戏", Code: "mobile", Desc: "以手机及平板设备为主要平台的游戏,其相关的实况、攻略、解说、短剧、演示等视频"}, 60 | 65: {Name: "网络游戏", Code: "online", Desc: "由网络运营商运营的多人在线游戏,以及电子竞技的相关游戏内容。包括赛事、攻略、实况、解说等相关视频"}, 61 | 173: {Name: "桌游棋牌", Code: "board", Desc: "桌游、棋牌、卡牌对战等及其相关电子版游戏的实况、攻略、解说、演示等视频"}, 62 | 121: {Name: "GMV", Code: "gmv", Desc: "由游戏素材制作的MV视频。以游戏内容或CG为主制作的,具有一定创作程度的MV类型的视频"}, 63 | 136: {Name: "音游", Code: "music", Desc: "各个平台上,通过配合音乐与节奏而进行的音乐类游戏视频"}, 64 | 19: {Name: "Mugen", Code: "mugen", Desc: "以Mugen引擎为平台制作、或与Mugen相关的游戏视频"}, 65 | 36: {Name: "知识", Code: "knowledge", Desc: ""}, 66 | 201: {Name: "科学科普", Code: "science", Desc: "回答你的十万个为什么"}, 67 | 124: {Name: "社科·法律·心理", Code: "social_science", Desc: "基于社会科学、法学、心理学展开或个人观点输出的知识视频"}, 68 | 228: {Name: "人文历史", Code: "humanity_history", Desc: "看看古今人物,聊聊历史过往,品品文学典籍"}, 69 | 207: {Name: "财经商业", Code: "business", Desc: "说金融市场,谈宏观经济,一起畅聊商业故事"}, 70 | 208: {Name: "校园学习", Code: "campus", Desc: "老师很有趣,同学多人才,我们都爱搞学习"}, 71 | 209: {Name: "职业职场", Code: "career", Desc: "职场加油站,成为最有料的职场人"}, 72 | 229: {Name: "设计·创意", Code: "design", Desc: "天马行空,创意设计,都在这里"}, 73 | 122: {Name: "野生技术协会", Code: "skill", Desc: "炫酷技能大集合,是时候展现真正的技术了"}, 74 | 188: {Name: "科技", Code: "tech", Desc: "科技"}, 75 | 95: {Name: "数码", Code: "digital", Desc: "科技数码产品大全,一起来做发烧友"}, 76 | 230: {Name: "软件应用", Code: "application", Desc: "超全软件应用指南"}, 77 | 231: {Name: "计算机技术", Code: "computer_tech", Desc: "研究分析、教学演示、经验分享......有关计算机技术的都在这里"}, 78 | 232: {Name: "工业·工程·机械", Code: "industry", Desc: "前方高能,机甲重工即将出没"}, 79 | 233: {Name: "极客DIY", Code: "diy", Desc: "炫酷技能,极客文化,硬核技巧,准备好你的惊讶"}, 80 | 234: {Name: "运动", Code: "sports", Desc: ""}, 81 | 235: {Name: "篮球·足球", Code: "basketballfootball", Desc: "与篮球、足球相关的视频,包括但不限于篮足球赛事、教学、评述、剪辑、剧情等相关内容"}, 82 | 164: {Name: "健身", Code: "aerobics", Desc: "与健身相关的视频,包括但不限于瑜伽、CrossFit、健美、力量举、普拉提、街健等相关内容"}, 83 | 236: {Name: "竞技体育", Code: "athletic", Desc: "与竞技体育相关的视频,包括但不限于乒乓、羽毛球、排球、赛车等竞技项目的赛事、评述、剪辑、剧情等相关内容"}, 84 | 237: {Name: "运动文化", Code: "culture", Desc: "与运动文化相关的视频,包络但不限于球鞋、球衣、球星卡等运动衍生品的分享、解读,体育产业的分析、科普等相关内容"}, 85 | 238: {Name: "运动综合", Code: "comprehensive", Desc: "与运动综合相关的视频,包括但不限于钓鱼、骑行、滑板等日常运动分享、教学、Vlog等相关内容"}, 86 | 223: {Name: "汽车", Code: "car", Desc: ""}, 87 | 176: {Name: "汽车生活", Code: "life", Desc: "分享汽车及出行相关的生活体验类视频"}, 88 | 224: {Name: "汽车文化", Code: "culture", Desc: "车迷的精神圣地,包括汽车赛事、品牌历史、汽车改装、经典车型和汽车模型等"}, 89 | 225: {Name: "汽车极客", Code: "geek", Desc: "汽车硬核达人聚集地,包括DIY造车、专业评测和技术知识分享"}, 90 | 226: {Name: "智能出行", Code: "smart", Desc: "探索新能源汽车和未来智能出行的前沿阵地"}, 91 | 227: {Name: "购车攻略", Code: "strategy", Desc: "丰富详实的购车建议和新车体验"}, 92 | 160: {Name: "生活", Code: "life", Desc: ""}, 93 | 138: {Name: "搞笑", Code: "funny", Desc: "各种沙雕有趣的搞笑剪辑,挑战,表演,配音等视频"}, 94 | 239: {Name: "家居房产", Code: "home", Desc: "与买房、装修、居家生活相关的分享"}, 95 | 161: {Name: "手工", Code: "handmake", Desc: "手工制品的制作过程或成品展示、教程、测评类视频"}, 96 | 162: {Name: "绘画", Code: "painting", Desc: "绘画过程或绘画教程,以及绘画相关的所有视频"}, 97 | 21: {Name: "日常", Code: "daily", Desc: "记录日常生活,分享生活故事"}, 98 | 211: {Name: "美食", Code: "food", Desc: ""}, 99 | 76: {Name: "美食制作", Code: "make", Desc: "学做人间美味,展示精湛厨艺"}, 100 | 212: {Name: "美食侦探", Code: "detective", Desc: "寻找美味餐厅,发现街头美食"}, 101 | 213: {Name: "美食测评", Code: "measurement", Desc: "吃货世界,品尝世间美味"}, 102 | 214: {Name: "田园美食", Code: "rural", Desc: "品味乡野美食,寻找山与海的味道"}, 103 | 215: {Name: "美食记录", Code: "record", Desc: "记录一日三餐,给生活添一点幸福感"}, 104 | 217: {Name: "美食", Code: "food", Desc: ""}, 105 | 218: {Name: "喵星人", Code: "cat", Desc: "喵喵喵喵喵"}, 106 | 219: {Name: "汪星人", Code: "dog", Desc: "汪汪汪汪汪"}, 107 | 220: {Name: "大熊猫", Code: "panda", Desc: "芝麻汤圆营业中"}, 108 | 221: {Name: "野生动物", Code: "wild_animal", Desc: "内有“猛兽”出没"}, 109 | 222: {Name: "爬宠", Code: "reptiles", Desc: "鳞甲有灵"}, 110 | 75: {Name: "动物综合", Code: "animal_composite", Desc: "收录除上述子分区外,其余动物相关视频以及非动物主体或多个动物主体的动物相关延伸内容"}, 111 | 119: {Name: "鬼畜", Code: "kichiku", Desc: ""}, 112 | 22: {Name: "鬼畜调教", Code: "guide", Desc: "使用素材在音频、画面上做一定处理,达到与BGM一定的同步感"}, 113 | 26: {Name: "音MAD", Code: "mad", Desc: "使用素材音频进行一定的二次创作来达到还原原曲的非商业性质稿件"}, 114 | 126: {Name: "人力VOCALOID", Code: "manual_vocaloid", Desc: "将人物或者角色的无伴奏素材进行人工调音,使其就像VOCALOID一样歌唱的技术"}, 115 | 216: {Name: "鬼畜剧场", Code: "theatre", Desc: "使用素材进行人工剪辑编排的有剧情的作品"}, 116 | 127: {Name: "教程演示", Code: "course", Desc: "鬼畜相关的教程演示"}, 117 | 155: {Name: "时尚", Code: "fashion", Desc: ""}, 118 | 157: {Name: "美妆", Code: "makeup", Desc: "涵盖妆容、发型、美甲等教程,彩妆、护肤相关产品测评、分享等"}, 119 | 158: {Name: "服饰", Code: "clothing", Desc: "服饰风格、搭配技巧相关的展示和教程视频"}, 120 | 159: {Name: "T台", Code: "catwalk", Desc: "发布会走秀现场及模特相关时尚片、采访、后台花絮"}, 121 | 192: {Name: "风尚标", Code: "trends", Desc: "时尚明星专访、街拍、时尚购物相关知识科普"}, 122 | 202: {Name: "资讯", Code: "information", Desc: ""}, 123 | 203: {Name: "热点", Code: "hotspot", Desc: "全民关注的时政热门资讯"}, 124 | 204: {Name: "环球", Code: "global", Desc: "全球范围内发生的具有重大影响力的事件动态"}, 125 | 205: {Name: "社会", Code: "social", Desc: "日常生活的社会事件、社会问题、社会风貌的报道"}, 126 | 206: {Name: "综合", Code: "multiple", Desc: "其它垂直领域的综合资讯"}, 127 | 5: {Name: "娱乐", Code: "ent", Desc: ""}, 128 | 71: {Name: "综艺", Code: "variety", Desc: "国内外有趣的综艺和综艺相关精彩剪辑"}, 129 | 137: {Name: "明星", Code: "star", Desc: "娱乐圈动态、明星资讯相关"}, 130 | 181: {Name: "影视", Code: "cinephile", Desc: ""}, 131 | 182: {Name: "影视杂谈", Code: "cinecism", Desc: "影视评论、解说、吐槽、科普等\t"}, 132 | 183: {Name: "影视剪辑", Code: "montage", Desc: "对影视素材进行剪辑再创作的视频"}, 133 | 85: {Name: "短片", Code: "shortfilm", Desc: "追求自我表达且具有特色的短片"}, 134 | 184: {Name: "预告·资讯", Code: "trailer_info", Desc: "影视类相关资讯,预告,花絮等视频"}, 135 | 177: {Name: "纪录片", Code: "documentary", Desc: ""}, 136 | 37: {Name: "人文·历史", Code: "history", Desc: ""}, 137 | 178: {Name: "科学·探索·自然", Code: "science", Desc: ""}, 138 | 179: {Name: "军事", Code: "military", Desc: ""}, 139 | 180: {Name: "社会·美食·旅行", Code: "travel", Desc: ""}, 140 | 23: {Name: "电影", Code: "movie", Desc: ""}, 141 | 147: {Name: "华语电影", Code: "chinese", Desc: ""}, 142 | 145: {Name: "欧美电影", Code: "west", Desc: ""}, 143 | 146: {Name: "日本电影", Code: "japan", Desc: ""}, 144 | 83: {Name: "其他国家", Code: "movie", Desc: ""}, 145 | 11: {Name: "电视剧", Code: "tv", Desc: ""}, 146 | 185: {Name: "国产剧", Code: "mainland", Desc: ""}, 147 | 187: {Name: "海外剧", Code: "overseas", Desc: ""}, 148 | } 149 | 150 | func GetVideoZone(tid int) VideoZone { 151 | return videoZones[tid] 152 | } 153 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## Example 2 | 3 | - [heartbeat](./heartbeat) - 心跳包发送 -------------------------------------------------------------------------------- /example/heartbeat/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | bg "github.com/iyear/biligo" 5 | "log" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | b, err := bg.NewBiliClient(&bg.BiliSetting{ 11 | Auth: &bg.CookieAuth{ 12 | DedeUserID: "", 13 | DedeUserIDCkMd5: "", 14 | SESSDATA: "", 15 | BiliJCT: "", 16 | }, 17 | DebugMode: true, 18 | }) 19 | if err != nil { 20 | log.Fatal("failed to make new bili client; error: ", err) 21 | return 22 | } 23 | 24 | sig := make(chan struct{}) 25 | 26 | go func(client *bg.BiliClient, sig chan struct{}) { 27 | start := time.Now() 28 | // 每十秒上报一次 29 | ticker := time.NewTicker(time.Second * 10) 30 | for { 31 | select { 32 | case <-ticker.C: 33 | err := client.VideoHeartBeat(242531611, 173439442, int64(time.Since(start).Seconds())) 34 | if err != nil { 35 | log.Println("failed to send heartbeat; error:", err) 36 | continue 37 | } 38 | log.Println("heartbeat...") 39 | case <-sig: 40 | ticker.Stop() 41 | log.Println("stop...") 42 | return 43 | } 44 | } 45 | }(b, sig) 46 | 47 | // 模拟一分钟 48 | m := time.NewTimer(time.Minute) 49 | <-m.C 50 | m.Stop() 51 | // 发出信号终止心跳包 52 | close(sig) 53 | } 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/iyear/biligo 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 7 | github.com/pkg/errors v0.9.1 8 | github.com/tidwall/gjson v1.8.1 9 | github.com/tidwall/pretty v1.2.0 // indirect 10 | google.golang.org/protobuf v1.27.1 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 3 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 4 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 5 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 6 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 7 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 8 | github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU= 9 | github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= 10 | github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= 11 | github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 12 | github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 13 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 14 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 15 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 16 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 17 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 18 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 19 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 20 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 21 | -------------------------------------------------------------------------------- /internal/util/base.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func IF(f bool, a interface{}, b interface{}) interface{} { 4 | if f { 5 | return a 6 | } 7 | return b 8 | } 9 | -------------------------------------------------------------------------------- /internal/util/string.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | ) 7 | 8 | func Int64SliceToString(nums []int64, sep string) string { 9 | if nums == nil || len(nums) == 0 { 10 | return "" 11 | } 12 | var buf bytes.Buffer 13 | buf.WriteString(strconv.FormatInt(nums[0], 10)) 14 | for i := 1; i < len(nums); i++ { 15 | buf.WriteString(sep) 16 | buf.WriteString(strconv.FormatInt(nums[i], 10)) 17 | } 18 | return buf.String() 19 | } 20 | func Uint64SliceToString(nums []uint64, sep string) string { 21 | if nums == nil || len(nums) == 0 { 22 | return "" 23 | } 24 | var buf bytes.Buffer 25 | buf.WriteString(strconv.FormatUint(nums[0], 10)) 26 | for i := 1; i < len(nums); i++ { 27 | buf.WriteString(sep) 28 | buf.WriteString(strconv.FormatUint(nums[i], 10)) 29 | } 30 | return buf.String() 31 | } 32 | func StringSliceToString(strings []string, sep string) string { 33 | if strings == nil || len(strings) == 0 { 34 | return "" 35 | } 36 | var buf bytes.Buffer 37 | buf.WriteString(strings[0]) 38 | for i := 1; i < len(strings); i++ { 39 | buf.WriteString(sep) 40 | buf.WriteString(strings[i]) 41 | } 42 | return buf.String() 43 | } 44 | -------------------------------------------------------------------------------- /internal/util/string_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestInt64SliceToString(t *testing.T) { 6 | if r := Int64SliceToString([]int64{1, 2, 3, 4, 5}, ","); r != "1,2,3,4,5" { 7 | t.Error(r) 8 | t.FailNow() 9 | } 10 | } 11 | func TestInt64SliceToString2(t *testing.T) { 12 | if r := Int64SliceToString([]int64{}, ","); r != "" { 13 | t.Error(r) 14 | t.FailNow() 15 | } 16 | } 17 | func TestInt64SliceToString3(t *testing.T) { 18 | if r := Int64SliceToString(nil, ","); r != "" { 19 | t.Error(r) 20 | t.FailNow() 21 | } 22 | } 23 | func TestUint64SliceToString(t *testing.T) { 24 | if r := Int64SliceToString([]int64{1111111111111111111, 222222222222222222, 33333333333333333, 44444444444444, 5555555555555555}, "#"); r != "1111111111111111111#222222222222222222#33333333333333333#44444444444444#5555555555555555" { 25 | t.Error(r) 26 | t.FailNow() 27 | } 28 | } 29 | func TestUint64SliceToString2(t *testing.T) { 30 | if r := Int64SliceToString([]int64{}, ","); r != "" { 31 | t.Error(r) 32 | t.FailNow() 33 | } 34 | } 35 | func TestUint64SliceToString3(t *testing.T) { 36 | if r := Int64SliceToString(nil, ","); r != "" { 37 | t.Error(r) 38 | t.FailNow() 39 | } 40 | } 41 | func TestStringSliceToString(t *testing.T) { 42 | if r := StringSliceToString([]string{"111", "222", "333", "444"}, "-"); r != "111-222-333-444" { 43 | t.Error(r) 44 | t.FailNow() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/util/time.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "time" 4 | 5 | func GetCST8Time(t time.Time) time.Time { 6 | return t.In(time.FixedZone("CST", 8*3600)) 7 | } 8 | -------------------------------------------------------------------------------- /logo/bilibili.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iyear/biligo/2b51a7f5ef332f20dbb0b04107ee6c1d5dc21f9d/logo/bilibili.png -------------------------------------------------------------------------------- /proto/dm/dm.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.17.3 5 | // source: dm.proto 6 | 7 | package dm 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type DmSegMobileReply struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | // 弹幕列表 29 | Elems []*DanmakuElem `protobuf:"bytes,1,rep,name=elems,proto3" json:"elems,omitempty"` 30 | // 是否已关闭弹幕 31 | // 0:未关闭 1:已关闭 32 | State int32 `protobuf:"varint,2,opt,name=state,proto3" json:"state,omitempty"` 33 | // 弹幕云屏蔽ai评分值 34 | AiFlag *DanmakuAIFlag `protobuf:"bytes,3,opt,name=ai_flag,json=aiFlag,proto3" json:"ai_flag,omitempty"` 35 | } 36 | 37 | func (x *DmSegMobileReply) Reset() { 38 | *x = DmSegMobileReply{} 39 | if protoimpl.UnsafeEnabled { 40 | mi := &file_dm_proto_msgTypes[0] 41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 | ms.StoreMessageInfo(mi) 43 | } 44 | } 45 | 46 | func (x *DmSegMobileReply) String() string { 47 | return protoimpl.X.MessageStringOf(x) 48 | } 49 | 50 | func (*DmSegMobileReply) ProtoMessage() {} 51 | 52 | func (x *DmSegMobileReply) ProtoReflect() protoreflect.Message { 53 | mi := &file_dm_proto_msgTypes[0] 54 | if protoimpl.UnsafeEnabled && x != nil { 55 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 56 | if ms.LoadMessageInfo() == nil { 57 | ms.StoreMessageInfo(mi) 58 | } 59 | return ms 60 | } 61 | return mi.MessageOf(x) 62 | } 63 | 64 | // Deprecated: Use DmSegMobileReply.ProtoReflect.Descriptor instead. 65 | func (*DmSegMobileReply) Descriptor() ([]byte, []int) { 66 | return file_dm_proto_rawDescGZIP(), []int{0} 67 | } 68 | 69 | func (x *DmSegMobileReply) GetElems() []*DanmakuElem { 70 | if x != nil { 71 | return x.Elems 72 | } 73 | return nil 74 | } 75 | 76 | func (x *DmSegMobileReply) GetState() int32 { 77 | if x != nil { 78 | return x.State 79 | } 80 | return 0 81 | } 82 | 83 | func (x *DmSegMobileReply) GetAiFlag() *DanmakuAIFlag { 84 | if x != nil { 85 | return x.AiFlag 86 | } 87 | return nil 88 | } 89 | 90 | // 弹幕ai云屏蔽列表 91 | type DanmakuAIFlag struct { 92 | state protoimpl.MessageState 93 | sizeCache protoimpl.SizeCache 94 | unknownFields protoimpl.UnknownFields 95 | 96 | // 弹幕ai云屏蔽条目 97 | DmFlags []*DanmakuFlag `protobuf:"bytes,1,rep,name=dm_flags,json=dmFlags,proto3" json:"dm_flags,omitempty"` 98 | } 99 | 100 | func (x *DanmakuAIFlag) Reset() { 101 | *x = DanmakuAIFlag{} 102 | if protoimpl.UnsafeEnabled { 103 | mi := &file_dm_proto_msgTypes[1] 104 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 105 | ms.StoreMessageInfo(mi) 106 | } 107 | } 108 | 109 | func (x *DanmakuAIFlag) String() string { 110 | return protoimpl.X.MessageStringOf(x) 111 | } 112 | 113 | func (*DanmakuAIFlag) ProtoMessage() {} 114 | 115 | func (x *DanmakuAIFlag) ProtoReflect() protoreflect.Message { 116 | mi := &file_dm_proto_msgTypes[1] 117 | if protoimpl.UnsafeEnabled && x != nil { 118 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 119 | if ms.LoadMessageInfo() == nil { 120 | ms.StoreMessageInfo(mi) 121 | } 122 | return ms 123 | } 124 | return mi.MessageOf(x) 125 | } 126 | 127 | // Deprecated: Use DanmakuAIFlag.ProtoReflect.Descriptor instead. 128 | func (*DanmakuAIFlag) Descriptor() ([]byte, []int) { 129 | return file_dm_proto_rawDescGZIP(), []int{1} 130 | } 131 | 132 | func (x *DanmakuAIFlag) GetDmFlags() []*DanmakuFlag { 133 | if x != nil { 134 | return x.DmFlags 135 | } 136 | return nil 137 | } 138 | 139 | // 弹幕ai云屏蔽条目 140 | type DanmakuFlag struct { 141 | state protoimpl.MessageState 142 | sizeCache protoimpl.SizeCache 143 | unknownFields protoimpl.UnknownFields 144 | 145 | // 弹幕dmid 146 | Dmid int64 `protobuf:"varint,1,opt,name=dmid,proto3" json:"dmid,omitempty"` 147 | // 评分 148 | Flag uint32 `protobuf:"varint,2,opt,name=flag,proto3" json:"flag,omitempty"` 149 | } 150 | 151 | func (x *DanmakuFlag) Reset() { 152 | *x = DanmakuFlag{} 153 | if protoimpl.UnsafeEnabled { 154 | mi := &file_dm_proto_msgTypes[2] 155 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 156 | ms.StoreMessageInfo(mi) 157 | } 158 | } 159 | 160 | func (x *DanmakuFlag) String() string { 161 | return protoimpl.X.MessageStringOf(x) 162 | } 163 | 164 | func (*DanmakuFlag) ProtoMessage() {} 165 | 166 | func (x *DanmakuFlag) ProtoReflect() protoreflect.Message { 167 | mi := &file_dm_proto_msgTypes[2] 168 | if protoimpl.UnsafeEnabled && x != nil { 169 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 170 | if ms.LoadMessageInfo() == nil { 171 | ms.StoreMessageInfo(mi) 172 | } 173 | return ms 174 | } 175 | return mi.MessageOf(x) 176 | } 177 | 178 | // Deprecated: Use DanmakuFlag.ProtoReflect.Descriptor instead. 179 | func (*DanmakuFlag) Descriptor() ([]byte, []int) { 180 | return file_dm_proto_rawDescGZIP(), []int{2} 181 | } 182 | 183 | func (x *DanmakuFlag) GetDmid() int64 { 184 | if x != nil { 185 | return x.Dmid 186 | } 187 | return 0 188 | } 189 | 190 | func (x *DanmakuFlag) GetFlag() uint32 { 191 | if x != nil { 192 | return x.Flag 193 | } 194 | return 0 195 | } 196 | 197 | // 弹幕条目 198 | type DanmakuElem struct { 199 | state protoimpl.MessageState 200 | sizeCache protoimpl.SizeCache 201 | unknownFields protoimpl.UnknownFields 202 | 203 | // 弹幕dmid 204 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 205 | // 弹幕出现位置(单位ms) 206 | Progress int32 `protobuf:"varint,2,opt,name=progress,proto3" json:"progress,omitempty"` 207 | // 弹幕类型 208 | Mode int32 `protobuf:"varint,3,opt,name=mode,proto3" json:"mode,omitempty"` 209 | // 弹幕字号 210 | Fontsize int32 `protobuf:"varint,4,opt,name=fontsize,proto3" json:"fontsize,omitempty"` 211 | // 弹幕颜色 212 | Color uint32 `protobuf:"varint,5,opt,name=color,proto3" json:"color,omitempty"` 213 | // 发送着mid hash 214 | MidHash string `protobuf:"bytes,6,opt,name=midHash,proto3" json:"midHash,omitempty"` 215 | // 弹幕正文 216 | Content string `protobuf:"bytes,7,opt,name=content,proto3" json:"content,omitempty"` 217 | // 发送时间 218 | Ctime int64 `protobuf:"varint,8,opt,name=ctime,proto3" json:"ctime,omitempty"` 219 | // 权重 区间:[1,10] 220 | Weight int32 `protobuf:"varint,9,opt,name=weight,proto3" json:"weight,omitempty"` 221 | // 动作 222 | Action string `protobuf:"bytes,10,opt,name=action,proto3" json:"action,omitempty"` 223 | // 弹幕池 224 | Pool int32 `protobuf:"varint,11,opt,name=pool,proto3" json:"pool,omitempty"` 225 | // 弹幕dmid str 226 | IdStr string `protobuf:"bytes,12,opt,name=idStr,proto3" json:"idStr,omitempty"` 227 | // 弹幕属性位(bin求AND) 228 | // bit0:保护 bit1:直播 bit2:高赞 229 | Attr int32 `protobuf:"varint,13,opt,name=attr,proto3" json:"attr,omitempty"` 230 | } 231 | 232 | func (x *DanmakuElem) Reset() { 233 | *x = DanmakuElem{} 234 | if protoimpl.UnsafeEnabled { 235 | mi := &file_dm_proto_msgTypes[3] 236 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 237 | ms.StoreMessageInfo(mi) 238 | } 239 | } 240 | 241 | func (x *DanmakuElem) String() string { 242 | return protoimpl.X.MessageStringOf(x) 243 | } 244 | 245 | func (*DanmakuElem) ProtoMessage() {} 246 | 247 | func (x *DanmakuElem) ProtoReflect() protoreflect.Message { 248 | mi := &file_dm_proto_msgTypes[3] 249 | if protoimpl.UnsafeEnabled && x != nil { 250 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 251 | if ms.LoadMessageInfo() == nil { 252 | ms.StoreMessageInfo(mi) 253 | } 254 | return ms 255 | } 256 | return mi.MessageOf(x) 257 | } 258 | 259 | // Deprecated: Use DanmakuElem.ProtoReflect.Descriptor instead. 260 | func (*DanmakuElem) Descriptor() ([]byte, []int) { 261 | return file_dm_proto_rawDescGZIP(), []int{3} 262 | } 263 | 264 | func (x *DanmakuElem) GetId() int64 { 265 | if x != nil { 266 | return x.Id 267 | } 268 | return 0 269 | } 270 | 271 | func (x *DanmakuElem) GetProgress() int32 { 272 | if x != nil { 273 | return x.Progress 274 | } 275 | return 0 276 | } 277 | 278 | func (x *DanmakuElem) GetMode() int32 { 279 | if x != nil { 280 | return x.Mode 281 | } 282 | return 0 283 | } 284 | 285 | func (x *DanmakuElem) GetFontsize() int32 { 286 | if x != nil { 287 | return x.Fontsize 288 | } 289 | return 0 290 | } 291 | 292 | func (x *DanmakuElem) GetColor() uint32 { 293 | if x != nil { 294 | return x.Color 295 | } 296 | return 0 297 | } 298 | 299 | func (x *DanmakuElem) GetMidHash() string { 300 | if x != nil { 301 | return x.MidHash 302 | } 303 | return "" 304 | } 305 | 306 | func (x *DanmakuElem) GetContent() string { 307 | if x != nil { 308 | return x.Content 309 | } 310 | return "" 311 | } 312 | 313 | func (x *DanmakuElem) GetCtime() int64 { 314 | if x != nil { 315 | return x.Ctime 316 | } 317 | return 0 318 | } 319 | 320 | func (x *DanmakuElem) GetWeight() int32 { 321 | if x != nil { 322 | return x.Weight 323 | } 324 | return 0 325 | } 326 | 327 | func (x *DanmakuElem) GetAction() string { 328 | if x != nil { 329 | return x.Action 330 | } 331 | return "" 332 | } 333 | 334 | func (x *DanmakuElem) GetPool() int32 { 335 | if x != nil { 336 | return x.Pool 337 | } 338 | return 0 339 | } 340 | 341 | func (x *DanmakuElem) GetIdStr() string { 342 | if x != nil { 343 | return x.IdStr 344 | } 345 | return "" 346 | } 347 | 348 | func (x *DanmakuElem) GetAttr() int32 { 349 | if x != nil { 350 | return x.Attr 351 | } 352 | return 0 353 | } 354 | 355 | var File_dm_proto protoreflect.FileDescriptor 356 | 357 | var file_dm_proto_rawDesc = []byte{ 358 | 0x0a, 0x08, 0x64, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x64, 0x61, 0x6e, 0x6d, 359 | 0x61, 0x6b, 0x75, 0x22, 0x85, 0x01, 0x0a, 0x10, 0x44, 0x6d, 0x53, 0x65, 0x67, 0x4d, 0x6f, 0x62, 360 | 0x69, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x6c, 0x65, 0x6d, 361 | 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x61, 0x6e, 0x6d, 0x61, 0x6b, 362 | 0x75, 0x2e, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x6b, 0x75, 0x45, 0x6c, 0x65, 0x6d, 0x52, 0x05, 0x65, 363 | 0x6c, 0x65, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 364 | 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x61, 0x69, 365 | 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 366 | 0x6e, 0x6d, 0x61, 0x6b, 0x75, 0x2e, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x6b, 0x75, 0x41, 0x49, 0x46, 367 | 0x6c, 0x61, 0x67, 0x52, 0x06, 0x61, 0x69, 0x46, 0x6c, 0x61, 0x67, 0x22, 0x40, 0x0a, 0x0d, 0x44, 368 | 0x61, 0x6e, 0x6d, 0x61, 0x6b, 0x75, 0x41, 0x49, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x2f, 0x0a, 0x08, 369 | 0x64, 0x6d, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 370 | 0x2e, 0x64, 0x61, 0x6e, 0x6d, 0x61, 0x6b, 0x75, 0x2e, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x6b, 0x75, 371 | 0x46, 0x6c, 0x61, 0x67, 0x52, 0x07, 0x64, 0x6d, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x35, 0x0a, 372 | 0x0b, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x6b, 0x75, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, 373 | 0x64, 0x6d, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x64, 0x6d, 0x69, 0x64, 374 | 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 375 | 0x66, 0x6c, 0x61, 0x67, 0x22, 0xb7, 0x02, 0x0a, 0x0b, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x6b, 0x75, 376 | 0x45, 0x6c, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 377 | 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 378 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 379 | 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 380 | 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x6f, 0x6e, 0x74, 0x73, 0x69, 0x7a, 0x65, 381 | 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x6f, 0x6e, 0x74, 0x73, 0x69, 0x7a, 0x65, 382 | 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 383 | 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x64, 0x48, 0x61, 0x73, 384 | 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x69, 0x64, 0x48, 0x61, 0x73, 0x68, 385 | 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 386 | 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x74, 387 | 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 388 | 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 389 | 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 390 | 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 391 | 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 392 | 0x70, 0x6f, 0x6f, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x64, 0x53, 0x74, 0x72, 0x18, 0x0c, 0x20, 393 | 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x64, 0x53, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x74, 394 | 0x74, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x61, 0x74, 0x74, 0x72, 0x42, 0x05, 395 | 0x5a, 0x03, 0x2f, 0x64, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 396 | } 397 | 398 | var ( 399 | file_dm_proto_rawDescOnce sync.Once 400 | file_dm_proto_rawDescData = file_dm_proto_rawDesc 401 | ) 402 | 403 | func file_dm_proto_rawDescGZIP() []byte { 404 | file_dm_proto_rawDescOnce.Do(func() { 405 | file_dm_proto_rawDescData = protoimpl.X.CompressGZIP(file_dm_proto_rawDescData) 406 | }) 407 | return file_dm_proto_rawDescData 408 | } 409 | 410 | var file_dm_proto_msgTypes = make([]protoimpl.MessageInfo, 4) 411 | var file_dm_proto_goTypes = []interface{}{ 412 | (*DmSegMobileReply)(nil), // 0: danmaku.DmSegMobileReply 413 | (*DanmakuAIFlag)(nil), // 1: danmaku.DanmakuAIFlag 414 | (*DanmakuFlag)(nil), // 2: danmaku.DanmakuFlag 415 | (*DanmakuElem)(nil), // 3: danmaku.DanmakuElem 416 | } 417 | var file_dm_proto_depIdxs = []int32{ 418 | 3, // 0: danmaku.DmSegMobileReply.elems:type_name -> danmaku.DanmakuElem 419 | 1, // 1: danmaku.DmSegMobileReply.ai_flag:type_name -> danmaku.DanmakuAIFlag 420 | 2, // 2: danmaku.DanmakuAIFlag.dm_flags:type_name -> danmaku.DanmakuFlag 421 | 3, // [3:3] is the sub-list for method output_type 422 | 3, // [3:3] is the sub-list for method input_type 423 | 3, // [3:3] is the sub-list for extension type_name 424 | 3, // [3:3] is the sub-list for extension extendee 425 | 0, // [0:3] is the sub-list for field type_name 426 | } 427 | 428 | func init() { file_dm_proto_init() } 429 | func file_dm_proto_init() { 430 | if File_dm_proto != nil { 431 | return 432 | } 433 | if !protoimpl.UnsafeEnabled { 434 | file_dm_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 435 | switch v := v.(*DmSegMobileReply); i { 436 | case 0: 437 | return &v.state 438 | case 1: 439 | return &v.sizeCache 440 | case 2: 441 | return &v.unknownFields 442 | default: 443 | return nil 444 | } 445 | } 446 | file_dm_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 447 | switch v := v.(*DanmakuAIFlag); i { 448 | case 0: 449 | return &v.state 450 | case 1: 451 | return &v.sizeCache 452 | case 2: 453 | return &v.unknownFields 454 | default: 455 | return nil 456 | } 457 | } 458 | file_dm_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 459 | switch v := v.(*DanmakuFlag); i { 460 | case 0: 461 | return &v.state 462 | case 1: 463 | return &v.sizeCache 464 | case 2: 465 | return &v.unknownFields 466 | default: 467 | return nil 468 | } 469 | } 470 | file_dm_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 471 | switch v := v.(*DanmakuElem); i { 472 | case 0: 473 | return &v.state 474 | case 1: 475 | return &v.sizeCache 476 | case 2: 477 | return &v.unknownFields 478 | default: 479 | return nil 480 | } 481 | } 482 | } 483 | type x struct{} 484 | out := protoimpl.TypeBuilder{ 485 | File: protoimpl.DescBuilder{ 486 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 487 | RawDescriptor: file_dm_proto_rawDesc, 488 | NumEnums: 0, 489 | NumMessages: 4, 490 | NumExtensions: 0, 491 | NumServices: 0, 492 | }, 493 | GoTypes: file_dm_proto_goTypes, 494 | DependencyIndexes: file_dm_proto_depIdxs, 495 | MessageInfos: file_dm_proto_msgTypes, 496 | }.Build() 497 | File_dm_proto = out.File 498 | file_dm_proto_rawDesc = nil 499 | file_dm_proto_goTypes = nil 500 | file_dm_proto_depIdxs = nil 501 | } 502 | -------------------------------------------------------------------------------- /proto/dm/dm.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package danmaku; 4 | option go_package = "/dm"; 5 | message DmSegMobileReply { 6 | // 弹幕列表 7 | repeated DanmakuElem elems = 1; 8 | // 是否已关闭弹幕 9 | // 0:未关闭 1:已关闭 10 | int32 state = 2; 11 | // 弹幕云屏蔽ai评分值 12 | DanmakuAIFlag ai_flag = 3; 13 | } 14 | // 弹幕ai云屏蔽列表 15 | message DanmakuAIFlag { 16 | // 弹幕ai云屏蔽条目 17 | repeated DanmakuFlag dm_flags = 1; 18 | } 19 | // 弹幕ai云屏蔽条目 20 | message DanmakuFlag { 21 | // 弹幕dmid 22 | int64 dmid = 1; 23 | // 评分 24 | uint32 flag = 2; 25 | } 26 | // 弹幕条目 27 | message DanmakuElem { 28 | // 弹幕dmid 29 | int64 id = 1; 30 | // 弹幕出现位置(单位ms) 31 | int32 progress = 2; 32 | // 弹幕类型 33 | int32 mode = 3; 34 | // 弹幕字号 35 | int32 fontsize = 4; 36 | // 弹幕颜色 37 | uint32 color = 5; 38 | // 发送着mid hash 39 | string midHash = 6; 40 | // 弹幕正文 41 | string content = 7; 42 | // 发送时间 43 | int64 ctime = 8; 44 | // 权重 区间:[1,10] 45 | int32 weight = 9; 46 | // 动作 47 | string action = 10; 48 | // 弹幕池 49 | int32 pool = 11; 50 | // 弹幕dmid str 51 | string idStr = 12; 52 | // 弹幕属性位(bin求AND) 53 | // bit0:保护 bit1:直播 bit2:高赞 54 | int32 attr = 13; 55 | } -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package biligo 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | const table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF" 13 | 14 | var tr = map[string]int64{} 15 | var s = []int{11, 10, 3, 8, 4, 6} 16 | 17 | const xor = 177451812 18 | const add = 8728348608 19 | 20 | func init() { 21 | tableByte := []byte(table) 22 | for i := 0; i < 58; i++ { 23 | tr[string(tableByte[i])] = int64(i) 24 | } 25 | } 26 | 27 | // BV2AV 带BV前缀 28 | func BV2AV(bv string) int64 { 29 | var r int64 30 | arr := []rune(bv) 31 | 32 | for i := 0; i < 6; i++ { 33 | r += tr[string(arr[s[i]])] * int64(math.Pow(float64(58), float64(i))) 34 | } 35 | return (r - add) ^ xor 36 | } 37 | 38 | // AV2BV 带BV前缀 39 | func AV2BV(av int64) string { 40 | x := (av ^ xor) + add 41 | r := []string{"B", "V", "1", " ", " ", "4", " ", "1", " ", "7", " ", " "} 42 | for i := 0; i < 6; i++ { 43 | r[s[i]] = string(table[int64(math.Floor(float64(x/int64(math.Pow(float64(58), float64(i))))))%58]) 44 | } 45 | var result string 46 | for i := 0; i < 12; i++ { 47 | result += r[i] 48 | } 49 | return result 50 | } 51 | 52 | // parseDynaAt 由于ctrl的location是字符定位的,而FindAllStringIndex获取的是字节定位,只能遍历一遍拿到字符定位 53 | func parseDynaAt(tp int, content string, at map[string]int64) []*dynaCtrl { 54 | match := regexp.MustCompile("@.*? ").FindAllStringIndex(content, -1) 55 | var ( 56 | ctrl []*dynaCtrl 57 | a = 0 58 | c = 0 59 | ) 60 | for i, t := range []rune(content) { 61 | if a == len(match) { 62 | break 63 | } 64 | if c == match[a][0] { 65 | up := strings.TrimPrefix(content[match[a][0]:match[a][1]], "@") 66 | up = strings.TrimSuffix(up, " ") 67 | ctrl = append(ctrl, &dynaCtrl{ 68 | Location: i, 69 | Type: tp, 70 | Length: len([]rune(up)) + 2, // 之前删了@和空格,需要加回来 71 | Data: strconv.FormatInt(at[up], 10), 72 | }) 73 | a++ 74 | } 75 | c += len(fmt.Sprintf("%c", t)) 76 | } 77 | return ctrl 78 | } 79 | func genDynaPic(pics []*DynaUploadPic) (string, error) { 80 | var pic []*dynaPic 81 | for _, p := range pics { 82 | pic = append(pic, &dynaPic{ 83 | ImgSrc: p.ImageURL, 84 | ImageWidth: p.ImageWidth, 85 | ImageHeight: p.ImageHeight, 86 | }) 87 | } 88 | j, err := json.Marshal(pic) 89 | if err != nil { 90 | return "", err 91 | } 92 | return string(j), nil 93 | } 94 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package biligo 2 | 3 | import "testing" 4 | 5 | func TestAV2BV(t *testing.T) { 6 | if AV2BV(170001) != "BV17x411w7KC" { 7 | t.FailNow() 8 | } 9 | } 10 | func TestBV2AV(t *testing.T) { 11 | if BV2AV("BV17x411w7KC") != 170001 { 12 | t.FailNow() 13 | } 14 | } 15 | --------------------------------------------------------------------------------