├── .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 | 
2 |
3 | ## 简介
4 | 
5 | 
6 | 
7 | 
8 | [](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 |
--------------------------------------------------------------------------------