├── .github
└── workflows
│ ├── pull.yml
│ └── push.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── README.md
├── aireply
├── ai_reply.go
├── chatgpt.go
├── lolimi.go
├── qingyunke.go
└── xiaoai.go
├── ascii2d
├── ascii2d.go
├── ascii2d_test.go
└── result.txt
├── bilibili
├── .gitignore
├── api.go
├── api_test.go
├── types.go
├── util.go
└── wbi.go
├── emozi
├── all_test.go
├── api.go
├── coder.go
└── login.go
├── go.mod
├── go.sum
├── huggingface
└── huggingface.go
├── kimoi
└── chat.go
├── neteasemusic
└── neteasemusic.go
├── niu
├── main.go
├── models.go
├── test_test.go
└── utils.go
├── novelai
└── main.go
├── nsfw
├── README.md
├── api.go
└── api_test.go
├── pixiv
├── cat.go
├── cat_test.go
├── download.go
├── download_test.go
└── pixiv.go
├── qzone
├── api.go
├── api_test.go
├── types.go
└── util.go
├── runoob
├── runoob.go
└── runoob_test.go
├── setu
├── default.go
├── pool.go
└── pool_test.go
├── shindanmaker
└── shindanmaker.go
├── tl
└── tl.go
├── tts
├── baidutts
│ ├── baidutts.go
│ ├── baidutts_test.go
│ └── data.go
├── lolimi
│ ├── api.go
│ └── tts.go
├── tts.go
└── ttscn
│ ├── speakers.json
│ ├── tts.go
│ └── tts_test.go
├── wallet
└── wallet.go
├── wenxinAI
├── erniemodle
│ └── erniemodle.go
└── ernievilg
│ └── ernievilg.go
└── yandex
└── yandex.go
/.github/workflows/pull.yml:
--------------------------------------------------------------------------------
1 | name: PullLint
2 | on: [ pull_request ]
3 | jobs:
4 | golangci:
5 | name: lint
6 | runs-on: ubuntu-latest
7 | steps:
8 | - name: Set up Go
9 | uses: actions/setup-go@master
10 | with:
11 | go-version: '1.20'
12 |
13 | - name: Check out code into the Go module directory
14 | uses: actions/checkout@v4
15 | with:
16 | ref: ${{ github.event.pull_request.head.sha }}
17 |
18 | - name: golangci-lint
19 | uses: golangci/golangci-lint-action@master
20 | with:
21 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
22 | version: latest
23 |
24 | # Optional: working directory, useful for monorepos
25 | # working-directory: somedir
26 |
27 | # Optional: golangci-lint command line arguments.
28 | # args: --issues-exit-code=0
29 |
30 | # Optional: show only new issues if it's a pull request. The default value is `false`.
31 | # only-new-issues: true
32 |
33 | # Optional: if set to true then the action don't cache or restore ~/go/pkg.
34 | # skip-pkg-cache: true
35 |
36 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
37 | # skip-build-cache: true
--------------------------------------------------------------------------------
/.github/workflows/push.yml:
--------------------------------------------------------------------------------
1 | name: PushLint
2 | on: [ push ]
3 | jobs:
4 | golangci:
5 | name: lint
6 | runs-on: ubuntu-latest
7 | steps:
8 | - name: Set up Go
9 | uses: actions/setup-go@master
10 | with:
11 | go-version: '1.20'
12 |
13 | - name: Check out code into the Go module directory
14 | uses: actions/checkout@master
15 |
16 | - name: golangci-lint
17 | uses: golangci/golangci-lint-action@master
18 | with:
19 | version: latest
20 |
21 | - name: Commit back
22 | if: ${{ !github.head_ref }}
23 | continue-on-error: true
24 | run: |
25 | git config --local user.name 'github-actions[bot]'
26 | git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com'
27 | git add --all
28 | git commit -m "🎨 改进代码样式"
29 | - name: Create Pull Request
30 | if: ${{ !github.head_ref }}
31 | continue-on-error: true
32 | uses: peter-evans/create-pull-request@v4
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | data
3 | tts/mockingbird/data
4 | .idea
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters-settings:
2 | errcheck:
3 | ignore: fmt:.*
4 | ignoretests: true
5 |
6 | goimports:
7 | local-prefixes: github.com/FloatTech/AnimeAPI
8 |
9 | forbidigo:
10 | # Forbid the following identifiers
11 | forbid:
12 | - ^fmt\.Errorf$ # consider errors.Errorf in github.com/pkg/errors
13 |
14 | linters:
15 | # please, do not use `enable-all`: it's deprecated and will be removed soon.
16 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
17 | disable-all: true
18 | fast: false
19 | enable:
20 | - bodyclose
21 | #- depguard
22 | - dogsled
23 | - errcheck
24 | - copyloopvar
25 | - exhaustive
26 | #- funlen
27 | #- goconst
28 | - gocritic
29 | #- gocyclo
30 | - gofmt
31 | - goimports
32 | - goprintffuncname
33 | #- gosec
34 | - gosimple
35 | - govet
36 | - ineffassign
37 | #- misspell
38 | - nolintlint
39 | - rowserrcheck
40 | - staticcheck
41 | - stylecheck
42 | - typecheck
43 | - unconvert
44 | - unparam
45 | - unused
46 | - whitespace
47 | - prealloc
48 | - predeclared
49 | - asciicheck
50 | - revive
51 | - forbidigo
52 | - makezero
53 |
54 | run:
55 | # default concurrency is a available CPU number.
56 | # concurrency: 4 # explicitly omit this value to fully utilize available resources.
57 | deadline: 5m
58 | issues-exit-code: 1
59 | tests: false
60 | skip-dirs:
61 | - order
62 | go: '1.20'
63 |
64 | # output configuration options
65 | output:
66 | format: "colored-line-number"
67 | print-issued-lines: true
68 | print-linter-name: true
69 | uniq-by-line: true
70 |
71 | issues:
72 | # Fix found issues (if it's supported by the linter)
73 | fix: true
74 | exclude-use-default: false
75 | exclude:
76 | - "Error return value of .((os.)?std(out|err)..*|.*Close|.*Seek|.*Flush|os.Remove(All)?|.*print(f|ln)?|os.(Un)?Setenv). is not check"
77 | - 'identifier ".*" contain non-ASCII character: U\+.*'
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AnimeAPI
2 | 二次元相关(也可能关系不大)API合集, 详细说明可前往各文件夹查看。
3 | ## aireply
4 | 调用 AI 回复对话
5 | ## ascii2d
6 | ascii2d 搜图
7 | ## bilibili
8 | b站相关API
9 | ## emozi
10 | 颜文字抽象转写
11 | ## huggingface
12 | huggingface API
13 | ## kimoi
14 | AI匹配kimoi词库
15 | ## neteasemusic
16 | 网易云的一些简单功能
17 | ## novelai
18 | 二次元 AI tag 作画
19 | ## nsfw
20 | 图片合规性审查
21 | ## niu
22 | niu
23 | ## pixiv
24 | P站解析与图片下载
25 | ## qzone
26 | QQ空间API
27 | ## runoob
28 | 菜鸟教程代码运行API
29 | ## setu
30 | 供各个插件使用的随机图片池
31 | ## shindanmaker
32 | 基于 https://shindanmaker.com 的 API
33 | ## tl
34 | 翻译
35 | ## tts
36 | 文字转语音
37 | ## wallet
38 | 货币系统
39 | ## wenxinAI
40 | 百度文心AI
41 | - [x] Ernie Vilg
42 | 文心一格AI画图
43 | - [x] ernieModel
44 | 文本理解与创作
45 | ## yandex
46 | Yandex 搜图
47 |
--------------------------------------------------------------------------------
/aireply/ai_reply.go:
--------------------------------------------------------------------------------
1 | // Package aireply 人工智能回复
2 | package aireply
3 |
4 | import "fmt"
5 |
6 | // AIReply 公用智能回复类
7 | type AIReply interface {
8 | // Talk 取得带 CQ 码的回复消息
9 | Talk(uid int64, msg, nickname string) string
10 | // Talk 取得文本回复消息
11 | TalkPlain(uid int64, msg, nickname string) string
12 | // String 获得实际使用的回复服务名
13 | fmt.Stringer
14 | }
15 |
--------------------------------------------------------------------------------
/aireply/chatgpt.go:
--------------------------------------------------------------------------------
1 | package aireply
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "net/http"
8 | "strings"
9 |
10 | "github.com/FloatTech/floatbox/binary"
11 | )
12 |
13 | // ChatGPT GPT回复类
14 | type ChatGPT struct {
15 | u string
16 | k string
17 | b []string
18 | }
19 |
20 | // chatGPTResponseBody 响应体
21 | type chatGPTResponseBody struct {
22 | ID string `json:"id"`
23 | Object string `json:"object"`
24 | Created int `json:"created"`
25 | Model string `json:"model"`
26 | Choices []map[string]interface{} `json:"choices"`
27 | Usage map[string]interface{} `json:"usage"`
28 | }
29 |
30 | // chatGPTRequestBody 请求体
31 | type chatGPTRequestBody struct {
32 | Model string `json:"model"`
33 | Prompt string `json:"prompt"`
34 | MaxTokens int `json:"max_tokens"`
35 | Temperature float32 `json:"temperature"`
36 | TopP int `json:"top_p"`
37 | FrequencyPenalty int `json:"frequency_penalty"`
38 | PresencePenalty int `json:"presence_penalty"`
39 | }
40 |
41 | const (
42 | // ChatGPTURL api地址
43 | ChatGPTURL = "https://api.openai.com/v1/"
44 | )
45 |
46 | // NewChatGPT ...
47 | func NewChatGPT(u, key string, banwords ...string) *ChatGPT {
48 | return &ChatGPT{u: u, k: key, b: banwords}
49 | }
50 |
51 | // String ...
52 | func (*ChatGPT) String() string {
53 | return "ChatGPT"
54 | }
55 |
56 | // Talk 取得带 CQ 码的回复消息
57 | func (c *ChatGPT) Talk(_ int64, msg, _ string) string {
58 | replystr := chat(msg, c.k, c.u)
59 | for _, w := range c.b {
60 | if strings.Contains(replystr, w) {
61 | return "ERROR: 回复可能含有敏感内容"
62 | }
63 | }
64 | return replystr
65 | }
66 |
67 | // TalkPlain 取得回复消息
68 | func (c *ChatGPT) TalkPlain(_ int64, msg, nickname string) string {
69 | return c.Talk(0, msg, nickname)
70 | }
71 |
72 | func chat(msg string, apiKey string, url string) string {
73 | requestBody := chatGPTRequestBody{
74 | Model: "text-davinci-003",
75 | Prompt: msg,
76 | MaxTokens: 2048,
77 | Temperature: 0.7,
78 | TopP: 1,
79 | FrequencyPenalty: 0,
80 | PresencePenalty: 0,
81 | }
82 | requestData := binary.SelectWriter()
83 | defer binary.PutWriter(requestData)
84 | err := json.NewEncoder(requestData).Encode(&requestBody)
85 | if err != nil {
86 | return err.Error()
87 | }
88 | req, err := http.NewRequest("POST", url+"completions", (*bytes.Buffer)(requestData))
89 | if err != nil {
90 | return err.Error()
91 | }
92 |
93 | req.Header.Set("Content-Type", "application/json")
94 | req.Header.Set("Authorization", "Bearer "+apiKey)
95 | client := &http.Client{}
96 | response, err := client.Do(req)
97 | if err != nil {
98 | return err.Error()
99 | }
100 | defer response.Body.Close()
101 | var gptResponseBody chatGPTResponseBody
102 | err = json.NewDecoder(response.Body).Decode(&gptResponseBody)
103 | if err != nil {
104 | return err.Error()
105 | }
106 | if len(gptResponseBody.Choices) > 0 {
107 | for _, v := range gptResponseBody.Choices {
108 | return fmt.Sprint(v["text"])
109 | }
110 | }
111 | return ""
112 | }
113 |
--------------------------------------------------------------------------------
/aireply/lolimi.go:
--------------------------------------------------------------------------------
1 | package aireply
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "net/url"
8 | "strings"
9 |
10 | "github.com/FloatTech/floatbox/binary"
11 | "github.com/FloatTech/floatbox/web"
12 | "github.com/tidwall/gjson"
13 | )
14 |
15 | // LolimiAi Lolimi回复类
16 | type LolimiAi struct {
17 | u string // API 地址
18 | n string // AI 名称
19 | k string // API 密钥
20 | b []string // Banwords
21 | t bool // API 响应模式是否为文本
22 | l int // 记忆限制数(小于 1 的值可以禁用记忆模式)
23 | m []lolimiMessage // 记忆数据
24 | }
25 |
26 | // lolimiMessage 消息记忆记录
27 | type lolimiMessage struct {
28 | Role string `json:"role"`
29 | Content string `json:"content"`
30 | }
31 |
32 | const (
33 | lolimiURL = "https://apii.lolimi.cn"
34 | // MomoURL api地址
35 | MomoURL = lolimiURL + "/api/mmai/mm?key=%s&msg=%s"
36 | // MomoBotName ...
37 | MomoBotName = "沫沫"
38 | // JingfengURL api地址
39 | JingfengURL = lolimiURL + "/api/jjai/jj?key=%s&msg=%s"
40 | // JingfengBotName ...
41 | JingfengBotName = "婧枫"
42 | // GPT4oURL api地址
43 | GPT4oURL = lolimiURL + "/api/4o/gpt4o?key=%s&msg=%s"
44 | // GPT4oBotName ...
45 | // TODO 换个更好的名字
46 | GPT4oBotName = "GPT4o"
47 |
48 | // 带记忆 POST 请求专区
49 |
50 | // C4oURL api地址
51 | C4oURL = lolimiURL + "/api/c4o/c?key=%s"
52 | // C4oBotName ...
53 | // TODO 换个更好的名字
54 | C4oBotName = "GPT4o"
55 | )
56 |
57 | // NewLolimiAi ...
58 | func NewLolimiAi(u, name string, key string, textMode bool, memoryLimit int, banwords ...string) *LolimiAi {
59 | return &LolimiAi{u: u, n: name, k: key, t: textMode, l: memoryLimit, b: banwords}
60 | }
61 |
62 | // String ...
63 | func (l *LolimiAi) String() string {
64 | return l.n
65 | }
66 |
67 | // TalkPlain 取得回复消息
68 | func (l *LolimiAi) TalkPlain(_ int64, msg, nickname string) string {
69 | msg = strings.ReplaceAll(msg, nickname, l.n)
70 | var data []byte
71 | var err error
72 | if l.l > 0 {
73 | u := fmt.Sprintf(l.u, url.QueryEscape(l.k))
74 | w := binary.SelectWriter()
75 | defer binary.PutWriter(w)
76 | err = json.NewEncoder(w).Encode(append(l.m,
77 | lolimiMessage{
78 | Role: "user", Content: msg,
79 | },
80 | ))
81 | if err != nil {
82 | return "ERROR: " + err.Error()
83 | }
84 | // TODO: 可能会返回
85 | // "请使用psot格式请求如有疑问进官方群"
86 | data, err = web.PostData(u, "application/json", (*bytes.Buffer)(w))
87 | } else {
88 | u := fmt.Sprintf(l.u, url.QueryEscape(l.k), url.QueryEscape(msg))
89 | data, err = web.GetData(u)
90 | }
91 | if err != nil {
92 | errMsg := err.Error()
93 | // Remove the key from error message
94 | errMsg = strings.ReplaceAll(errMsg, l.k, "********")
95 | return "ERROR: " + errMsg
96 | }
97 | var replystr string
98 | if l.t {
99 | replystr = binary.BytesToString(data)
100 | } else {
101 | replystr = gjson.Get(binary.BytesToString(data), "data.output").String()
102 | }
103 | // TODO: 是否要删除遗留代码
104 | replystr = strings.ReplaceAll(replystr, "
", "\n")
106 | replystr = strings.ReplaceAll(replystr, "\" />", "]")
107 | textReply := strings.ReplaceAll(replystr, l.n, nickname)
108 | for _, w := range l.b {
109 | if strings.Contains(textReply, w) {
110 | return "ERROR: 回复可能含有敏感内容"
111 | }
112 | }
113 | if l.l > 0 {
114 | // 添加记忆
115 | if len(l.m) >= l.l-1 && len(l.m) >= 2 {
116 | l.m = l.m[2:]
117 | }
118 | l.m = append(l.m,
119 | lolimiMessage{
120 | Role: "user", Content: msg,
121 | },
122 | lolimiMessage{
123 | Role: "assistant", Content: textReply,
124 | },
125 | )
126 | }
127 | return textReply
128 | }
129 |
130 | // Talk 取得带 CQ 码的回复消息
131 | func (l *LolimiAi) Talk(_ int64, msg, nickname string) string {
132 | return l.TalkPlain(0, msg, nickname)
133 | }
134 |
--------------------------------------------------------------------------------
/aireply/qingyunke.go:
--------------------------------------------------------------------------------
1 | package aireply
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "regexp"
7 | "strings"
8 |
9 | "github.com/FloatTech/floatbox/binary"
10 | "github.com/FloatTech/floatbox/web"
11 | "github.com/tidwall/gjson"
12 | )
13 |
14 | // QYK 青云客回复类
15 | type QYK struct {
16 | u string
17 | n string
18 | b []string
19 | }
20 |
21 | const (
22 | // QYKURL api地址
23 | QYKURL = "http://api.qingyunke.com/api.php?key=free&appid=0&msg=%v"
24 | // QYKBotName ...
25 | QYKBotName = "菲菲"
26 | )
27 |
28 | var (
29 | qykMatchFace = regexp.MustCompile(`\{face:(\d+)\}(.*)`)
30 | )
31 |
32 | // NewQYK ...
33 | func NewQYK(u, name string, banwords ...string) *QYK {
34 | return &QYK{u: u, n: name, b: banwords}
35 | }
36 |
37 | // String ...
38 | func (*QYK) String() string {
39 | return "青云客"
40 | }
41 |
42 | // Talk 取得带 CQ 码的回复消息
43 | func (q *QYK) Talk(_ int64, msg, nickname string) string {
44 | msg = strings.ReplaceAll(msg, nickname, q.n)
45 | u := fmt.Sprintf(q.u, url.QueryEscape(msg))
46 | data, err := web.RequestDataWith(web.NewDefaultClient(), u, "GET", "", web.RandUA(), nil)
47 | if err != nil {
48 | return "ERROR: " + err.Error()
49 | }
50 | replystr := gjson.Get(binary.BytesToString(data), "content").String()
51 | replystr = strings.ReplaceAll(replystr, "{face:", "[CQ:face,id=")
52 | replystr = strings.ReplaceAll(replystr, "{br}", "\n")
53 | replystr = strings.ReplaceAll(replystr, "}", "]")
54 | replystr = strings.ReplaceAll(replystr, q.n, nickname)
55 | for _, w := range q.b {
56 | if strings.Contains(replystr, w) {
57 | return "ERROR: 回复可能含有敏感内容"
58 | }
59 | }
60 | return replystr
61 | }
62 |
63 | // TalkPlain 取得回复消息
64 | func (q *QYK) TalkPlain(_ int64, msg, nickname string) string {
65 | msg = strings.ReplaceAll(msg, nickname, q.n)
66 |
67 | u := fmt.Sprintf(q.u, url.QueryEscape(msg))
68 | data, err := web.RequestDataWith(web.NewDefaultClient(), u, "GET", "", web.RandUA(), nil)
69 | if err != nil {
70 | return "ERROR: " + err.Error()
71 | }
72 | replystr := gjson.Get(binary.BytesToString(data), "content").String()
73 | replystr = qykMatchFace.ReplaceAllLiteralString(replystr, "")
74 | replystr = strings.ReplaceAll(replystr, "{br}", "\n")
75 | replystr = strings.ReplaceAll(replystr, q.n, nickname)
76 | for _, w := range q.b {
77 | if strings.Contains(replystr, w) {
78 | return "ERROR: 回复可能含有敏感内容"
79 | }
80 | }
81 | return replystr
82 | }
83 |
--------------------------------------------------------------------------------
/aireply/xiaoai.go:
--------------------------------------------------------------------------------
1 | package aireply
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "strings"
7 |
8 | "github.com/FloatTech/floatbox/binary"
9 | "github.com/FloatTech/floatbox/web"
10 | )
11 |
12 | // XiaoAi 小爱回复类
13 | type XiaoAi struct {
14 | u string
15 | n string
16 | b []string
17 | }
18 |
19 | const (
20 | // XiaoAiURL api地址
21 | XiaoAiURL = "http://81.70.100.130/api/xiaoai.php?n=text&msg=%v"
22 | // XiaoAiBotName ...
23 | XiaoAiBotName = "小爱"
24 | )
25 |
26 | // NewXiaoAi ...
27 | func NewXiaoAi(u, name string, banwords ...string) *XiaoAi {
28 | return &XiaoAi{u: u, n: name, b: banwords}
29 | }
30 |
31 | // String ...
32 | func (*XiaoAi) String() string {
33 | return "小爱"
34 | }
35 |
36 | // TalkPlain 取得回复消息
37 | func (x *XiaoAi) TalkPlain(_ int64, msg, nickname string) string {
38 | msg = strings.ReplaceAll(msg, nickname, x.n)
39 | u := fmt.Sprintf(x.u, url.QueryEscape(msg))
40 | replyMsg, err := web.GetData(u)
41 | if err != nil {
42 | return "ERROR: " + err.Error()
43 | }
44 | textReply := strings.ReplaceAll(binary.BytesToString(replyMsg), x.n, nickname)
45 | if textReply == "" {
46 | textReply = nickname + "听不懂你的话了, 能再说一遍吗"
47 | }
48 | textReply = strings.ReplaceAll(textReply, "小米智能助理", "电子宠物")
49 | for _, w := range x.b {
50 | if strings.Contains(textReply, w) {
51 | return "ERROR: 回复可能含有敏感内容"
52 | }
53 | }
54 | return textReply
55 | }
56 |
57 | // Talk 取得带 CQ 码的回复消息
58 | func (x *XiaoAi) Talk(_ int64, msg, nickname string) string {
59 | return x.TalkPlain(0, msg, nickname)
60 | }
61 |
--------------------------------------------------------------------------------
/ascii2d/ascii2d.go:
--------------------------------------------------------------------------------
1 | // Package ascii2d ascii2d搜图api
2 | package ascii2d
3 |
4 | import (
5 | "errors"
6 | "net/http"
7 | "net/url"
8 | "strings"
9 |
10 | "github.com/FloatTech/floatbox/web"
11 | xpath "github.com/antchfx/htmlquery"
12 | )
13 |
14 | // Result ...
15 | type Result struct {
16 | Info string // Info 图片分辨率 格式 大小信息
17 | Link string // Link 图片链接
18 | Name string // Name 图片名
19 | Author string // Author 作者链接
20 | AuthNm string // AuthNm 作者名
21 | Thumb string // Thumb 缩略图链接
22 | Type string // Type pixiv / twitter ...
23 | }
24 |
25 | // ASCII2d ...
26 | func ASCII2d(image string) (r []*Result, err error) {
27 | const api = "https://ascii2d.net/search/uri"
28 | client := web.NewTLS12Client()
29 | // 包装请求参数
30 | data := url.Values{}
31 | data.Set("uri", image) // 图片链接
32 | fromData := strings.NewReader(data.Encode())
33 |
34 | // 网络请求
35 | reqcolor, _ := http.NewRequest("POST", api, fromData)
36 | reqcolor.Header.Set("Content-Type", "application/x-www-form-urlencoded")
37 | reqcolor.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0")
38 | respcolor, err := client.Do(reqcolor)
39 | if err != nil {
40 | return nil, err
41 | }
42 | defer respcolor.Body.Close()
43 | // 色合检索改变到特征检索
44 | var bovw = strings.ReplaceAll(respcolor.Request.URL.String(), "color", "bovw")
45 | req, _ := http.NewRequest("GET", bovw, nil)
46 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0")
47 | resp, err := client.Do(req)
48 | if err != nil {
49 | return nil, err
50 | }
51 | defer resp.Body.Close()
52 | // 解析XPATH
53 | doc, err := xpath.Parse(resp.Body)
54 | if err != nil {
55 | return nil, err
56 | }
57 | // 取出每个返回的结果
58 | list := xpath.Find(doc, `//div[@class="row item-box"]`)
59 | if len(list) == 0 {
60 | return nil, errors.New("ascii2d not found")
61 | }
62 | r = make([]*Result, 0, len(list))
63 | // 遍历结果
64 | for _, n := range list {
65 | linkPath := xpath.FindOne(n, `//div[2]/div[3]/h6/a[1]`)
66 | authPath := xpath.FindOne(n, `//div[2]/div[3]/h6/a[2]`)
67 | picPath := xpath.FindOne(n, `//div[1]/img`)
68 | if linkPath != nil && authPath != nil && picPath != nil {
69 | r = append(r, &Result{
70 | Info: xpath.InnerText(xpath.FindOne(n, `//div[2]/small`)),
71 | Link: xpath.SelectAttr(linkPath, "href"),
72 | Name: xpath.InnerText(linkPath),
73 | Author: xpath.SelectAttr(authPath, "href"),
74 | AuthNm: xpath.InnerText(authPath),
75 | Thumb: "https://ascii2d.net" + xpath.SelectAttr(picPath, "src"),
76 | Type: strings.Trim(xpath.InnerText(xpath.FindOne(n, `//div[2]/div[3]/h6/small`)), "\n"),
77 | })
78 | }
79 | }
80 | return
81 | }
82 |
--------------------------------------------------------------------------------
/ascii2d/ascii2d_test.go:
--------------------------------------------------------------------------------
1 | package ascii2d
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/json"
6 | "os"
7 | "testing"
8 | )
9 |
10 | func TestSearch(t *testing.T) {
11 | r, err := ASCII2d("https://gchat.qpic.cn/gchatpic_new//--05F47960F2546E874F515A403FD174DF/0?term=3")
12 | if err != nil {
13 | t.Fatal(err)
14 | }
15 | res, err := os.ReadFile("result.txt")
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | b, err := json.Marshal(&r)
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 | m1 := md5.Sum(res)
24 | m2 := md5.Sum(b)
25 | if m1 != m2 {
26 | f, err := os.Create("result.txt")
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 | _, _ = f.Write(b)
31 | t.Fatal("new result has been written to result.txt")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ascii2d/result.txt:
--------------------------------------------------------------------------------
1 | [{"Info":"1000x1000 PNG 357.8KB","Link":"http://seiga.nicovideo.jp/seiga/im3973555","Name":"ちらっと港湾棲姫さん","Author":"http://seiga.nicovideo.jp/user/illust/14115112","AuthNm":"ナギネコ","Thumb":"https://ascii2d.net/thumbnail/3/6/2/7/3627bafa384b532427b264ca3afa2c25.jpg","Type":"ニコニコ静画"},{"Info":"1725x1988 JPEG 913.1KB","Link":"https://www.pixiv.net/artworks/61999890","Name":"多々良小傘 ワンドロ","Author":"https://www.pixiv.net/users/2657881","AuthNm":"碧闢@紅楼夢C-30a","Thumb":"https://ascii2d.net/thumbnail/f/8/f/c/f8fcf474758e3ded91b28a1c6f13ddb2.jpg","Type":"pixiv"},{"Info":"1464x1181 JPEG 175.7KB","Link":"https://twitter.com/kano09x/status/1319304574201520129","Name":"2020.10.23","Author":"https://twitter.com/intent/user?user_id=285160442","AuthNm":"kano09x","Thumb":"https://ascii2d.net/thumbnail/2/f/8/9/2f89245611361187acd5ebddb84e5cee.jpg","Type":"twitter"},{"Info":"960x720 JPEG 361.6KB","Link":"https://www.pixiv.net/artworks/82317126","Name":"大魔王の娘ちゃん","Author":"https://www.pixiv.net/users/1423658","AuthNm":"ミズカネ","Thumb":"https://ascii2d.net/thumbnail/3/b/a/8/3ba815b8eea34abf5db9145d98b99788.jpg","Type":"pixiv"},{"Info":"1254x1701 JPEG 222.8KB","Link":"https://twitter.com/kirikine/status/1433473008153206791","Name":"2021.09.03","Author":"https://twitter.com/intent/user?user_id=973233221025476609","AuthNm":"kirikine","Thumb":"https://ascii2d.net/thumbnail/9/9/e/d/99eda63517af4ca24ba908b1192c58b2.jpg","Type":"twitter"},{"Info":"1464x1181 PNG 1781.1KB","Link":"https://www.pixiv.net/artworks/89021523","Name":"Vtuberさんまとめ7","Author":"https://www.pixiv.net/users/1982820","AuthNm":"かの","Thumb":"https://ascii2d.net/thumbnail/e/2/d/4/e2d4dddc1e34c49c6c08d043e45ecb9d.jpg","Type":"pixiv"},{"Info":"450x326 JPEG 31.4KB","Link":"https://www.pixiv.net/artworks/61807822","Name":"I’ll Kill Him","Author":"https://www.pixiv.net/users/465084","AuthNm":"若林稔弥","Thumb":"https://ascii2d.net/thumbnail/9/1/3/d/913de2bbe576d64313a4f3a226614b2d.jpg","Type":"pixiv"},{"Info":"2048x1536 JPEG 278.7KB","Link":"https://twitter.com/shift0808/status/1128958226517544960","Name":"2019.05.16","Author":"https://twitter.com/intent/user?user_id=279542814","AuthNm":"shift0808","Thumb":"https://ascii2d.net/thumbnail/a/d/1/2/ad12407d167c28310678b8bb225490c3.jpg","Type":"twitter"},{"Info":"600x450 JPEG 86.2KB","Link":"https://www.pixiv.net/artworks/68660723","Name":"うんうんっ♪","Author":"https://www.pixiv.net/users/99019","AuthNm":"ロード","Thumb":"https://ascii2d.net/thumbnail/e/5/d/e/e5de80b082655a99e9621e86a7e6aafe.jpg","Type":"pixiv"},{"Info":"1920x1080 JPEG 1260.6KB","Link":"https://www.pixiv.net/artworks/71657895","Name":"おしぐさつえい","Author":"https://www.pixiv.net/users/119383","AuthNm":"えろいいきもの","Thumb":"https://ascii2d.net/thumbnail/9/8/2/7/9827b8e868a4d6948f8b450808fc10f1.jpg","Type":"pixiv"},{"Info":"1000x1200 JPEG 89.0KB","Link":"https://twitter.com/_a_ker__/status/1179729639838666753","Name":"2019.10.03","Author":"https://twitter.com/intent/user?user_id=1076334458830151682","AuthNm":"_a_ker__","Thumb":"https://ascii2d.net/thumbnail/8/4/3/6/8436fd38882d6c31b56754173c06781a.jpg","Type":"twitter"},{"Info":"1920x1080 JPEG 507.6KB","Link":"https://twitter.com/NAMCOOo/status/1259500543174569984","Name":"2020.05.11","Author":"https://twitter.com/intent/user?user_id=626301711","AuthNm":"NAMCOOo","Thumb":"https://ascii2d.net/thumbnail/8/b/a/7/8ba7b95a2c84424b7c8628e9b93d8c97.jpg","Type":"twitter"},{"Info":"400x225 JPEG 19.2KB","Link":"https://www.pixiv.net/artworks/71666203","Name":"416","Author":"https://www.pixiv.net/users/268181","AuthNm":"hasmk2(沙鼠)","Thumb":"https://ascii2d.net/thumbnail/7/f/5/0/7f50774a69d0583ffe13540f314ea964.jpg","Type":"pixiv"},{"Info":"1011x756 JPEG 86.4KB","Link":"https://twitter.com/suna_chika/status/1038078662032089088","Name":"2018.09.07","Author":"https://twitter.com/intent/user?user_id=284523660","AuthNm":"suna_chika","Thumb":"https://ascii2d.net/thumbnail/c/0/3/4/c03455e48fb1d90dfd064021e6dd9342.jpg","Type":"twitter"},{"Info":"600x600 JPEG 88.6KB","Link":"https://www.pixiv.net/artworks/61893074","Name":"あじみ(vore注意)","Author":"https://www.pixiv.net/users/3123230","AuthNm":"gizaneko","Thumb":"https://ascii2d.net/thumbnail/3/4/4/6/34466e7a379b5d49e1e7cba7e3d356d3.jpg","Type":"pixiv"},{"Info":"1536x2048 JPEG 507.0KB","Link":"https://twitter.com/makoton0412/status/782956757135953920","Name":"2016.10.03","Author":"https://twitter.com/intent/user?user_id=201262703","AuthNm":"makoton0412","Thumb":"https://ascii2d.net/thumbnail/0/5/9/f/059f2e3306945db532ae99525546c5e1.jpg","Type":"twitter"},{"Info":"800x398 JPEG 171.0KB","Link":"http://seiga.nicovideo.jp/seiga/im1618894","Name":"さむねよう","Author":"http://seiga.nicovideo.jp/user/illust/20256302","AuthNm":"RYUN","Thumb":"https://ascii2d.net/thumbnail/8/b/5/2/8b52dd116bbae6080ba6d4d99f237382.jpg","Type":"ニコニコ静画"},{"Info":"1280x720 PNG 330.0KB","Link":"https://www.pixiv.net/artworks/88918640","Name":"犯罪結社『アーミス』襲撃 2-9.25 - 裏舞台にて -","Author":"https://www.pixiv.net/users/21772318","AuthNm":"US-C-ELSE","Thumb":"https://ascii2d.net/thumbnail/8/2/d/2/82d25956a4ad0341cdcedc98cab20da3.jpg","Type":"pixiv"},{"Info":"679x901 JPEG 80.8KB","Link":"https://twitter.com/sukage1/status/837578491021316100","Name":"2017.03.03","Author":"https://twitter.com/intent/user?user_id=102534527","AuthNm":"sukage1","Thumb":"https://ascii2d.net/thumbnail/9/3/e/1/93e1fbe1106295972246b13a32ec0bf0.jpg","Type":"twitter"},{"Info":"1200x1200 JPEG 183.2KB","Link":"https://twitter.com/13_ikari/status/1067777623206453248","Name":"2018.11.28","Author":"https://twitter.com/intent/user?user_id=2370073645","AuthNm":"13_ikari","Thumb":"https://ascii2d.net/thumbnail/5/0/7/6/507661b3e61a1e91b4899a99774eadcf.jpg","Type":"twitter"}]
--------------------------------------------------------------------------------
/bilibili/.gitignore:
--------------------------------------------------------------------------------
1 | *.json
--------------------------------------------------------------------------------
/bilibili/api.go:
--------------------------------------------------------------------------------
1 | // Package bilibili b站相关API
2 | package bilibili
3 |
4 | import (
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "net/http"
9 | "strconv"
10 |
11 | "github.com/FloatTech/floatbox/binary"
12 | "github.com/FloatTech/floatbox/web"
13 | "github.com/tidwall/gjson"
14 | )
15 |
16 | // ErrAPINeedCookie ...
17 | var ErrAPINeedCookie = errors.New("api need cookie")
18 |
19 | // SearchUser 查找b站用户
20 | func SearchUser(cookiecfg *CookieConfig, keyword string) (r []SearchResult, err error) {
21 | client := &http.Client{}
22 | req, err := http.NewRequest("GET", fmt.Sprintf(SearchUserURL, keyword), nil)
23 | if err != nil {
24 | return
25 | }
26 | if cookiecfg != nil {
27 | cookie := ""
28 | cookie, err = cookiecfg.Load()
29 | if err != nil {
30 | return
31 | }
32 | req.Header.Add("cookie", cookie)
33 | }
34 | res, err := client.Do(req)
35 | if err != nil {
36 | return
37 | }
38 | defer res.Body.Close()
39 | if res.StatusCode != http.StatusOK {
40 | err = errors.New("status code: " + strconv.Itoa(res.StatusCode))
41 | return
42 | }
43 | var sd SearchData
44 | err = json.NewDecoder(res.Body).Decode(&sd)
45 | if err != nil {
46 | return
47 | }
48 | r = sd.Data.Result
49 | return
50 | }
51 |
52 | // GetVtbDetail 查找vtb信息
53 | func GetVtbDetail(uid string) (result VtbDetail, err error) {
54 | resp, err := http.Get(fmt.Sprintf(VtbDetailURL, uid))
55 | if err != nil {
56 | return
57 | }
58 | defer resp.Body.Close()
59 | err = json.NewDecoder(resp.Body).Decode(&result)
60 | return
61 | }
62 |
63 | // LoadCardDetail 加载卡片
64 | func LoadCardDetail(str string) (card Card, err error) {
65 | err = json.Unmarshal(binary.StringToBytes(str), &card)
66 | return
67 | }
68 |
69 | // LoadDynamicDetail 加载动态卡片
70 | func LoadDynamicDetail(str string) (card DynamicCard, err error) {
71 | err = json.Unmarshal(binary.StringToBytes(str), &card)
72 | return
73 | }
74 |
75 | // GetDynamicDetail 用动态id查动态信息
76 | func GetDynamicDetail(cookiecfg *CookieConfig, dynamicIDStr string) (card DynamicCard, err error) {
77 | var data []byte
78 | data, err = web.RequestDataWithHeaders(web.NewDefaultClient(), fmt.Sprintf(DynamicDetailURL, dynamicIDStr), "GET", func(req *http.Request) error {
79 | if cookiecfg != nil {
80 | cookie := ""
81 | cookie, err = cookiecfg.Load()
82 | if err != nil {
83 | return err
84 | }
85 | req.Header.Add("cookie", cookie)
86 | }
87 | return nil
88 | }, nil)
89 | if err != nil {
90 | return
91 | }
92 | err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data.card").Raw), &card)
93 | return
94 | }
95 |
96 | // GetMemberCard 获取b站个人详情
97 | func GetMemberCard(uid any) (result MemberCard, err error) {
98 | data, err := web.RequestDataWith(web.NewDefaultClient(), fmt.Sprintf(MemberCardURL, uid), "GET", "", web.RandUA(), nil)
99 | if err != nil {
100 | return
101 | }
102 | err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data.card").Raw), &result)
103 | return
104 | }
105 |
106 | // GetMedalWall 用b站uid获得牌子
107 | func GetMedalWall(cookiecfg *CookieConfig, uid string) (result []Medal, err error) {
108 | client := &http.Client{}
109 | req, err := http.NewRequest("GET", fmt.Sprintf(MedalWallURL, uid), nil)
110 | if err != nil {
111 | return
112 | }
113 | if cookiecfg != nil {
114 | cookie := ""
115 | cookie, err = cookiecfg.Load()
116 | if err != nil {
117 | return
118 | }
119 | req.Header.Add("cookie", cookie)
120 | }
121 | res, err := client.Do(req)
122 | if err != nil {
123 | return
124 | }
125 | defer res.Body.Close()
126 | var md MedalData
127 | err = json.NewDecoder(res.Body).Decode(&md)
128 | if err != nil {
129 | return
130 | }
131 | if md.Code == -101 {
132 | err = ErrAPINeedCookie
133 | return
134 | }
135 | if md.Code != 0 {
136 | err = errors.New(md.Message)
137 | }
138 | result = md.Data.List
139 | return
140 | }
141 |
142 | // GetAllGuard 查询mid的上舰信息
143 | func GetAllGuard(mid string) (guardUser GuardUser, err error) {
144 | var data []byte
145 | data, err = web.GetData(AllGuardURL)
146 | if err != nil {
147 | return
148 | }
149 | m := gjson.ParseBytes(data).Get("@this").Map()
150 | err = json.Unmarshal(binary.StringToBytes(m[mid].String()), &guardUser)
151 | if err != nil {
152 | return
153 | }
154 | return
155 | }
156 |
157 | // GetArticleInfo 用id查专栏信息
158 | func GetArticleInfo(id string) (card Card, err error) {
159 | var data []byte
160 | data, err = web.GetData(fmt.Sprintf(ArticleInfoURL, id))
161 | if err != nil {
162 | return
163 | }
164 | err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data").Raw), &card)
165 | return
166 | }
167 |
168 | // GetLiveRoomInfo 用直播间id查直播间信息
169 | func GetLiveRoomInfo(roomID string) (card RoomCard, err error) {
170 | var data []byte
171 | data, err = web.GetData(fmt.Sprintf(ArticleInfoURL, roomID))
172 | if err != nil {
173 | return
174 | }
175 | err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data").Raw), &card)
176 | return
177 | }
178 |
179 | // GetVideoInfo 用av或bv查视频信息
180 | func GetVideoInfo(id string) (card Card, err error) {
181 | var data []byte
182 | _, err = strconv.Atoi(id)
183 | if err == nil {
184 | data, err = web.GetData(fmt.Sprintf(VideoInfoURL, id, ""))
185 | } else {
186 | data, err = web.GetData(fmt.Sprintf(VideoInfoURL, "", id))
187 | }
188 | if err != nil {
189 | return
190 | }
191 | err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data").Raw), &card)
192 | return
193 | }
194 |
195 | // GetVideoSummary 用av或bv查看AI视频总结
196 | func GetVideoSummary(cookiecfg *CookieConfig, id string) (videoSummary VideoSummary, err error) {
197 | var (
198 | data []byte
199 | card Card
200 | )
201 | _, err = strconv.Atoi(id)
202 | if err == nil {
203 | data, err = web.GetData(fmt.Sprintf(VideoInfoURL, id, ""))
204 | } else {
205 | data, err = web.GetData(fmt.Sprintf(VideoInfoURL, "", id))
206 | }
207 | if err != nil {
208 | return
209 | }
210 | err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data").Raw), &card)
211 | if err != nil {
212 | return
213 | }
214 | data, err = web.RequestDataWithHeaders(web.NewDefaultClient(), SignURL(fmt.Sprintf(VideoSummaryURL, card.BvID, card.CID, card.Owner.Mid)), "GET", func(req *http.Request) error {
215 | if cookiecfg != nil {
216 | cookie := ""
217 | cookie, err = cookiecfg.Load()
218 | if err != nil {
219 | return err
220 | }
221 | req.Header.Add("cookie", cookie)
222 | }
223 | req.Header.Set("User-Agent", web.RandUA())
224 | return nil
225 | }, nil)
226 | if err != nil {
227 | return
228 | }
229 | err = json.Unmarshal(data, &videoSummary)
230 | return
231 | }
232 |
--------------------------------------------------------------------------------
/bilibili/api_test.go:
--------------------------------------------------------------------------------
1 | package bilibili
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestGetAllGuard(t *testing.T) {
8 | guardUser, err := GetAllGuard("628537")
9 | if err != nil {
10 | t.Fatal(err)
11 | }
12 | t.Logf("%+v\n", guardUser)
13 | }
14 |
15 | func TestGetDynamicDetail(t *testing.T) {
16 | cfg := NewCookieConfig("config.json")
17 | detail, err := GetDynamicDetail(cfg, "851252197280710664")
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 | t.Logf("%+v\n", detail)
22 | }
23 |
--------------------------------------------------------------------------------
/bilibili/types.go:
--------------------------------------------------------------------------------
1 | package bilibili
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "os"
7 |
8 | "github.com/FloatTech/floatbox/file"
9 | )
10 |
11 | const (
12 | // TURL bilibili动态前缀
13 | TURL = "https://t.bilibili.com/"
14 | // LiveURL bilibili直播前缀
15 | LiveURL = "https://live.bilibili.com/"
16 | // DynamicDetailURL 当前动态信息,一个card
17 | DynamicDetailURL = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=%v"
18 | // MemberCardURL 个人信息
19 | MemberCardURL = "https://api.bilibili.com/x/web-interface/card?mid=%v"
20 | // ArticleInfoURL 查看专栏信息
21 | ArticleInfoURL = "https://api.bilibili.com/x/article/viewinfo?id=%v"
22 | // CVURL b站专栏前缀
23 | CVURL = "https://www.bilibili.com/read/cv"
24 | // LiveRoomInfoURL 查看直播间信息
25 | LiveRoomInfoURL = "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=%v"
26 | // LURL b站直播间前缀
27 | LURL = "https://live.bilibili.com/"
28 | // VideoInfoURL 查看视频信息
29 | VideoInfoURL = "https://api.bilibili.com/x/web-interface/view?aid=%v&bvid=%v"
30 | // VURL 视频网址前缀
31 | VURL = "https://www.bilibili.com/video/"
32 | // SearchUserURL 查找b站用户
33 | SearchUserURL = "http://api.bilibili.com/x/web-interface/search/type?search_type=bili_user&keyword=%v"
34 | // VtbDetailURL 查找vtb信息
35 | VtbDetailURL = "https://api.vtbs.moe/v1/detail/%v"
36 | // MedalWallURL 查找牌子
37 | MedalWallURL = "https://api.live.bilibili.com/xlive/web-ucenter/user/MedalWall?target_id=%v"
38 | // SpaceHistoryURL 历史动态信息,一共12个card
39 | SpaceHistoryURL = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid=%v&offset_dynamic_id=%v&need_top=0"
40 | // LiveListURL 获得直播状态
41 | LiveListURL = "https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids"
42 | // DanmakuAPI 弹幕网获得用户弹幕api
43 | DanmakuAPI = "https://ukamnads.icu/api/v2/user?uId=%v&pageNum=%v&pageSize=5&target=-1&useEmoji=true"
44 | // DanmakuURL 弹幕网链接
45 | DanmakuURL = "https://danmakus.com/user/%v"
46 | // AllGuardURL 查询所有舰长,提督,总督
47 | AllGuardURL = "https://api.vtbs.moe/v1/guard/all"
48 | // VideoSummaryURL AI视频总结
49 | VideoSummaryURL = "https://api.bilibili.com/x/web-interface/view/conclusion/get?bvid=%v&cid=%v&up_mid=%v"
50 | // VideoDownloadURL 视频下载
51 | VideoDownloadURL = "https://api.bilibili.com/x/player/playurl?bvid=%v&cid=%v&qn=80&fnval=1&fnver=0&fourk=1"
52 | // OnlineTotalURL 在线人数
53 | OnlineTotalURL = "https://api.bilibili.com/x/player/online/total?bvid=%v&cid=%v"
54 | // NavURL 导航URL
55 | NavURL = "https://api.bilibili.com/x/web-interface/nav"
56 | )
57 |
58 | // DynamicCard 总动态结构体,包括desc,card
59 | type DynamicCard struct {
60 | Desc Desc `json:"desc"`
61 | Card string `json:"card"`
62 | Extension struct {
63 | VoteCfg struct {
64 | VoteID int `json:"vote_id"`
65 | Desc string `json:"desc"`
66 | JoinNum int `json:"join_num"`
67 | } `json:"vote_cfg"`
68 | Vote string `json:"vote"`
69 | } `json:"extension"`
70 | }
71 |
72 | // Card 卡片结构体
73 | type Card struct {
74 | Item struct {
75 | Content string `json:"content"`
76 | UploadTime int `json:"upload_time"`
77 | Description string `json:"description"`
78 | Pictures []struct {
79 | ImgSrc string `json:"img_src"`
80 | } `json:"pictures"`
81 | Timestamp int `json:"timestamp"`
82 | Cover struct {
83 | Default string `json:"default"`
84 | } `json:"cover"`
85 | OrigType int `json:"orig_type"`
86 | } `json:"item"`
87 | AID any `json:"aid"`
88 | BvID any `json:"bvid"`
89 | Dynamic any `json:"dynamic"`
90 | CID int `json:"cid"`
91 | Pic string `json:"pic"`
92 | Title string `json:"title"`
93 | ID int `json:"id"`
94 | Summary string `json:"summary"`
95 | ImageUrls []string `json:"image_urls"`
96 | OriginImageUrls []string `json:"origin_image_urls"`
97 | Sketch struct {
98 | Title string `json:"title"`
99 | DescText string `json:"desc_text"`
100 | CoverURL string `json:"cover_url"`
101 | TargetURL string `json:"target_url"`
102 | } `json:"sketch"`
103 | Stat struct {
104 | Aid int `json:"aid"`
105 | View int `json:"view"`
106 | Danmaku int `json:"danmaku"`
107 | Reply int `json:"reply"`
108 | Favorite int `json:"favorite"`
109 | Coin int `json:"coin"`
110 | Share int `json:"share"`
111 | Like int `json:"like"`
112 | } `json:"stat"`
113 | Stats struct {
114 | Aid int `json:"aid"`
115 | View int `json:"view"`
116 | Danmaku int `json:"danmaku"`
117 | Reply int `json:"reply"`
118 | Favorite int `json:"favorite"`
119 | Coin int `json:"coin"`
120 | Share int `json:"share"`
121 | Like int `json:"like"`
122 | } `json:"stats"`
123 | Owner struct {
124 | Name string `json:"name"`
125 | Pubdate int `json:"pubdate"`
126 | Mid int `json:"mid"`
127 | } `json:"owner"`
128 | Cover string `json:"cover"`
129 | ShortID any `json:"short_id"`
130 | LivePlayInfo struct {
131 | ParentAreaName string `json:"parent_area_name"`
132 | AreaName string `json:"area_name"`
133 | Cover string `json:"cover"`
134 | Link string `json:"link"`
135 | Online int `json:"online"`
136 | RoomID int `json:"room_id"`
137 | LiveStatus int `json:"live_status"`
138 | WatchedShow string `json:"watched_show"`
139 | Title string `json:"title"`
140 | } `json:"live_play_info"`
141 | Intro string `json:"intro"`
142 | Schema string `json:"schema"`
143 | Author any `json:"author"`
144 | AuthorName string `json:"author_name"`
145 | PlayCnt int `json:"play_cnt"`
146 | ReplyCnt int `json:"reply_cnt"`
147 | TypeInfo string `json:"type_info"`
148 | User struct {
149 | Name string `json:"name"`
150 | Uname string `json:"uname"`
151 | } `json:"user"`
152 | Desc string `json:"desc"`
153 | ShareSubtitle string `json:"share_subtitle"`
154 | ShortLink string `json:"short_link"`
155 | PublishTime int `json:"publish_time"`
156 | BannerURL string `json:"banner_url"`
157 | Ctime int `json:"ctime"`
158 | Vest struct {
159 | Content string `json:"content"`
160 | } `json:"vest"`
161 | Upper string `json:"upper"`
162 | Origin string `json:"origin"`
163 | Pubdate int `json:"pubdate"`
164 | Rights struct {
165 | IsCooperation int `json:"is_cooperation"`
166 | } `json:"rights"`
167 | Staff []struct {
168 | Title string `json:"title"`
169 | Name string `json:"name"`
170 | Follower int `json:"follower"`
171 | } `json:"staff"`
172 | }
173 |
174 | // Desc 描述结构体
175 | type Desc struct {
176 | Type int `json:"type"`
177 | DynamicIDStr string `json:"dynamic_id_str"`
178 | OrigType int `json:"orig_type"`
179 | Timestamp int `json:"timestamp"`
180 | Origin struct {
181 | DynamicIDStr string `json:"dynamic_id_str"`
182 | } `json:"origin"`
183 | UserProfile struct {
184 | Info struct {
185 | Uname string `json:"uname"`
186 | } `json:"info"`
187 | } `json:"user_profile"`
188 | }
189 |
190 | // Vote 投票结构体
191 | type Vote struct {
192 | ChoiceCnt int `json:"choice_cnt"`
193 | Desc string `json:"desc"`
194 | Endtime int `json:"endtime"`
195 | JoinNum int `json:"join_num"`
196 | Options []struct {
197 | Idx int `json:"idx"`
198 | Desc string `json:"desc"`
199 | ImgURL string `json:"img_url"`
200 | } `json:"options"`
201 | }
202 |
203 | // MemberCard 个人信息卡片
204 | type MemberCard struct {
205 | Mid string `json:"mid"`
206 | Name string `json:"name"`
207 | Sex string `json:"sex"`
208 | Face string `json:"face"`
209 | Coins float64 `json:"coins"`
210 | Regtime int64 `json:"regtime"`
211 | Birthday string `json:"birthday"`
212 | Sign string `json:"sign"`
213 | Attentions []int64 `json:"attentions"`
214 | Fans int `json:"fans"`
215 | Friend int `json:"friend"`
216 | Attention int `json:"attention"`
217 | LevelInfo struct {
218 | CurrentLevel int `json:"current_level"`
219 | } `json:"level_info"`
220 | }
221 |
222 | // RoomCard 直播间卡片
223 | type RoomCard struct {
224 | RoomInfo struct {
225 | RoomID int `json:"room_id"`
226 | ShortID int `json:"short_id"`
227 | Title string `json:"title"`
228 | LiveStatus int `json:"live_status"`
229 | AreaName string `json:"area_name"`
230 | ParentAreaName string `json:"parent_area_name"`
231 | Keyframe string `json:"keyframe"`
232 | Online int `json:"online"`
233 | } `json:"room_info"`
234 | AnchorInfo struct {
235 | BaseInfo struct {
236 | Uname string `json:"uname"`
237 | } `json:"base_info"`
238 | } `json:"anchor_info"`
239 | }
240 |
241 | // SearchData 查找b站用户总结构体
242 | type SearchData struct {
243 | Data struct {
244 | NumResults int `json:"numResults"`
245 | Result []SearchResult `json:"result"`
246 | } `json:"data"`
247 | }
248 |
249 | // SearchResult 查找b站用户结果
250 | type SearchResult struct {
251 | Mid int64 `json:"mid"`
252 | Uname string `json:"uname"`
253 | Gender int64 `json:"gender"`
254 | Usign string `json:"usign"`
255 | Level int64 `json:"level"`
256 | }
257 |
258 | // MedalData 牌子接口返回结构体
259 | type MedalData struct {
260 | Code int `json:"code"`
261 | Message string `json:"message"`
262 | Data struct {
263 | List []Medal `json:"list"`
264 | } `json:"data"`
265 | }
266 |
267 | // MedalInfo b站牌子信息
268 | type MedalInfo struct {
269 | Mid int64 `json:"target_id"`
270 | MedalName string `json:"medal_name"`
271 | Level int64 `json:"level"`
272 | MedalColorStart int64 `json:"medal_color_start"`
273 | MedalColorEnd int64 `json:"medal_color_end"`
274 | MedalColorBorder int64 `json:"medal_color_border"`
275 | }
276 |
277 | // Medal ...
278 | type Medal struct {
279 | Uname string `json:"target_name"`
280 | MedalInfo `json:"medal_info"`
281 | }
282 |
283 | // MedalSorter ...
284 | type MedalSorter []Medal
285 |
286 | // Len ...
287 | func (m MedalSorter) Len() int {
288 | return len(m)
289 | }
290 |
291 | // Swap ...
292 | func (m MedalSorter) Swap(i, j int) {
293 | m[i], m[j] = m[j], m[i]
294 | }
295 |
296 | // Less ...
297 | func (m MedalSorter) Less(i, j int) bool {
298 | return m[i].Level > m[j].Level
299 | }
300 |
301 | // VtbDetail vtb信息
302 | type VtbDetail struct {
303 | Mid int `json:"mid"`
304 | Uname string `json:"uname"`
305 | Video int `json:"video"`
306 | Roomid int `json:"roomid"`
307 | Rise int `json:"rise"`
308 | Follower int `json:"follower"`
309 | GuardNum int `json:"guardNum"`
310 | AreaRank int `json:"areaRank"`
311 | }
312 |
313 | // GuardUser dd用户
314 | type GuardUser struct {
315 | Uname string `json:"uname"`
316 | Face string `json:"face"`
317 | Mid int64 `json:"mid"`
318 | Dd [][]int64 `json:"dd"`
319 | }
320 |
321 | // Danmakusuki 弹幕网结构体
322 | type Danmakusuki struct {
323 | Code int `json:"code"`
324 | Message string `json:"message"`
325 | Data struct {
326 | Total int `json:"total"`
327 | PageNum int `json:"pageNum"`
328 | PageSize int `json:"pageSize"`
329 | HasMore bool `json:"hasMore"`
330 | Data struct {
331 | Records []struct {
332 | Channel struct {
333 | UID int `json:"uId"`
334 | UName string `json:"uName"`
335 | RoomID int `json:"roomId"`
336 | FaceURL string `json:"faceUrl"`
337 | FrameURL string `json:"frameUrl"`
338 | IsLiving bool `json:"isLiving"`
339 | Title string `json:"title"`
340 | Tags []interface{} `json:"tags"`
341 | LastLiveDate int64 `json:"lastLiveDate"`
342 | LastLiveDanmakuCount int `json:"lastLiveDanmakuCount"`
343 | TotalDanmakuCount int `json:"totalDanmakuCount"`
344 | TotalIncome float64 `json:"totalIncome"`
345 | TotalLiveCount int `json:"totalLiveCount"`
346 | TotalLiveSecond int `json:"totalLiveSecond"`
347 | AddDate string `json:"addDate"`
348 | CommentCount int `json:"commentCount"`
349 | LastLiveIncome float64 `json:"lastLiveIncome"`
350 | } `json:"channel"`
351 | Live struct {
352 | LiveID string `json:"liveId"`
353 | IsFinish bool `json:"isFinish"`
354 | IsFull bool `json:"isFull"`
355 | ParentArea string `json:"parentArea"`
356 | Area string `json:"area"`
357 | CoverURL string `json:"coverUrl"`
358 | DanmakusCount int `json:"danmakusCount"`
359 | StartDate int64 `json:"startDate"`
360 | StopDate int64 `json:"stopDate"`
361 | Title string `json:"title"`
362 | TotalIncome float64 `json:"totalIncome"`
363 | WatchCount int `json:"watchCount"`
364 | LikeCount int `json:"likeCount"`
365 | PayCount int `json:"payCount"`
366 | InteractionCount int `json:"interactionCount"`
367 | MaxOnlineCount int `json:"maxOnlineCount"`
368 | } `json:"live"`
369 | Danmakus []struct {
370 | UID int `json:"uId"`
371 | UName string `json:"uName"`
372 | Type int64 `json:"type"`
373 | SendDate int64 `json:"sendDate"`
374 | Message string `json:"message"`
375 | Price float64 `json:"price"`
376 | } `json:"danmakus"`
377 | } `json:"records"`
378 | } `json:"data"`
379 | } `json:"data"`
380 | }
381 |
382 | // VideoSummary AI视频总结结构体
383 | type VideoSummary struct {
384 | Code int `json:"code"`
385 | Message string `json:"message"`
386 | TTL int `json:"ttl"`
387 | Data struct {
388 | Code int `json:"code"`
389 | ModelResult struct {
390 | ResultType int `json:"result_type"`
391 | Summary string `json:"summary"`
392 | Outline []struct {
393 | Title string `json:"title"`
394 | PartOutline []struct {
395 | Timestamp int `json:"timestamp"`
396 | Content string `json:"content"`
397 | } `json:"part_outline"`
398 | Timestamp int `json:"timestamp"`
399 | } `json:"outline"`
400 | } `json:"model_result"`
401 | Stid string `json:"stid"`
402 | Status int `json:"status"`
403 | LikeNum int `json:"like_num"`
404 | DislikeNum int `json:"dislike_num"`
405 | } `json:"data"`
406 | }
407 |
408 | // VideoDownload 视频下载结构体(mp4格式)
409 | type VideoDownload struct {
410 | Code int `json:"code"`
411 | Message string `json:"message"`
412 | TTL int `json:"ttl"`
413 | Data struct {
414 | From string `json:"from"`
415 | Result string `json:"result"`
416 | Message string `json:"message"`
417 | Quality int `json:"quality"`
418 | Format string `json:"format"`
419 | Timelength int `json:"timelength"`
420 | AcceptFormat string `json:"accept_format"`
421 | AcceptDescription []string `json:"accept_description"`
422 | AcceptQuality []int `json:"accept_quality"`
423 | VideoCodecid int `json:"video_codecid"`
424 | SeekParam string `json:"seek_param"`
425 | SeekType string `json:"seek_type"`
426 | Durl []struct {
427 | Order int `json:"order"`
428 | Length int `json:"length"`
429 | Size int `json:"size"`
430 | Ahead string `json:"ahead"`
431 | Vhead string `json:"vhead"`
432 | URL string `json:"url"`
433 | BackupURL []string `json:"backup_url"`
434 | } `json:"durl"`
435 | SupportFormats []struct {
436 | Quality int `json:"quality"`
437 | Format string `json:"format"`
438 | NewDescription string `json:"new_description"`
439 | DisplayDesc string `json:"display_desc"`
440 | Superscript string `json:"superscript"`
441 | Codecs interface{} `json:"codecs"`
442 | } `json:"support_formats"`
443 | HighFormat interface{} `json:"high_format"`
444 | LastPlayTime int `json:"last_play_time"`
445 | LastPlayCid int `json:"last_play_cid"`
446 | } `json:"data"`
447 | }
448 |
449 | // OnlineTotal 在线人数结构体
450 | type OnlineTotal struct {
451 | Code int `json:"code"`
452 | Message string `json:"message"`
453 | TTL int `json:"ttl"`
454 | Data struct {
455 | Total string `json:"total"`
456 | Count string `json:"count"`
457 | ShowSwitch struct {
458 | Total bool `json:"total"`
459 | Count bool `json:"count"`
460 | } `json:"show_switch"`
461 | Abtest struct {
462 | Group string `json:"group"`
463 | } `json:"abtest"`
464 | } `json:"data"`
465 | }
466 |
467 | // CookieConfig 配置结构体
468 | type CookieConfig struct {
469 | BilibiliCookie string `json:"bilibili_cookie"`
470 | file string
471 | }
472 |
473 | // NewCookieConfig ...
474 | func NewCookieConfig(file string) *CookieConfig {
475 | return &CookieConfig{
476 | file: file,
477 | }
478 | }
479 |
480 | // Set ...
481 | func (cfg *CookieConfig) Set(cookie string) (err error) {
482 | cfg.BilibiliCookie = cookie
483 | return cfg.Save()
484 | }
485 |
486 | // Load ...
487 | func (cfg *CookieConfig) Load() (cookie string, err error) {
488 | if cfg.BilibiliCookie != "" {
489 | cookie = cfg.BilibiliCookie
490 | return
491 | }
492 | if file.IsNotExist(cfg.file) {
493 | err = errors.New("no cookie config")
494 | return
495 | }
496 | reader, err := os.Open(cfg.file)
497 | if err != nil {
498 | return
499 | }
500 | defer reader.Close()
501 | err = json.NewDecoder(reader).Decode(cfg)
502 | cookie = cfg.BilibiliCookie
503 | return
504 | }
505 |
506 | // Save ...
507 | func (cfg *CookieConfig) Save() (err error) {
508 | reader, err := os.Create(cfg.file)
509 | if err != nil {
510 | return err
511 | }
512 | defer reader.Close()
513 | return json.NewEncoder(reader).Encode(cfg)
514 | }
515 |
--------------------------------------------------------------------------------
/bilibili/util.go:
--------------------------------------------------------------------------------
1 | package bilibili
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 | )
7 |
8 | // HumanNum 格式化人数
9 | func HumanNum(res int) string {
10 | if res/10000 != 0 {
11 | return strconv.FormatFloat(float64(res)/10000, 'f', 2, 64) + "万"
12 | }
13 | return strconv.Itoa(res)
14 | }
15 |
16 | // GetRealURL 获取跳转后的链接
17 | func GetRealURL(url string) (realurl string, err error) {
18 | data, err := http.Head(url)
19 | if err != nil {
20 | return
21 | }
22 | _ = data.Body.Close()
23 | realurl = data.Request.URL.String()
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/bilibili/wbi.go:
--------------------------------------------------------------------------------
1 | package bilibili
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "net/url"
7 | "path/filepath"
8 | "sort"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | "github.com/FloatTech/floatbox/binary"
14 | "github.com/FloatTech/floatbox/web"
15 | "github.com/RomiChan/syncx"
16 | "github.com/tidwall/gjson"
17 | )
18 |
19 | var (
20 | mixinKeyEncTab = []int{
21 | 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
22 | 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
23 | 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
24 | 36, 20, 34, 44, 52,
25 | }
26 | cache syncx.Map[string, string]
27 | lastUpdateTime time.Time
28 | replacements = [...]string{"!", "'", "(", ")", "*"}
29 | )
30 |
31 | // SignURL wbi签名包装 https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md
32 | func SignURL(urlStr string) string {
33 | urlObj, _ := url.Parse(urlStr)
34 | imgKey, subKey := getWbiKeysCached()
35 | query := urlObj.Query()
36 | params := map[string]string{}
37 | for k, v := range query {
38 | if len(v) > 0 {
39 | params[k] = v[0]
40 | }
41 | }
42 | newParams := wbiSign(params, imgKey, subKey)
43 | for k, v := range newParams {
44 | query.Set(k, v)
45 | }
46 | urlObj.RawQuery = query.Encode()
47 | newURL := urlObj.String()
48 | return newURL
49 | }
50 |
51 | func getMixinKey(orig string) string {
52 | var str strings.Builder
53 | t := 0
54 | for _, v := range mixinKeyEncTab {
55 | if v < len(orig) {
56 | str.WriteByte(orig[v])
57 | t++
58 | }
59 | if t > 31 {
60 | break
61 | }
62 | }
63 | return str.String()
64 | }
65 |
66 | func wbiSign(params map[string]string, imgKey string, subKey string) map[string]string {
67 | mixinKey := getMixinKey(imgKey + subKey)
68 | currTime := strconv.FormatInt(time.Now().Unix(), 10)
69 | params["wts"] = currTime
70 | // Sort keys
71 | keys := make([]string, 0, len(params))
72 | for k, v := range params {
73 | keys = append(keys, k)
74 | for _, old := range replacements {
75 | v = strings.ReplaceAll(v, old, "")
76 | }
77 | params[k] = v
78 | }
79 | sort.Strings(keys)
80 | h := md5.New()
81 | for k, v := range keys {
82 | h.Write([]byte(v))
83 | h.Write([]byte{'='})
84 | h.Write([]byte(params[v]))
85 | if k < len(keys)-1 {
86 | h.Write([]byte{'&'})
87 | }
88 | }
89 | h.Write([]byte(mixinKey))
90 | params["w_rid"] = hex.EncodeToString(h.Sum(make([]byte, 0, md5.Size)))
91 | return params
92 | }
93 |
94 | func getWbiKeysCached() (string, string) {
95 | if time.Since(lastUpdateTime).Minutes() > 10 {
96 | imgKey, subKey := getWbiKeys()
97 | cache.Store("imgKey", imgKey)
98 | cache.Store("subKey", subKey)
99 | lastUpdateTime = time.Now()
100 | return imgKey, subKey
101 | }
102 | imgKeyI, _ := cache.Load("imgKey")
103 | subKeyI, _ := cache.Load("subKey")
104 | return imgKeyI, subKeyI
105 | }
106 |
107 | func getWbiKeys() (string, string) {
108 | data, _ := web.GetData(NavURL)
109 | json := binary.BytesToString(data)
110 | imgURL := gjson.Get(json, "data.wbi_img.img_url").String()
111 | subURL := gjson.Get(json, "data.wbi_img.sub_url").String()
112 | imgKey := imgURL[strings.LastIndex(imgURL, "/")+1:]
113 | imgKey = strings.TrimSuffix(imgKey, filepath.Ext(imgKey))
114 | subKey := subURL[strings.LastIndex(subURL, "/")+1:]
115 | subKey = strings.TrimSuffix(subKey, filepath.Ext(subKey))
116 | return imgKey, subKey
117 | }
118 |
--------------------------------------------------------------------------------
/emozi/all_test.go:
--------------------------------------------------------------------------------
1 | package emozi
2 |
3 | import "testing"
4 |
5 | func TestAll(t *testing.T) {
6 | usr := Anonymous()
7 | in := "你好,世界!"
8 | out, _, err := usr.Marshal(false, in)
9 | if err != nil {
10 | t.Fatal(err)
11 | }
12 | exp := "🥛👔🐴👤🌹🐱🐴👩,💦🌞😨🌍➕👴😨👨🌾!" //nolint: go-staticcheck
13 | if out != exp {
14 | t.Fatal("expected", exp, "but got", out)
15 | }
16 | out, err = usr.Unmarshal(false, out)
17 | if err != nil {
18 | t.Fatal(err)
19 | }
20 | exp = "[你|儗]好,世[界|畍]!"
21 | if out != exp {
22 | t.Fatal("expected", exp, "but got", out)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/emozi/api.go:
--------------------------------------------------------------------------------
1 | // Package emozi 颜文字抽象转写
2 | package emozi
3 |
4 | const api = "https://emozi.seku.su/api/"
5 |
6 | // User 用户
7 | type User struct {
8 | name string
9 | pswd string
10 | auth string
11 | }
12 |
13 | type encodebody struct {
14 | Random bool `json:"random"`
15 | Text string `json:"text"`
16 | Choice []int `json:"choice"`
17 | }
18 |
19 | type encoderesult struct {
20 | Code int `json:"code"`
21 | Message string `json:"message"`
22 | Result struct {
23 | Text string `json:"text"`
24 | Choice []int `json:"choice,omitempty"`
25 | } `json:"result"`
26 | }
27 |
28 | type decodebody struct {
29 | Force bool `json:"force"`
30 | Text string `json:"text"`
31 | }
32 |
33 | type decoderesult struct {
34 | Code int `json:"code"`
35 | Message string `json:"message"`
36 | Result string `json:"result"`
37 | }
38 |
39 | type loginbody struct {
40 | Username string `json:"username"`
41 | Password string `json:"password"`
42 | Salt string `json:"salt"`
43 | }
44 |
--------------------------------------------------------------------------------
/emozi/coder.go:
--------------------------------------------------------------------------------
1 | package emozi
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "net/http"
8 |
9 | "github.com/FloatTech/floatbox/binary"
10 | )
11 |
12 | // Marshal 编码
13 | //
14 | // - randomSameMeaning 随机使用近音颜文字
15 | // - text 中文文本
16 | // - choices 多音字选择
17 | func (usr *User) Marshal(randomSameMeaning bool, text string, choices ...int) (string, []int, error) {
18 | w := binary.SelectWriter()
19 | defer binary.PutWriter(w)
20 | err := json.NewEncoder(w).Encode(&encodebody{
21 | Random: randomSameMeaning,
22 | Text: text,
23 | Choice: choices,
24 | })
25 | if err != nil {
26 | return "", nil, err
27 | }
28 | req, err := http.NewRequest("POST", api+"encode", (*bytes.Buffer)(w))
29 | if err != nil {
30 | return "", nil, err
31 | }
32 | req.Header.Set("Content-Type", "application/json")
33 | if usr.auth != "" {
34 | req.Header.Set("Authorization", usr.auth)
35 | }
36 | resp, err := http.DefaultClient.Do(req)
37 | if err != nil {
38 | return "", nil, err
39 | }
40 | defer resp.Body.Close()
41 | r := encoderesult{}
42 | err = json.NewDecoder(resp.Body).Decode(&r)
43 | if err != nil {
44 | return "", nil, err
45 | }
46 | if r.Code != 0 {
47 | return "", nil, errors.New(r.Message)
48 | }
49 | return r.Result.Text, r.Result.Choice, nil
50 | }
51 |
52 | // Unmarshal 解码
53 | //
54 | // - force 强制解码不是由程序生成的转写
55 | // - text 颜文字文本
56 | func (usr *User) Unmarshal(force bool, text string) (string, error) {
57 | w := binary.SelectWriter()
58 | defer binary.PutWriter(w)
59 | err := json.NewEncoder(w).Encode(&decodebody{
60 | Force: force,
61 | Text: text,
62 | })
63 | if err != nil {
64 | return "", err
65 | }
66 | req, err := http.NewRequest("POST", api+"decode", (*bytes.Buffer)(w))
67 | if err != nil {
68 | return "", err
69 | }
70 | req.Header.Set("Content-Type", "application/json")
71 | if usr.auth != "" {
72 | req.Header.Set("Authorization", usr.auth)
73 | }
74 | resp, err := http.DefaultClient.Do(req)
75 | if err != nil {
76 | return "", err
77 | }
78 | defer resp.Body.Close()
79 | r := decoderesult{}
80 | err = json.NewDecoder(resp.Body).Decode(&r)
81 | if err != nil {
82 | return "", err
83 | }
84 | if r.Code != 0 {
85 | return "", errors.New(r.Message)
86 | }
87 | return r.Result, nil
88 | }
89 |
--------------------------------------------------------------------------------
/emozi/login.go:
--------------------------------------------------------------------------------
1 | package emozi
2 |
3 | import (
4 | "bytes"
5 | "crypto/md5"
6 | "encoding/hex"
7 | "encoding/json"
8 | "errors"
9 | "net/http"
10 | "net/url"
11 |
12 | "github.com/FloatTech/floatbox/binary"
13 | "github.com/FloatTech/floatbox/web"
14 | "github.com/tidwall/gjson"
15 | )
16 |
17 | // NewUser 创建已注册用户实例
18 | //
19 | // 注册请前往 API 网址
20 | func NewUser(name, pswd string) (usr User) {
21 | usr.name = name
22 | usr.pswd = pswd
23 | return
24 | }
25 |
26 | // Anonymous 创建匿名用户
27 | //
28 | // 有访问请求数限制
29 | func Anonymous() (usr User) {
30 | return
31 | }
32 |
33 | // Login 登录
34 | func (usr *User) Login() error {
35 | data, err := web.GetData(api + "getLoginSalt?username=" + url.QueryEscape(usr.name))
36 | if err != nil {
37 | return err
38 | }
39 | r := gjson.ParseBytes(data)
40 | if r.Get("code").Int() != 0 {
41 | return errors.New(r.Get("message").Str)
42 | }
43 | salt := r.Get("result.salt").Str
44 | h := md5.New()
45 | h.Write([]byte(usr.pswd))
46 | h.Write([]byte(salt))
47 | passchlg := hex.EncodeToString(h.Sum(make([]byte, 0, md5.Size)))
48 | w := binary.SelectWriter()
49 | defer binary.PutWriter(w)
50 | err = json.NewEncoder(w).Encode(&loginbody{
51 | Username: usr.name,
52 | Password: passchlg,
53 | Salt: salt,
54 | })
55 | if err != nil {
56 | return err
57 | }
58 | data, err = web.PostData(api+"login", "application/json", (*bytes.Buffer)(w))
59 | if err != nil {
60 | return err
61 | }
62 | r = gjson.ParseBytes(data)
63 | if r.Get("code").Int() != 0 {
64 | return errors.New(r.Get("message").Str)
65 | }
66 | usr.auth = r.Get("result.token").Str
67 | return nil
68 | }
69 |
70 | // IsValid 检查是否有效
71 | func (usr *User) IsValid() bool {
72 | if usr.name == "" || usr.pswd == "" || usr.auth == "" {
73 | return false
74 | }
75 | _, err := web.RequestDataWithHeaders(http.DefaultClient, api+"", "GET", func(r *http.Request) error {
76 | r.Header.Set("Authorization", usr.auth)
77 | return nil
78 | }, nil)
79 | return err == nil
80 | }
81 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/FloatTech/AnimeAPI
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024
7 | github.com/FloatTech/sqlite v1.7.1
8 | github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
9 | github.com/antchfx/htmlquery v1.3.3
10 | github.com/corona10/goimagehash v1.1.0
11 | github.com/fumiama/go-base16384 v1.7.0
12 | github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce
13 | github.com/pkg/errors v0.9.1
14 | github.com/pkumza/numcn v1.0.0
15 | github.com/sirupsen/logrus v1.9.3
16 | github.com/stretchr/testify v1.9.0
17 | github.com/tidwall/gjson v1.18.0
18 | golang.org/x/image v0.21.0
19 | )
20 |
21 | require (
22 | github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 // indirect
23 | github.com/antchfx/xpath v1.3.2 // indirect
24 | github.com/davecgh/go-spew v1.1.1 // indirect
25 | github.com/dustin/go-humanize v1.0.1 // indirect
26 | github.com/fumiama/cron v1.3.0 // indirect
27 | github.com/fumiama/go-registry v0.2.7 // indirect
28 | github.com/fumiama/go-simple-protobuf v0.2.0 // indirect
29 | github.com/fumiama/gofastTEA v0.0.10 // indirect
30 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
31 | github.com/google/uuid v1.6.0 // indirect
32 | github.com/mattn/go-isatty v0.0.20 // indirect
33 | github.com/ncruces/go-strftime v0.1.9 // indirect
34 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
35 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
36 | github.com/pmezard/go-difflib v1.0.0 // indirect
37 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
38 | github.com/tidwall/match v1.1.1 // indirect
39 | github.com/tidwall/pretty v1.2.0 // indirect
40 | github.com/wdvxdr1123/ZeroBot v1.8.0 // indirect
41 | golang.org/x/net v0.24.0 // indirect
42 | golang.org/x/sys v0.26.0 // indirect
43 | golang.org/x/text v0.19.0 // indirect
44 | gopkg.in/yaml.v3 v3.0.1 // indirect
45 | modernc.org/libc v1.61.0 // indirect
46 | modernc.org/mathutil v1.6.0 // indirect
47 | modernc.org/memory v1.8.0 // indirect
48 | modernc.org/sqlite v1.33.1 // indirect
49 | )
50 |
51 | replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.29.10-simp
52 |
53 | replace modernc.org/libc => github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5
54 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024 h1:mrvWpiwfRklt9AyiQjKgDGJjf4YL6FZ3yC+ydbkuF2o=
2 | github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024/go.mod h1:+P3hs+Cvl10/Aj3SNE96TuBvKAXCe+XD1pKphTZyiwk=
3 | github.com/FloatTech/sqlite v1.7.1 h1:XKUY0+MNaRmvEIgRv7QLbl7PFVpUfQ72+XQg+no2Vq0=
4 | github.com/FloatTech/sqlite v1.7.1/go.mod h1:/4tzfCGhrZnnjC1U8vcfwGQeF6eR649fhOsS3+Le0+s=
5 | github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ/VCf80LiQo9C7jHgrunZDwiRcY=
6 | github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
7 | github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
8 | github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
9 | github.com/antchfx/htmlquery v1.3.3 h1:x6tVzrRhVNfECDaVxnZi1mEGrQg3mjE/rxbH2Pe6dNE=
10 | github.com/antchfx/htmlquery v1.3.3/go.mod h1:WeU3N7/rL6mb6dCwtE30dURBnBieKDC/fR8t6X+cKjU=
11 | github.com/antchfx/xpath v1.3.2 h1:LNjzlsSjinu3bQpw9hWMY9ocB80oLOWuQqFvO6xt51U=
12 | github.com/antchfx/xpath v1.3.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
13 | github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
14 | github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
18 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
19 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
20 | github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo=
21 | github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY=
22 | github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
23 | github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
24 | github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs=
25 | github.com/fumiama/go-registry v0.2.7/go.mod h1:m+wp5fF8dYgVoFkBPZl+vlK90loymaJE0JCtocVQLEs=
26 | github.com/fumiama/go-simple-protobuf v0.2.0 h1:ACyN1MAlu7pDR3EszWgzUeNP+IRsSHwH6V9JCJA5R5o=
27 | github.com/fumiama/go-simple-protobuf v0.2.0/go.mod h1:5yYNapXq1tQMOZg9bOIVhQlZk9pQqpuFIO4DZLbsdy4=
28 | github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoGhhQ=
29 | github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk=
30 | github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5 h1:jDxsIupsT84A6WHcs6kWbst+KqrRQ8/o0VyoFMnbBOA=
31 | github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=
32 | github.com/fumiama/sqlite3 v1.29.10-simp h1:c5y3uKyU0q9t0/SyfynzYyuslQ5zP+5CD8e0yYY554A=
33 | github.com/fumiama/sqlite3 v1.29.10-simp/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
34 | github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce h1:T6iDDU16rFyxV/FwfJJR6qcgkIlXJEIFlUTSmTD1h6s=
35 | github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce/go.mod h1:UVx8YP1jKKL1Cj+uy+OnQRM2Ih6U36Mqy9GSf7jabsI=
36 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
37 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
38 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
39 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
40 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
41 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
42 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
43 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
44 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
45 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
46 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
47 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
48 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
49 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
50 | github.com/pkumza/numcn v1.0.0 h1:ZT5cf9IJkUZgRgEtCiNNykk0RwsrKXSTsvDHOwUTzgE=
51 | github.com/pkumza/numcn v1.0.0/go.mod h1:QSeH+al9dWCd8di5HZM/ZqHqhZmUKfph572e9Ev/ETc=
52 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
54 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
55 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
56 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
57 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
58 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
59 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
60 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
61 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
62 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
63 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
64 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
65 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
66 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
67 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
68 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
69 | github.com/wdvxdr1123/ZeroBot v1.8.0 h1:v7m+0kGtL6XQlUH9O/LzmOntDJs2clzVj93YsAWWMbk=
70 | github.com/wdvxdr1123/ZeroBot v1.8.0/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
71 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
72 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
73 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
74 | golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
75 | golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
76 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
77 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
78 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
79 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
80 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
81 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
82 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
83 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
84 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
85 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
86 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
87 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
88 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
89 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
90 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
91 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
92 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
93 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
94 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
95 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
96 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
97 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
98 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
99 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
100 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
101 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
102 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
103 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
104 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
105 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
106 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
107 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
108 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
109 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
110 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
111 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
112 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
113 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
114 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
115 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
116 | modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
117 | modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
118 | modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
119 | modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
120 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
121 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
122 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
123 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
124 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
125 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
126 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
127 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
128 |
--------------------------------------------------------------------------------
/huggingface/huggingface.go:
--------------------------------------------------------------------------------
1 | // Package huggingface ai界的github, 常用参数
2 | package huggingface
3 |
4 | import (
5 | "bytes"
6 | "encoding/json"
7 |
8 | "github.com/FloatTech/floatbox/web"
9 | )
10 |
11 | const (
12 | // HuggingfaceSpaceHTTPS huggingface space https api
13 | HuggingfaceSpaceHTTPS = "https://hf.space"
14 | // Embed huggingface space api embed
15 | Embed = HuggingfaceSpaceHTTPS + "/embed"
16 | // HTTPSPushPath 推送队列
17 | HTTPSPushPath = Embed + "/%v/api/queue/push/"
18 | // HTTPSStatusPath 状态队列
19 | HTTPSStatusPath = Embed + "/%v/api/queue/status/"
20 | // HuggingfaceSpaceWss huggingface space wss api
21 | HuggingfaceSpaceWss = "wss://spaces.huggingface.tech"
22 | // WssJoinPath 推送队列2
23 | WssJoinPath = HuggingfaceSpaceWss + "/%v/queue/join"
24 | // HTTPSPredictPath 推送队列3
25 | HTTPSPredictPath = Embed + "/%v/api/predict/"
26 | // DefaultAction 默认动作
27 | DefaultAction = "predict"
28 | // CompleteStatus 完成状态
29 | CompleteStatus = "COMPLETE"
30 | // WssCompleteStatus 完成状态2
31 | WssCompleteStatus = "process_completed"
32 | // TimeoutMax 超时时间
33 | TimeoutMax = 300
34 | )
35 |
36 | // PushRequest 推送默认请求
37 | type PushRequest struct {
38 | Action string `json:"action,omitempty"`
39 | FnIndex int `json:"fn_index"`
40 | Data []interface{} `json:"data"`
41 | SessionHash string `json:"session_hash"`
42 | }
43 |
44 | // PushResponse 推送默认响应
45 | type PushResponse struct {
46 | Hash string `json:"hash"`
47 | QueuePosition int `json:"queue_position"`
48 | }
49 |
50 | // StatusRequest 状态默认请求
51 | type StatusRequest struct {
52 | Hash string `json:"hash"`
53 | }
54 |
55 | // StatusResponse 状态默认响应
56 | type StatusResponse struct {
57 | Status string `json:"status"`
58 | Data struct {
59 | Data []interface{} `json:"data"`
60 | Duration float64 `json:"duration"`
61 | AverageDuration float64 `json:"average_duration"`
62 | }
63 | }
64 |
65 | // Push 推送请求
66 | func Push(pushURL string, pushReq *PushRequest) (pushRes PushResponse, err error) {
67 | b, err := json.Marshal(pushReq)
68 | if err != nil {
69 | return
70 | }
71 | data, err := web.PostData(pushURL, "application/json", bytes.NewReader(b))
72 | if err != nil {
73 | return
74 | }
75 | err = json.Unmarshal(data, &pushRes)
76 | return
77 | }
78 |
79 | // Status 状态请求
80 | func Status(statusURL string, statusReq *StatusRequest) (data []byte, err error) {
81 | b, err := json.Marshal(statusReq)
82 | if err != nil {
83 | return
84 | }
85 | data, err = web.PostData(statusURL, "application/json", bytes.NewReader(b))
86 | return
87 | }
88 |
--------------------------------------------------------------------------------
/kimoi/chat.go:
--------------------------------------------------------------------------------
1 | // Package kimoi AI 匹配 kimoi 词库
2 | package kimoi
3 |
4 | import (
5 | "bytes"
6 | "encoding/base64"
7 | "encoding/json"
8 | "net/http"
9 |
10 | base14 "github.com/fumiama/go-base16384"
11 | )
12 |
13 | const key = "暻撈莬穔僿貶稙棯悟澸滰蓱咜唕母屬石褤汴儱榅璕婴㴅"
14 |
15 | const api = "https://ninex.azurewebsites.net/api/chat?code="
16 |
17 | // Response 回复结构
18 | type Response struct {
19 | // Reply 文本
20 | Reply string `json:"reply"`
21 | // Confidence 置信度, 建议不要使用 < 0.5 或 > 0.95 的结果
22 | Confidence float64 `json:"confidence"`
23 | }
24 |
25 | // Chat 用户对 AI 说一句话
26 | func Chat(msg string) (r Response, err error) {
27 | resp, err := http.Post(
28 | api+base64.URLEncoding.EncodeToString(base14.DecodeFromString(key)),
29 | "text/plain", bytes.NewBufferString(msg),
30 | )
31 | if err != nil {
32 | return
33 | }
34 | defer resp.Body.Close()
35 | err = json.NewDecoder(resp.Body).Decode(&r)
36 | return
37 | }
38 |
--------------------------------------------------------------------------------
/neteasemusic/neteasemusic.go:
--------------------------------------------------------------------------------
1 | // Package netease 网易云音乐一些简单的API:搜歌、下歌、搜歌词、下歌词
2 | package netease
3 |
4 | import (
5 | "encoding/json"
6 | "net/http"
7 | "net/url"
8 | "os"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/FloatTech/floatbox/binary"
13 | "github.com/FloatTech/floatbox/file"
14 | "github.com/FloatTech/floatbox/process"
15 | "github.com/FloatTech/floatbox/web"
16 | "github.com/pkg/errors"
17 | )
18 |
19 | // wyy搜歌结果
20 | type searchResult struct {
21 | Result struct {
22 | Songs []struct {
23 | ID int `json:"id"`
24 | Name string `json:"name"`
25 | Artists []struct {
26 | ID int `json:"id"`
27 | Name string `json:"name"`
28 | PicURL interface{} `json:"picUrl"`
29 | Alias []interface{} `json:"alias"`
30 | AlbumSize int `json:"albumSize"`
31 | PicID int `json:"picId"`
32 | FansGroup interface{} `json:"fansGroup"`
33 | Img1V1URL string `json:"img1v1Url"`
34 | Img1V1 int `json:"img1v1"`
35 | Trans interface{} `json:"trans"`
36 | } `json:"artists"`
37 | Album struct {
38 | ID int `json:"id"`
39 | Name string `json:"name"`
40 | Artist struct {
41 | ID int `json:"id"`
42 | Name string `json:"name"`
43 | PicURL interface{} `json:"picUrl"`
44 | Alias []interface{} `json:"alias"`
45 | AlbumSize int `json:"albumSize"`
46 | PicID int `json:"picId"`
47 | FansGroup interface{} `json:"fansGroup"`
48 | Img1V1URL string `json:"img1v1Url"`
49 | Img1V1 int `json:"img1v1"`
50 | Trans interface{} `json:"trans"`
51 | } `json:"artist"`
52 | PublishTime int64 `json:"publishTime"`
53 | Size int `json:"size"`
54 | CopyrightID int `json:"copyrightId"`
55 | Status int `json:"status"`
56 | PicID int64 `json:"picId"`
57 | Mark int `json:"mark"`
58 | } `json:"album"`
59 | Duration int `json:"duration"`
60 | CopyrightID int `json:"copyrightId"`
61 | Status int `json:"status"`
62 | Alias []interface{} `json:"alias"`
63 | Rtype int `json:"rtype"`
64 | Ftype int `json:"ftype"`
65 | Mvid int `json:"mvid"`
66 | Fee int `json:"fee"`
67 | RURL interface{} `json:"rUrl"`
68 | Mark int `json:"mark"`
69 | } `json:"songs"`
70 | SongCount int `json:"songCount"`
71 | } `json:"result"`
72 | Code int `json:"code"`
73 | }
74 |
75 | // 歌词内容
76 | type musicLrc struct {
77 | LyricVersion int `json:"lyricVersion"`
78 | Lyric string `json:"lyric"`
79 | Code int `json:"code"`
80 | }
81 |
82 | // SearchMusic 搜索网易云音乐歌曲
83 | //
84 | // keyword:搜索内容 n:输出数量
85 | //
86 | // list:map[歌曲名称]歌曲ID
87 | func SearchMusic(keyword string, n int) (list map[string]int, err error) {
88 | list = make(map[string]int, 2*n)
89 | requestURL := "http://music.163.com/api/search/get/web?type=1&limit=" + strconv.Itoa(n) + "&s=" + url.QueryEscape(keyword)
90 | data, err := web.GetData(requestURL)
91 | if err != nil {
92 | return
93 | }
94 | var searchResult searchResult
95 | err = json.Unmarshal(data, &searchResult)
96 | if err != nil {
97 | return
98 | }
99 | if searchResult.Code != 200 {
100 | err = errors.Errorf("Status Code: %d", searchResult.Code)
101 | return
102 | }
103 | for _, musicinfo := range searchResult.Result.Songs {
104 | musicName := musicinfo.Name
105 | // 歌手信息
106 | artistsmun := len(musicinfo.Artists)
107 | if artistsmun != 0 {
108 | musicName += " - "
109 | for i, artistsinfo := range musicinfo.Artists {
110 | if artistsinfo.Name != "" {
111 | musicName += artistsinfo.Name
112 | }
113 | if i != 0 && i < artistsmun-1 {
114 | musicName += "&"
115 | }
116 | }
117 | }
118 | // 出自信息
119 | if len(musicinfo.Alias) != 0 {
120 | musicName += " - " + musicinfo.Alias[0].(string)
121 | }
122 | // 记录歌曲信息
123 | list[musicName] = musicinfo.ID
124 | }
125 | return
126 | }
127 |
128 | // DownloadMusic 下载网易云音乐(歌曲ID,歌曲名称,下载路径)
129 | func DownloadMusic(musicID int, musicName, pathOfMusic string) error {
130 | downMusic := pathOfMusic + "/" + musicName + ".mp3"
131 | musicURL := "http://music.163.com/song/media/outer/url?id=" + strconv.Itoa(musicID)
132 | if file.IsNotExist(downMusic) {
133 | // 检查歌曲是否存在
134 | response, err := http.Head(musicURL)
135 | if err != nil {
136 | return err
137 | }
138 | _ = response.Body.Close()
139 | if response.StatusCode != 200 {
140 | return errors.Errorf("Status Code: %d", response.StatusCode)
141 | }
142 |
143 | // 检查 Content-Type 是否为 HTML
144 | contentType := response.Header.Get("Content-Type")
145 | if strings.HasPrefix(contentType, "text/html") {
146 | return errors.New("URL points to an HTML page instead of an MP3 file")
147 | }
148 |
149 | // 下载歌曲
150 | err = file.DownloadTo(musicURL, downMusic)
151 | process.SleepAbout1sTo2s()
152 | return err
153 | }
154 | return nil
155 | }
156 |
157 | // SreachLrc 搜索网易云音乐歌词(歌曲ID)
158 | func SreachLrc(musicID int) (lrc string, err error) {
159 | musicURL := "http://music.163.com/api/song/media?id=" + strconv.Itoa(musicID)
160 | data, err := web.GetData(musicURL)
161 | if err != nil {
162 | return
163 | }
164 | var lrcinfo musicLrc
165 | err = json.Unmarshal(data, &lrcinfo)
166 | if err == nil {
167 | lrc = lrcinfo.Lyric
168 | }
169 | return
170 | }
171 |
172 | // DownloadLrc 下载网易云音乐歌词(歌曲ID,歌曲名称,下载路径)
173 | func DownloadLrc(musicID int, musicName, pathOfMusic string) error {
174 | err := os.MkdirAll(pathOfMusic, 0777)
175 | if err != nil {
176 | return err
177 | }
178 | downfile := pathOfMusic + "/" + musicName + ".lrc"
179 | musicURL := "http://music.163.com/api/song/media?id=" + strconv.Itoa(musicID)
180 | if file.IsNotExist(downfile) {
181 | data, err := web.GetData(musicURL)
182 | if err != nil {
183 | return err
184 | }
185 | var lrcinfo musicLrc
186 | err = json.Unmarshal(data, &lrcinfo)
187 | if err != nil {
188 | return err
189 | }
190 | if lrcinfo.Code != 200 {
191 | return errors.Errorf("Status Code: %d", lrcinfo.Code)
192 | }
193 | if lrcinfo.Lyric != "" {
194 | return os.WriteFile(downfile, binary.StringToBytes(lrcinfo.Lyric), 0666)
195 | }
196 | return errors.New("该歌曲无歌词")
197 | }
198 | return nil
199 | }
200 |
--------------------------------------------------------------------------------
/niu/main.go:
--------------------------------------------------------------------------------
1 | // Package niu 牛牛大作战
2 | package niu
3 |
4 | import (
5 | "errors"
6 | "fmt"
7 | "os"
8 | "strconv"
9 | "strings"
10 | "sync"
11 | "time"
12 |
13 | "github.com/FloatTech/floatbox/file"
14 | sql "github.com/FloatTech/sqlite"
15 |
16 | "github.com/FloatTech/AnimeAPI/wallet"
17 | )
18 |
19 | var (
20 | db = &model{}
21 | globalLock sync.Mutex
22 | // ErrNoBoys 表示当前没有男孩子可用的错误。
23 | ErrNoBoys = errors.New("暂时没有男孩子哦")
24 |
25 | // ErrNoGirls 表示当前没有女孩子可用的错误。
26 | ErrNoGirls = errors.New("暂时没有女孩子哦")
27 |
28 | // ErrNoNiuNiu 表示用户尚未拥有牛牛的错误。
29 | ErrNoNiuNiu = errors.New("你还没有牛牛呢,快去注册吧!")
30 |
31 | // ErrNoNiuNiuINAuction 表示拍卖行当前没有牛牛可用的错误。
32 | ErrNoNiuNiuINAuction = errors.New("拍卖行还没有牛牛呢")
33 |
34 | // ErrNoMoney 表示用户资金不足的错误。
35 | ErrNoMoney = errors.New("你的钱不够快去赚钱吧!")
36 |
37 | // ErrAdduserNoNiuNiu 表示对方尚未拥有牛牛,因此无法进行某些操作的错误。
38 | ErrAdduserNoNiuNiu = errors.New("对方还没有牛牛呢,不能🤺")
39 |
40 | // ErrCannotFight 表示无法进行战斗操作的错误。
41 | ErrCannotFight = errors.New("你要和谁🤺?你自己吗?")
42 |
43 | // ErrNoNiuNiuTwo 表示用户尚未拥有牛牛,无法执行特定操作的错误。
44 | ErrNoNiuNiuTwo = errors.New("你还没有牛牛呢,咋的你想凭空造一个啊")
45 |
46 | // ErrAlreadyRegistered 表示用户已经注册过的错误。
47 | ErrAlreadyRegistered = errors.New("你已经注册过了")
48 |
49 | // ErrInvalidPropType 表示传入的道具类别错误的错误。
50 | ErrInvalidPropType = errors.New("道具类别传入错误")
51 |
52 | // ErrInvalidPropUsageScope 表示道具使用域错误的错误。
53 | ErrInvalidPropUsageScope = errors.New("道具使用域错误")
54 |
55 | // ErrPropNotFound 表示找不到指定道具的错误。
56 | ErrPropNotFound = errors.New("道具不存在")
57 | )
58 |
59 | func init() {
60 | if file.IsNotExist("data/niuniu") {
61 | err := os.MkdirAll("data/niuniu", 0775)
62 | if err != nil {
63 | panic(err)
64 | }
65 | }
66 | db.sql = sql.New("data/niuniu/niuniu.db")
67 | err := db.sql.Open(time.Hour * 24)
68 | if err != nil {
69 | panic(err)
70 | }
71 | }
72 |
73 | // DeleteWordNiuNiu ...
74 | func DeleteWordNiuNiu(gid, uid int64) error {
75 | globalLock.Lock()
76 | defer globalLock.Unlock()
77 | return db.deleteWordNiuNiu(gid, uid)
78 | }
79 |
80 | // SetWordNiuNiu length > 0 就增加 , length < 0 就减小
81 | func SetWordNiuNiu(gid, uid int64, length float64) error {
82 | globalLock.Lock()
83 | defer globalLock.Unlock()
84 | niu, err := db.getWordNiuNiu(gid, uid)
85 | if err != nil {
86 | return err
87 | }
88 | niu.Length += length
89 | return db.setWordNiuNiu(gid, niu)
90 | }
91 |
92 | // GetWordNiuNiu ...
93 | func GetWordNiuNiu(gid, uid int64) (float64, error) {
94 | globalLock.Lock()
95 | defer globalLock.Unlock()
96 |
97 | niu, err := db.getWordNiuNiu(gid, uid)
98 | return niu.Length, err
99 | }
100 |
101 | // GetRankingInfo 获取排行信息
102 | func GetRankingInfo(gid int64, t bool) (BaseInfos, error) {
103 | globalLock.Lock()
104 | defer globalLock.Unlock()
105 | var (
106 | list users
107 | err error
108 | )
109 |
110 | niuOfGroup, err := db.getAllNiuNiuOfGroup(gid)
111 | if err != nil {
112 | return nil, err
113 | }
114 |
115 | list = niuOfGroup.filter(t)
116 | list.sort(t)
117 | if len(list) == 0 {
118 | if t {
119 | return nil, ErrNoBoys
120 | }
121 | return nil, ErrNoGirls
122 | }
123 | f := make(BaseInfos, len(list))
124 | for i, info := range list {
125 | f[i] = BaseInfo{
126 | UID: info.UID,
127 | Length: info.Length,
128 | }
129 | }
130 | return f, nil
131 | }
132 |
133 | // GetGroupUserRank 获取指定用户在群中的排名
134 | func GetGroupUserRank(gid, uid int64) (int, error) {
135 | globalLock.Lock()
136 | defer globalLock.Unlock()
137 | niu, err := db.getWordNiuNiu(gid, uid)
138 | if err != nil {
139 | return -1, err
140 | }
141 | group, err := db.getAllNiuNiuOfGroup(gid)
142 | if err != nil {
143 | return -1, err
144 | }
145 | return group.ranking(niu.Length, uid), nil
146 | }
147 |
148 | // View 查看牛牛
149 | func View(gid, uid int64, name string) (string, error) {
150 | i, err := db.getWordNiuNiu(gid, uid)
151 | if err != nil {
152 | return "", ErrNoNiuNiu
153 | }
154 | niuniu := i.Length
155 | var result strings.Builder
156 | sexLong := "长"
157 | sex := "♂️"
158 | if niuniu < 0 {
159 | sexLong = "深"
160 | sex = "♀️"
161 | }
162 | niuniuList, err := db.getAllNiuNiuOfGroup(gid)
163 | if err != nil {
164 | return "", err
165 | }
166 | result.WriteString(fmt.Sprintf("\n📛%s<%s>的牛牛信息\n⭕性别:%s\n⭕%s度:%.2fcm\n⭕排行:%d\n⭕%s ",
167 | name, strconv.FormatInt(uid, 10),
168 | sex, sexLong, niuniu, niuniuList.ranking(niuniu, uid), generateRandomString(niuniu)))
169 | return result.String(), nil
170 | }
171 |
172 | // HitGlue 打胶
173 | func HitGlue(gid, uid int64, prop string) (string, error) {
174 | niuniu, err := db.getWordNiuNiu(gid, uid)
175 | if err != nil {
176 | return "", ErrNoNiuNiuTwo
177 | }
178 |
179 | messages, err := niuniu.processDaJiao(prop)
180 | if err != nil {
181 | return "", err
182 | }
183 | if err = db.setWordNiuNiu(gid, niuniu); err != nil {
184 | return "", err
185 | }
186 | return messages, nil
187 | }
188 |
189 | // Register 注册牛牛
190 | func Register(gid, uid int64) (string, error) {
191 | if _, err := db.getWordNiuNiu(gid, uid); err == nil {
192 | return "", ErrAlreadyRegistered
193 | }
194 | // 获取初始长度
195 | length := db.newLength()
196 | u := userInfo{
197 | UID: uid,
198 | Length: length,
199 | }
200 | if err := db.setWordNiuNiu(gid, &u); err != nil {
201 | return "", err
202 | }
203 | return fmt.Sprintf("注册成功,你的牛牛现在有%.2fcm", u.Length), nil
204 | }
205 |
206 | // JJ ...
207 | func JJ(gid, uid, adduser int64, prop string) (message string, adduserLength float64, err error) {
208 | myniuniu, err := db.getWordNiuNiu(gid, uid)
209 | if err != nil {
210 | return "", 0, ErrNoNiuNiu
211 | }
212 |
213 | adduserniuniu, err := db.getWordNiuNiu(gid, adduser)
214 | if err != nil {
215 | return "", 0, ErrAdduserNoNiuNiu
216 | }
217 |
218 | if uid == adduser {
219 | return "", 0, ErrCannotFight
220 | }
221 |
222 | message, err = myniuniu.processJJ(adduserniuniu, prop)
223 | if err != nil {
224 | return "", 0, err
225 | }
226 |
227 | if err = db.setWordNiuNiu(gid, myniuniu); err != nil {
228 | return "", 0, err
229 | }
230 |
231 | if err = db.setWordNiuNiu(gid, adduserniuniu); err != nil {
232 | return "", 0, err
233 | }
234 |
235 | adduserLength = adduserniuniu.Length
236 |
237 | if err = db.setWordNiuNiu(gid, adduserniuniu); err != nil {
238 | return "", 0, err
239 | }
240 |
241 | return
242 | }
243 |
244 | // Cancel 注销牛牛
245 | func Cancel(gid, uid int64) (string, error) {
246 | _, err := db.getWordNiuNiu(gid, uid)
247 | if err != nil {
248 | return "", ErrNoNiuNiuTwo
249 | }
250 | err = db.deleteWordNiuNiu(gid, uid)
251 | if err != nil {
252 | err = errors.New("遇到不可抗力因素,注销失败!")
253 | }
254 | return "注销成功,你已经没有牛牛了", err
255 | }
256 |
257 | // Redeem 赎牛牛
258 | func Redeem(gid, uid int64, lastLength float64) error {
259 | money := wallet.GetWalletOf(uid)
260 | if money < 150 {
261 | var builder strings.Builder
262 | walletName := wallet.GetWalletName()
263 | builder.WriteString("赎牛牛需要150")
264 | builder.WriteString(walletName)
265 | builder.WriteString(",快去赚钱吧,目前仅有:")
266 | builder.WriteString(strconv.Itoa(money))
267 | builder.WriteString("个")
268 | builder.WriteString(walletName)
269 | return errors.New(builder.String())
270 | }
271 |
272 | if err := wallet.InsertWalletOf(uid, -150); err != nil {
273 | return err
274 | }
275 |
276 | niu, err := db.getWordNiuNiu(gid, uid)
277 | if err != nil {
278 | return ErrNoNiuNiu
279 | }
280 |
281 | niu.Length = lastLength
282 |
283 | return db.setWordNiuNiu(gid, niu)
284 | }
285 |
286 | // Store 牛牛商店
287 | func Store(gid, uid int64, n int) error {
288 | info, err := db.getWordNiuNiu(gid, uid)
289 | if err != nil {
290 | return err
291 | }
292 |
293 | money, err := info.purchaseItem(n)
294 | if err != nil {
295 | return err
296 | }
297 |
298 | if wallet.GetWalletOf(uid) < money {
299 | return ErrNoMoney
300 | }
301 |
302 | if err = wallet.InsertWalletOf(uid, -money); err != nil {
303 | return err
304 | }
305 |
306 | return db.setWordNiuNiu(uid, info)
307 | }
308 |
309 | // Sell 出售牛牛
310 | func Sell(gid, uid int64) (string, error) {
311 | niu, err := db.getWordNiuNiu(gid, uid)
312 | if err != nil {
313 | return "", ErrNoNiuNiu
314 | }
315 | money, t, message := profit(niu.Length)
316 | if !t {
317 | return "", errors.New(message)
318 | }
319 |
320 | if err := db.deleteWordNiuNiu(gid, uid); err != nil {
321 | return "", err
322 | }
323 |
324 | err = wallet.InsertWalletOf(uid, money)
325 | if err != nil {
326 | return message, err
327 | }
328 |
329 | infos, _ := db.getAllNiuNiuAuction(gid)
330 |
331 | u := AuctionInfo{
332 | ID: len(infos),
333 | UserID: niu.UID,
334 | Length: niu.Length,
335 | Money: money * 2,
336 | }
337 |
338 | err = db.setNiuNiuAuction(gid, &u)
339 | return message, err
340 | }
341 |
342 | // ShowAuction 展示牛牛拍卖行
343 | func ShowAuction(gid int64) ([]AuctionInfo, error) {
344 | globalLock.Lock()
345 | defer globalLock.Unlock()
346 | return db.getAllNiuNiuAuction(gid)
347 | }
348 |
349 | // Auction 购买牛牛
350 | func Auction(gid, uid int64, index int) (string, error) {
351 | infos, err := db.getAllNiuNiuAuction(gid)
352 | if err != nil {
353 | return "", ErrNoNiuNiuINAuction
354 | }
355 | if err := wallet.InsertWalletOf(uid, -infos[index].Money); err != nil {
356 | return "", ErrNoMoney
357 | }
358 |
359 | niu, err := db.getWordNiuNiu(gid, uid)
360 |
361 | if err != nil {
362 | niu.UID = uid
363 | }
364 |
365 | niu.Length = infos[index].Length
366 |
367 | if infos[index].Money >= 500 {
368 | niu.WeiGe += 2
369 | niu.Philter += 2
370 | }
371 |
372 | if err = db.deleteNiuNiuAuction(gid, uint(index)); err != nil {
373 | return "", err
374 | }
375 |
376 | if err = db.setWordNiuNiu(gid, niu); err != nil {
377 | return "", err
378 | }
379 |
380 | if infos[index].Money >= 500 {
381 | return fmt.Sprintf("恭喜你购买成功,当前长度为%.2fcm,此次购买将赠送你2个伟哥,2个媚药", niu.Length), nil
382 | }
383 |
384 | return fmt.Sprintf("恭喜你购买成功,当前长度为%.2fcm", niu.Length), nil
385 | }
386 |
387 | // Bag 牛牛背包
388 | func Bag(gid, uid int64) (string, error) {
389 | niu, err := db.getWordNiuNiu(gid, uid)
390 | if err != nil {
391 | return "", ErrNoNiuNiu
392 | }
393 |
394 | var result strings.Builder
395 | result.Grow(100)
396 |
397 | result.WriteString("当前牛牛背包如下\n")
398 | result.WriteString(fmt.Sprintf("伟哥: %v\n", niu.WeiGe))
399 | result.WriteString(fmt.Sprintf("媚药: %v\n", niu.Philter))
400 | result.WriteString(fmt.Sprintf("击剑神器: %v\n", niu.Artifact))
401 | result.WriteString(fmt.Sprintf("击剑神稽: %v\n", niu.ShenJi))
402 |
403 | return result.String(), nil
404 | }
405 |
--------------------------------------------------------------------------------
/niu/models.go:
--------------------------------------------------------------------------------
1 | // Package niu 牛牛大作战
2 | package niu
3 |
4 | import (
5 | "errors"
6 | "fmt"
7 | "math"
8 | "math/rand"
9 | "sort"
10 | "strconv"
11 | "sync"
12 |
13 | sql "github.com/FloatTech/sqlite"
14 | )
15 |
16 | var (
17 | daJiaoProps = []string{"伟哥", "媚药"}
18 | jjPorps = []string{"击剑神器", "击剑神稽"}
19 | query = "WHERE UID = ?"
20 | )
21 |
22 | type users []*userInfo
23 |
24 | type model struct {
25 | sql sql.Sqlite
26 | sync.RWMutex
27 | }
28 |
29 | type userInfo struct {
30 | UID int64
31 | Length float64
32 | UserCount int
33 | WeiGe int // 伟哥
34 | Philter int // 媚药
35 | Artifact int // 击剑神器
36 | ShenJi int // 击剑神稽
37 | Buff1 int // 暂定
38 | Buff2 int // 暂定
39 | Buff3 int // 暂定
40 | Buff4 int // 暂定
41 | Buff5 int // 暂定
42 | }
43 |
44 | // AuctionInfo 拍卖信息
45 | type AuctionInfo struct {
46 | ID int `db:"id"`
47 | UserID int64 `db:"user_id"`
48 | Length float64 `db:"length"`
49 | Money int `db:"money"`
50 | }
51 |
52 | // BaseInfo ...
53 | type BaseInfo struct {
54 | UID int64
55 | Length float64
56 | }
57 |
58 | // BaseInfos ...
59 | type BaseInfos []BaseInfo
60 |
61 | func (m users) filter(pos bool) users {
62 | if pos {
63 | return m.positive()
64 | }
65 | return m.negative()
66 | }
67 |
68 | func (m users) positive() users {
69 | var m1 []*userInfo
70 | for _, i2 := range m {
71 | if i2.Length > 0 {
72 | m1 = append(m1, i2)
73 | }
74 | }
75 | return m1
76 | }
77 |
78 | func (m users) negative() users {
79 | var m1 []*userInfo
80 | for _, i2 := range m {
81 | if i2.Length <= 0 {
82 | m1 = append(m1, i2)
83 | }
84 | }
85 | return m1
86 | }
87 |
88 | func (m users) sort(isDesc bool) {
89 | t := func(i, j int) bool {
90 | return m[i].Length < m[j].Length
91 | }
92 | if isDesc {
93 | t = func(i, j int) bool {
94 | return m[i].Length > m[j].Length
95 | }
96 | }
97 | sort.Slice(m, t)
98 | }
99 |
100 | func (m users) ranking(niuniu float64, uid int64) int {
101 | m.sort(niuniu > 0)
102 | for i, user := range m {
103 | if user.UID == uid {
104 | return i + 1
105 | }
106 | }
107 | return -1
108 | }
109 |
110 | func (u *userInfo) useWeiGe() (string, float64) {
111 | niuniu := u.Length
112 | reduce := math.Abs(hitGlue(niuniu))
113 | niuniu += reduce
114 | return randomChoice([]string{
115 | fmt.Sprintf("哈哈,你这一用道具,牛牛就像是被激发了潜能,增加了%.2fcm!看来今天是个大日子呢!", reduce),
116 | fmt.Sprintf("你这是用了什么神奇的道具?牛牛竟然增加了%.2fcm,简直是牛气冲天!", reduce),
117 | fmt.Sprintf("使用道具后,你的牛牛就像是开启了加速模式,一下增加了%.2fcm,这成长速度让人惊叹!", reduce),
118 | }), niuniu
119 | }
120 |
121 | func (u *userInfo) usePhilter() (string, float64) {
122 | niuniu := u.Length
123 | reduce := math.Abs(hitGlue(niuniu))
124 | niuniu -= reduce
125 | return randomChoice([]string{
126 | fmt.Sprintf("你使用媚药,咿呀咿呀一下使当前长度发生了一些变化,当前长度%.2f", niuniu),
127 | fmt.Sprintf("看来你追求的是‘微观之美’,故意使用道具让牛牛凹进去了%.2fcm!", reduce),
128 | fmt.Sprintf("缩小奇迹’在你身上发生了,牛牛凹进去了%.2fcm,你的选择真是独特!", reduce),
129 | }), niuniu
130 | }
131 |
132 | func (u *userInfo) useArtifact(adduserniuniu float64) (string, float64, float64) {
133 | myLength := u.Length
134 | difference := myLength - adduserniuniu
135 | var (
136 | change float64
137 | )
138 | if difference > 0 {
139 | change = hitGlue(myLength + adduserniuniu)
140 | } else {
141 | change = hitGlue((myLength + adduserniuniu) / 2)
142 | }
143 | myLength += change
144 | return randomChoice([]string{
145 | fmt.Sprintf("凭借神秘道具的力量,你让对方在你的长度面前俯首称臣!你的长度增加了%.2fcm,当前长度达到了%.2fcm", change, myLength),
146 | fmt.Sprintf("神器在手,天下我有!你使用道具后,长度猛增%.2fcm,现在的总长度是%.2fcm,无人能敌!", change, myLength),
147 | fmt.Sprintf("这就是道具的魔力!你轻松增加了%.2fcm,让对手望尘莫及,当前长度为%.2fcm!", change, myLength),
148 | fmt.Sprintf("道具一出,谁与争锋!你的长度因道具而增长%.2fcm,现在的长度是%.2fcm,霸气尽显!", change, myLength),
149 | fmt.Sprintf("使用道具的你,如同获得神助!你的长度增长了%.2fcm,达到%.2fcm的惊人长度,胜利自然到手!", change, myLength),
150 | }), myLength, adduserniuniu - change/1.3
151 | }
152 |
153 | func (u *userInfo) useShenJi(adduserniuniu float64) (string, float64, float64) {
154 | myLength := u.Length
155 | difference := myLength - adduserniuniu
156 | var (
157 | change float64
158 | )
159 | if difference > 0 {
160 | change = hitGlue(myLength + adduserniuniu)
161 | } else {
162 | change = hitGlue((myLength + adduserniuniu) / 2)
163 | }
164 | myLength -= change
165 | var r string
166 | if myLength > 0 {
167 | r = randomChoice([]string{
168 | fmt.Sprintf("哦吼!?看来你的牛牛因为使用了神秘道具而缩水了呢🤣🤣🤣!缩小了%.2fcm!", change),
169 | fmt.Sprintf("哈哈,看来这个道具有点儿调皮,让你的长度缩水了%.2fcm!现在你的长度是%.2fcm,下次可得小心使用哦!", change, myLength),
170 | fmt.Sprintf("使用道具后,你的牛牛似乎有点儿害羞,缩水了%.2fcm!现在的长度是%.2fcm,希望下次它能挺直腰板!", change, myLength),
171 | fmt.Sprintf("哎呀,这个道具的效果有点儿意外,你的长度减少了%.2fcm,现在只有%.2fcm了!下次选道具可得睁大眼睛!", change, myLength),
172 | })
173 | } else {
174 | r = randomChoice([]string{
175 | fmt.Sprintf("哦哟,小姐姐真是玩得一手好游戏,使用道具后数值又降低了%.2fcm,小巧得更显魅力!", change),
176 | fmt.Sprintf("看来小姐姐喜欢更加精致的风格,使用道具后,数值减少了%.2fcm,更加迷人了!", change),
177 | fmt.Sprintf("小姐姐的每一次变化都让人惊喜,使用道具后,数值减少了%.2fcm,更加优雅动人!", change),
178 | fmt.Sprintf("小姐姐这是在展示什么是真正的精致小巧,使用道具后,数值减少了%.2fcm,美得不可方物!", change),
179 | })
180 | }
181 | return r, myLength, adduserniuniu + 0.7*change
182 | }
183 |
184 | func (u *userInfo) applyProp(props string) error {
185 | propsMap := map[string]struct {
186 | itemCount *int
187 | errMsg string
188 | }{
189 | "伟哥": {&u.WeiGe, "你还没有伟哥呢,不能使用"},
190 | "媚药": {&u.Philter, "你还没有媚药呢,不能使用"},
191 | "击剑神器": {&u.Artifact, "你还没有击剑神器呢,不能使用"},
192 | "击剑神稽": {&u.ShenJi, "你还没有击剑神稽呢,不能使用"},
193 | }
194 |
195 | if propInfo, ok := propsMap[props]; ok {
196 | return u.useItem(propInfo.itemCount, propInfo.errMsg)
197 | }
198 | return ErrPropNotFound
199 | }
200 |
201 | func (u *userInfo) useItem(itemCount *int, errMsg string) error {
202 | if *itemCount > 0 {
203 | *itemCount--
204 | return nil
205 | }
206 | return errors.New(errMsg)
207 | }
208 |
209 | func (u *userInfo) checkProps(props, propSort string) error {
210 | validProps := map[string][]string{
211 | "dajiao": daJiaoProps,
212 | "jj": jjPorps,
213 | }
214 |
215 | // 检查是否是有效道具类别
216 | validPropsList, ok := validProps[propSort]
217 | if !ok {
218 | return ErrInvalidPropType
219 | }
220 |
221 | validPropsMap := make(map[string]struct{})
222 | for _, prop := range validPropsList {
223 | validPropsMap[prop] = struct{}{}
224 | }
225 |
226 | // 如果道具属于有效道具,返回 nil
227 | if _, exists := validPropsMap[props]; exists {
228 | return nil
229 | }
230 |
231 | // 检查是否相反
232 | conflictingProps := daJiaoProps
233 | if propSort == "dajiao" {
234 | conflictingProps = jjPorps
235 | }
236 |
237 | // 如果道具属于冲突集合,返回
238 | for _, conflictProp := range conflictingProps {
239 | if props == conflictProp {
240 | return ErrInvalidPropUsageScope
241 | }
242 | }
243 |
244 | return ErrPropNotFound
245 | }
246 |
247 | func (u *userInfo) purchaseItem(n int) (int, error) {
248 | var (
249 | money int
250 | err error
251 | )
252 | switch n {
253 | case 1:
254 | money = 300
255 | u.WeiGe += 5
256 | case 2:
257 | money = 300
258 | u.Philter += 5
259 | case 3:
260 | money = 500
261 | u.Artifact += 2
262 | case 4:
263 | money = 500
264 | u.ShenJi += 2
265 | default:
266 | err = errors.New("无效的选择")
267 | }
268 | return money, err
269 | }
270 |
271 | func (u *userInfo) processDaJiao(props string) (string, error) {
272 | var (
273 | messages string
274 | info userInfo
275 | err error
276 | f float64
277 | )
278 | info = *u
279 | if props != "" {
280 | err := u.checkProps(props, "dajiao")
281 | if err != nil {
282 | return "", err
283 | }
284 | if err := u.applyProp(props); err != nil {
285 | return "", err
286 | }
287 | }
288 | switch {
289 | case u.WeiGe-info.WeiGe != 0:
290 | messages, f = u.useWeiGe()
291 | u.Length = f
292 |
293 | case u.Philter-info.Philter != 0:
294 | messages, f = u.usePhilter()
295 | u.Length = f
296 |
297 | default:
298 | messages, f = hitGlueNiuNiu(u.Length)
299 | u.Length = f
300 | }
301 | return messages, err
302 | }
303 |
304 | func (u *userInfo) processJJ(adduserniuniu *userInfo, props string) (string, error) {
305 | var (
306 | fencingResult string
307 | f float64
308 | f1 float64
309 | info userInfo
310 | err error
311 | )
312 | info = *u
313 | if props != "" {
314 | err := u.checkProps(props, "jj")
315 | if err != nil {
316 | return "", err
317 | }
318 | if err := u.applyProp(props); err != nil {
319 | return "", err
320 | }
321 | }
322 | switch {
323 | case u.ShenJi-info.ShenJi != 0:
324 | fencingResult, f, f1 = u.useShenJi(adduserniuniu.Length)
325 | u.Length = f
326 | adduserniuniu.Length = f1
327 |
328 | case u.Artifact-info.Artifact != 0:
329 | fencingResult, f, f1 = u.useArtifact(adduserniuniu.Length)
330 | u.Length = f
331 | adduserniuniu.Length = f1
332 |
333 | default:
334 | fencingResult, f, f1 = fencing(u.Length, adduserniuniu.Length)
335 | u.Length = f
336 | adduserniuniu.Length = f1
337 | }
338 | return fencingResult, err
339 | }
340 |
341 | func (db *model) newLength() float64 {
342 | return float64(rand.Intn(9)+1) + (float64(rand.Intn(100)) / 100)
343 | }
344 |
345 | func (db *model) getWordNiuNiu(gid, uid int64) (*userInfo, error) {
346 | db.RLock()
347 | defer db.RUnlock()
348 |
349 | var u userInfo
350 | err := db.sql.Find(strconv.FormatInt(gid, 10), &u, query, uid)
351 | return &u, err
352 | }
353 |
354 | func (db *model) setWordNiuNiu(gid int64, u *userInfo) error {
355 | db.Lock()
356 | defer db.Unlock()
357 | err := db.sql.Insert(strconv.FormatInt(gid, 10), u)
358 | if err != nil {
359 | err = db.sql.Create(strconv.FormatInt(gid, 10), &userInfo{})
360 | if err != nil {
361 | return err
362 | }
363 | err = db.sql.Insert(strconv.FormatInt(gid, 10), u)
364 | }
365 | return err
366 | }
367 |
368 | func (db *model) deleteWordNiuNiu(gid, uid int64) error {
369 | db.Lock()
370 | defer db.Unlock()
371 | return db.sql.Del(strconv.FormatInt(gid, 10), query, uid)
372 | }
373 |
374 | func (db *model) getAllNiuNiuOfGroup(gid int64) (users, error) {
375 | db.Lock()
376 | defer db.Unlock()
377 | var user userInfo
378 | var useras users
379 | err := db.sql.FindFor(fmt.Sprintf("%d", gid), &user, "",
380 | func() error {
381 | newUser := user
382 | useras = append(useras, &newUser)
383 | return nil
384 | })
385 | return useras, err
386 | }
387 |
388 | func (db *model) setNiuNiuAuction(gid int64, u *AuctionInfo) error {
389 | db.Lock()
390 | defer db.Unlock()
391 | err := db.sql.Insert(fmt.Sprintf("auction_%d", gid), u)
392 | if err != nil {
393 | err = db.sql.Create(fmt.Sprintf("auction_%d", gid), &AuctionInfo{})
394 | if err != nil {
395 | return err
396 | }
397 | err = db.sql.Insert(fmt.Sprintf("auction_%d", gid), u)
398 | }
399 | return err
400 | }
401 |
402 | func (db *model) deleteNiuNiuAuction(gid int64, id uint) error {
403 | db.Lock()
404 | defer db.Unlock()
405 | return db.sql.Del(fmt.Sprintf("auction_%d", gid), "WHERE id = ?", id)
406 | }
407 |
408 | func (db *model) getAllNiuNiuAuction(gid int64) ([]AuctionInfo, error) {
409 | db.RLock()
410 | defer db.RUnlock()
411 | var user AuctionInfo
412 | var useras []AuctionInfo
413 | err := db.sql.FindFor(fmt.Sprintf("auction_%d", gid), &user, "",
414 | func() error {
415 | useras = append(useras, user)
416 | return nil
417 | })
418 |
419 | return useras, err
420 | }
421 |
--------------------------------------------------------------------------------
/niu/test_test.go:
--------------------------------------------------------------------------------
1 | package niu
2 |
3 | import "testing"
4 |
5 | func TestCreateUserInfoByProps(t *testing.T) {
6 | user := &userInfo{
7 | UID: 123,
8 | Length: 12,
9 | WeiGe: 2,
10 | }
11 | err := user.applyProp("媚药")
12 | if err != nil {
13 | t.Error(err)
14 | }
15 | t.Log("成功-----", user)
16 | }
17 |
18 | func TestCheckProp(t *testing.T) {
19 | user := &userInfo{
20 | UID: 123,
21 | Length: 12,
22 | WeiGe: 2,
23 | }
24 | err := user.checkProps("击剑", "jj")
25 | if err != nil {
26 | t.Error(err)
27 | }
28 | t.Log("成功")
29 | }
30 |
--------------------------------------------------------------------------------
/niu/utils.go:
--------------------------------------------------------------------------------
1 | // Package niu 牛牛大作战
2 | package niu
3 |
4 | import (
5 | "fmt"
6 | "math"
7 | "math/rand"
8 |
9 | "github.com/FloatTech/AnimeAPI/wallet"
10 | )
11 |
12 | func randomChoice(options []string) string {
13 | return options[rand.Intn(len(options))]
14 | }
15 |
16 | func profit(niuniu float64) (money int, t bool, message string) {
17 | switch {
18 | case 0 < niuniu && niuniu <= 15:
19 | message = randomChoice([]string{
20 | "你的牛牛太小啦",
21 | "这么小的牛牛就要肩负起这么大的责任吗?快去打胶吧!",
22 | })
23 | case niuniu > 15:
24 | money = int(niuniu * 10)
25 | message = randomChoice([]string{
26 | fmt.Sprintf("你的牛牛已经离你而去了,你赚取了%d个%s", money, wallet.GetWalletName()),
27 | fmt.Sprintf("啊!你的牛☞已经没啦🤣,为了这点钱就出卖你的牛牛可真不值,你赚取了%d个%s", money, wallet.GetWalletName()),
28 | })
29 | t = true
30 | case niuniu <= 0 && niuniu >= -15:
31 | message = randomChoice([]string{
32 | "你的牛牛太小啦",
33 | "这么小的牛牛就要肩负起这么大的责任吗?快去找别人玩吧!",
34 | })
35 | case niuniu < -15:
36 | money = int(math.Abs(niuniu * 10))
37 | message = randomChoice([]string{
38 | fmt.Sprintf("此世做了女孩子来世来当男孩子(bushi),你赚取了%d个%s", money, wallet.GetWalletName()),
39 | fmt.Sprintf("呜呜呜,不哭不哭当女孩子不委屈的,你赚取了%d个%s", money, wallet.GetWalletName()),
40 | })
41 | t = true
42 | }
43 | return
44 | }
45 |
46 | func hitGlueNiuNiu(niuniu float64) (string, float64) {
47 | probability := rand.Intn(100 + 1)
48 | reduce := math.Abs(hitGlue(niuniu))
49 | switch {
50 | case probability <= 40:
51 | niuniu += reduce
52 | return randomChoice([]string{
53 | fmt.Sprintf("你嘿咻嘿咻一下,促进了牛牛发育,牛牛增加%.2fcm了呢!", reduce),
54 | fmt.Sprintf("你打了个舒服痛快的🦶呐,牛牛增加了%.2fcm呢!", reduce),
55 | }), niuniu
56 | case probability <= 60:
57 | return randomChoice([]string{
58 | "你打了个🦶,但是什么变化也没有,好奇怪捏~",
59 | "你的牛牛刚开始变长了,可过了一会又回来了,什么变化也没有,好奇怪捏~",
60 | }), niuniu
61 | default:
62 | niuniu -= reduce
63 | if niuniu < 0 {
64 | return randomChoice([]string{
65 | fmt.Sprintf("哦吼!?看来你的牛牛凹进去了%.2fcm呢!", reduce),
66 | fmt.Sprintf("你突发恶疾!你的牛牛凹进去了%.2fcm!", reduce),
67 | fmt.Sprintf("笑死,你因为打🦶过度导致牛牛凹进去了%.2fcm!🤣🤣🤣", reduce),
68 | }), niuniu
69 | }
70 | return randomChoice([]string{
71 | fmt.Sprintf("阿哦,你过度打🦶,牛牛缩短%.2fcm了呢!", reduce),
72 | fmt.Sprintf("你的牛牛变长了很多,你很激动地继续打🦶,然后牛牛缩短了%.2fcm呢!", reduce),
73 | fmt.Sprintf("小打怡情,大打伤身,强打灰飞烟灭!你过度打🦶,牛牛缩短了%.2fcm捏!", reduce),
74 | }), niuniu
75 | }
76 | }
77 |
78 | func generateRandomString(niuniu float64) string {
79 | switch {
80 | case niuniu <= -100:
81 | return "wtf?你已经进化成魅魔了!魅魔在击剑时有20%的几率消耗自身长度吞噬对方牛牛呢。"
82 | case niuniu <= -50:
83 | return "嗯....好像已经穿过了身体吧..从另一面来看也可以算是凸出来的吧?"
84 | case niuniu <= -25:
85 | return randomChoice([]string{
86 | "这名女生,你的身体很健康哦!",
87 | "WOW,真的凹进去了好多呢!",
88 | "你已经是我们女孩子的一员啦!",
89 | })
90 | case niuniu <= -10:
91 | return randomChoice([]string{
92 | "你已经是一名女生了呢,",
93 | "从女生的角度来说,你发育良好(,",
94 | "你醒啦?你已经是一名女孩子啦!",
95 | "唔...可以放进去一根手指了都...",
96 | })
97 | case niuniu <= 0:
98 | return randomChoice([]string{
99 | "安了安了,不要伤心嘛,做女生有什么不好的啊。",
100 | "不哭不哭,摸摸头,虽然很难再长出来,但是请不要伤心啦啊!",
101 | "加油加油!我看好你哦!",
102 | "你醒啦?你现在已经是一名女孩子啦!",
103 | })
104 | case niuniu <= 10:
105 | return randomChoice([]string{
106 | "你行不行啊?细狗!",
107 | "虽然短,但是小小的也很可爱呢。",
108 | "像一只蚕宝宝。",
109 | "长大了。",
110 | })
111 | case niuniu <= 25:
112 | return randomChoice([]string{
113 | "唔...没话说",
114 | "已经很长了呢!",
115 | })
116 | case niuniu <= 50:
117 | return randomChoice([]string{
118 | "话说这种真的有可能吗?",
119 | "厚礼谢!",
120 | })
121 | case niuniu <= 100:
122 | return randomChoice([]string{
123 | "已经突破天际了嘛...",
124 | "唔...这玩意应该不会变得比我高吧?",
125 | "你这个长度会死人的...!",
126 | "你马上要进化成牛头人了!!",
127 | "你是什么怪物,不要过来啊!!",
128 | })
129 | default:
130 | return "惊世骇俗!你已经进化成牛头人了!牛头人在击剑时有20%的几率消耗自身长度吞噬对方牛牛呢。"
131 | }
132 | }
133 |
134 | // fencing 击剑对决逻辑,返回对决结果和myLength的变化值
135 | func fencing(myLength, oppoLength float64) (string, float64, float64) {
136 | devourLimit := 0.27
137 |
138 | probability := rand.Intn(100) + 1
139 |
140 | switch {
141 | case oppoLength <= -100 && myLength > 0 && 10 < probability && probability <= 20:
142 | change := hitGlue(oppoLength) + rand.Float64()*math.Log2(math.Abs(0.5*(myLength+oppoLength)))
143 | myLength += change
144 | myLength *= 0.85
145 | return fmt.Sprintf("对方身为魅魔诱惑了你,你同化成魅魔!当前长度%.2fcm!", -myLength), -myLength, oppoLength
146 |
147 | case oppoLength >= 100 && myLength > 0 && 10 < probability && probability <= 20:
148 | change := math.Min(math.Abs(devourLimit*myLength), math.Abs(1.5*myLength))
149 | myLength += change
150 | myLength *= 0.85
151 | return fmt.Sprintf("对方以牛头人的荣誉摧毁了你的牛牛!当前长度%.2fcm!", myLength), myLength, oppoLength
152 |
153 | case myLength <= -100 && oppoLength > 0 && 10 < probability && probability <= 20:
154 | change := hitGlue(myLength+oppoLength) + rand.Float64()*math.Log2(math.Abs(0.5*(myLength+oppoLength)))
155 | oppoLength -= change
156 | myLength -= change
157 | myLength *= 0.85
158 | return fmt.Sprintf("你身为魅魔诱惑了对方,吞噬了对方部分长度!当前长度%.2fcm!", myLength), myLength, oppoLength
159 |
160 | case myLength >= 100 && oppoLength > 0 && 10 < probability && probability <= 20:
161 | myLength -= oppoLength
162 | myLength *= 0.85
163 | oppoLength = 0.01
164 | return fmt.Sprintf("你以牛头人的荣誉摧毁了对方的牛牛!当前长度%.2fcm!", myLength), myLength, oppoLength
165 |
166 | default:
167 | return determineResultBySkill(myLength, oppoLength)
168 | }
169 | }
170 |
171 | // determineResultBySkill 根据击剑技巧决定结果
172 | func determineResultBySkill(myLength, oppoLength float64) (string, float64, float64) {
173 | probability := rand.Intn(100) + 1
174 | winProbability := calculateWinProbability(myLength, oppoLength) * 100
175 | return applySkill(myLength, oppoLength,
176 | float64(probability) <= winProbability)
177 | }
178 |
179 | // calculateWinProbability 计算胜率
180 | func calculateWinProbability(heightA, heightB float64) float64 {
181 | pA := 0.9
182 | heightRatio := math.Max(heightA, heightB) / math.Min(heightA, heightB)
183 | reductionRate := 0.1 * (heightRatio - 1)
184 | reduction := pA * reductionRate
185 |
186 | adjustedPA := pA - reduction
187 | return math.Max(adjustedPA, 0.01)
188 | }
189 |
190 | // applySkill 应用击剑技巧并生成结果
191 | func applySkill(myLength, oppoLength float64, increaseLength1 bool) (string, float64, float64) {
192 | reduce := fence(oppoLength)
193 | // 兜底操作
194 | if reduce == 0 {
195 | reduce = rand.Float64() + float64(rand.Intn(3))
196 | }
197 | if increaseLength1 {
198 | myLength += reduce
199 | oppoLength -= 0.8 * reduce
200 | if myLength < 0 {
201 | return fmt.Sprintf("哦吼!?你的牛牛在长大欸!长大了%.2fcm!", reduce), myLength, oppoLength
202 | }
203 | return fmt.Sprintf("你以绝对的长度让对方屈服了呢!你的长度增加%.2fcm,当前长度%.2fcm!", reduce, myLength), myLength, oppoLength
204 | }
205 | myLength -= reduce
206 | oppoLength += 0.8 * reduce
207 | if myLength < 0 {
208 | return fmt.Sprintf("哦吼!?看来你的牛牛因为击剑而凹进去了呢🤣🤣🤣!凹进去了%.2fcm!", reduce), myLength, oppoLength
209 | }
210 | return fmt.Sprintf("对方以绝对的长度让你屈服了呢!你的长度减少%.2fcm,当前长度%.2fcm!", reduce, myLength), myLength, oppoLength
211 | }
212 |
213 | // fence 根据长度计算减少的长度
214 | func fence(rd float64) float64 {
215 | rd = math.Abs(rd)
216 | if rd == 0 {
217 | rd = 1
218 | }
219 | r := hitGlue(rd)*2 + rand.Float64()*math.Log2(rd)
220 |
221 | return float64(int(r * rand.Float64()))
222 | }
223 |
224 | func hitGlue(l float64) float64 {
225 | if l == 0 {
226 | l = 0.1
227 | }
228 | l = math.Abs(l)
229 | switch {
230 | case l > 1 && l <= 10:
231 | return rand.Float64() * math.Log2(l*2)
232 | case 10 < l && l <= 100:
233 | return rand.Float64() * math.Log2(l*1.5)
234 | case 100 < l && l <= 1000:
235 | return rand.Float64() * (math.Log10(l*1.5) * 2)
236 | case l > 1000:
237 | return rand.Float64() * (math.Log10(l) * 2)
238 | default:
239 | return rand.Float64()
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/novelai/main.go:
--------------------------------------------------------------------------------
1 | // Package novelai ...
2 | package novelai
3 |
4 | import (
5 | "bytes"
6 | "encoding/base64"
7 | "encoding/json"
8 | "io"
9 | "math/rand"
10 | "net/http"
11 | "strconv"
12 | "strings"
13 |
14 | "github.com/FloatTech/floatbox/binary"
15 | "github.com/FloatTech/floatbox/web"
16 | )
17 |
18 | const (
19 | loginapi = "https://api.novelai.net/user/login"
20 | genapi = "https://api.novelai.net/ai/generate-image"
21 | )
22 |
23 | // NovalAI ...
24 | type NovalAI struct {
25 | Tok string `json:"accessToken"`
26 | key string
27 | conf *Payload
28 | }
29 |
30 | // NewNovalAI ...
31 | func NewNovalAI(key string, config *Payload) *NovalAI {
32 | return &NovalAI{
33 | key: key,
34 | conf: config,
35 | }
36 | }
37 |
38 | // Login ...
39 | func (nv *NovalAI) Login() error {
40 | if nv.Tok != "" {
41 | return nil
42 | }
43 | buf := bytes.NewBuffer([]byte(`{"key": "`))
44 | buf.WriteString(nv.key)
45 | buf.WriteString(`"}`)
46 | resp, err := http.Post(loginapi, "application/json", buf)
47 | if err != nil {
48 | return err
49 | }
50 | defer resp.Body.Close()
51 | return json.NewDecoder(resp.Body).Decode(nv)
52 | }
53 |
54 | // Draw ...
55 | func (nv *NovalAI) Draw(tags string) (seed int, tagsproceeded string, img []byte, err error) {
56 | tags = strings.ReplaceAll(tags, ",", ",")
57 | if !strings.Contains(tags, ",") {
58 | tags = strings.ReplaceAll(tags, " ", ",")
59 | }
60 | if tags == "" {
61 | return
62 | }
63 | config := *nv.conf
64 | config.Input = tags
65 | for config.Parameters.Seed == 0 {
66 | config.Parameters.Seed = int(rand.Int31())
67 | }
68 | seed = config.Parameters.Seed
69 | tagsproceeded = tags
70 | buf := binary.SelectWriter()
71 | defer binary.PutWriter(buf)
72 | err = config.WriteJSON(buf)
73 | if err != nil {
74 | return
75 | }
76 | req, err := http.NewRequest("POST", genapi, (*bytes.Buffer)(buf))
77 | if err != nil {
78 | return
79 | }
80 | req.Header.Add("Authorization", "Bearer "+nv.Tok)
81 | req.Header.Add("Content-Length", strconv.Itoa(buf.Len()))
82 | req.Header.Add("Content-Type", "application/json")
83 | req.Header.Add("Origin", "https://novelai.net")
84 | req.Header.Add("Referer", "https://novelai.net/")
85 | req.Header.Add("User-Agent", web.RandUA())
86 | var resp *http.Response
87 | resp, err = http.DefaultClient.Do(req)
88 | if err != nil {
89 | return
90 | }
91 | defer resp.Body.Close()
92 | var b [8]byte
93 | for i := 0; i < 2; i++ {
94 | for b[0] != '\n' {
95 | _, err = resp.Body.Read(b[:1])
96 | if err != nil {
97 | return
98 | }
99 | }
100 | b[0] = 0
101 | }
102 | _, err = resp.Body.Read(b[:5])
103 | if err != nil {
104 | return
105 | }
106 | img, err = io.ReadAll(base64.NewDecoder(base64.StdEncoding, resp.Body))
107 | return
108 | }
109 |
110 | // Para ...
111 | type Para struct {
112 | Width int `json:"width"`
113 | Height int `json:"height"`
114 | Scale int `json:"scale"`
115 | Sampler string `json:"sampler"`
116 | Steps int `json:"steps"`
117 | Seed int `json:"seed"`
118 | NSamples int `json:"n_samples"`
119 | Strength float64 `json:"strength"`
120 | Noise float64 `json:"noise"`
121 | UcPreset int `json:"ucPreset"`
122 | Uc string `json:"uc"`
123 | }
124 |
125 | // Payload ...
126 | type Payload struct {
127 | Input string `json:"input"`
128 | Model string `json:"model"`
129 | Parameters Para `json:"parameters"`
130 | }
131 |
132 | // NewDefaultPayload ...
133 | func NewDefaultPayload() *Payload {
134 | return &Payload{
135 | Model: "safe-diffusion",
136 | Parameters: Para{
137 | Width: 512,
138 | Height: 768,
139 | Scale: 12,
140 | Sampler: "k_euler_ancestral",
141 | Steps: 28,
142 | NSamples: 1,
143 | Strength: 0.7,
144 | Noise: 0.2,
145 | UcPreset: 0,
146 | Uc: "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry",
147 | },
148 | }
149 | }
150 |
151 | // String ...
152 | func (p *Payload) String() string {
153 | b, err := json.Marshal(p)
154 | if err != nil {
155 | return ""
156 | }
157 | return binary.BytesToString(b)
158 | }
159 |
160 | // WriteJSON ...
161 | func (p *Payload) WriteJSON(w io.Writer) error {
162 | return json.NewEncoder(w).Encode(p)
163 | }
164 |
--------------------------------------------------------------------------------
/nsfw/README.md:
--------------------------------------------------------------------------------
1 | # nsfw
2 | > 服务端详见 https://github.com/synodriver/unit_strange_code
--------------------------------------------------------------------------------
/nsfw/api.go:
--------------------------------------------------------------------------------
1 | // Package nsfw 图片鉴赏
2 | package nsfw
3 |
4 | import (
5 | "encoding/json"
6 | "net/url"
7 |
8 | "github.com/FloatTech/floatbox/web"
9 | )
10 |
11 | // Picture ...
12 | type Picture struct {
13 | Sexy float64 `json:"sexy"`
14 | Neutral float64 `json:"neutral"`
15 | Porn float64 `json:"porn"`
16 | Hentai float64 `json:"hentai"`
17 | Drawings float64 `json:"drawings"`
18 | }
19 |
20 | const apiurl = "https://nsfwtag.azurewebsites.net/api/nsfw?url="
21 |
22 | // Classify ...
23 | func Classify(u string) (*Picture, error) {
24 | u = apiurl + url.QueryEscape(u)
25 | var data []byte
26 | data, err := web.GetData(u)
27 | if err != nil {
28 | return nil, err
29 | }
30 | ps := make([]Picture, 1)
31 | err = json.Unmarshal(data, &ps)
32 | if err != nil {
33 | return nil, err
34 | }
35 | return &ps[0], nil
36 | }
37 |
--------------------------------------------------------------------------------
/nsfw/api_test.go:
--------------------------------------------------------------------------------
1 | package nsfw
2 |
3 | import "testing"
4 |
5 | func TestClassify(t *testing.T) {
6 | p, err := Classify("https://1mg.obfs.dev/")
7 | if err != nil {
8 | t.Fatal(err)
9 | }
10 | t.Log(p)
11 | // t.Fail()
12 | }
13 |
--------------------------------------------------------------------------------
/pixiv/cat.go:
--------------------------------------------------------------------------------
1 | package pixiv
2 |
3 | import (
4 | "crypto/tls"
5 | "encoding/json"
6 | "net"
7 | "net/http"
8 | "net/url"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/FloatTech/floatbox/web"
13 | )
14 |
15 | // Generate API 返回结果
16 | type Generate struct {
17 | Success bool `json:"success"`
18 | ErrorMsg string `json:"error"`
19 | ID int `json:"id"`
20 | Title string `json:"title"`
21 | Artist struct {
22 | ID int `json:"id"`
23 | Name string `json:"name"`
24 | } `json:"artist"`
25 | Multiple bool `json:"multiple"`
26 | OriginalURL string `json:"original_url"`
27 | OriginalURLProxy string `json:"original_url_proxy"`
28 | OriginalUrls []string `json:"original_urls"`
29 | OriginalUrlsProxy []string `json:"original_urls_proxy"`
30 | Thumbnails []string `json:"thumbnails"`
31 | }
32 |
33 | // Cat 调用 pixiv.cat 的 generate API
34 | func Cat(id int64) (*Generate, error) {
35 | data, err := web.RequestDataWithHeaders(&http.Client{
36 | Transport: &http.Transport{
37 | DialTLS: func(_, _ string) (net.Conn, error) {
38 | return tls.Dial("tcp", "66.42.35.2:443", &tls.Config{
39 | ServerName: "api.pixiv.cat",
40 | MaxVersion: tls.VersionTLS12,
41 | })
42 | },
43 | }},
44 | "https://api.pixiv.cat/v1/generate",
45 | "POST",
46 | func(r *http.Request) error {
47 | r.Header.Set("authority", "api.pixiv.cat")
48 | r.Header.Set("accept", "*/*")
49 | r.Header.Set("accept-language", "zh,zh-CN;q=0.9,zh-HK;q=0.8,zh-TW;q=0.7,ja;q=0.6,en;q=0.5,en-GB;q=0.4,en-US;q=0.3")
50 | r.Header.Set("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
51 | r.Header.Set("origin", "https://pixiv.cat")
52 | r.Header.Set("referer", "https://pixiv.cat/")
53 | r.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76")
54 | return nil
55 | },
56 | strings.NewReader("p="+url.QueryEscape("https://www.pixiv.net/member_illust.php?illust_id="+strconv.FormatInt(id, 10))),
57 | )
58 | if err != nil {
59 | return nil, err
60 | }
61 | g := &Generate{}
62 | err = json.Unmarshal(data, g)
63 | if err != nil {
64 | return nil, err
65 | }
66 | return g, nil
67 | }
68 |
--------------------------------------------------------------------------------
/pixiv/cat_test.go:
--------------------------------------------------------------------------------
1 | package pixiv
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestCat(t *testing.T) {
10 | g, err := Cat(81918463)
11 | if err != nil {
12 | t.Fatal(err)
13 | }
14 | assert.Equal(t, true, g.Multiple)
15 | assert.Equal(t, []string{"https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p0.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p1.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p2.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p3.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p4.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p5.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p6.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p7.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p8.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p9.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p10.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p11.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p12.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p13.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p14.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p15.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p16.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p17.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p18.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p19.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p20.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p21.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p22.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p23.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p24.png", "https://i.pximg.net/img-original/img/2020/05/28/19/39/02/81918463_p25.png"}, g.OriginalUrls)
16 | g, err = Cat(79673672)
17 | if err != nil {
18 | t.Fatal(err)
19 | }
20 | assert.Equal(t, false, g.Multiple)
21 | assert.Equal(t, "https://i.pximg.net/img-original/img/2020/02/23/13/26/44/79673672_p0.jpg", g.OriginalURL)
22 | }
23 |
--------------------------------------------------------------------------------
/pixiv/download.go:
--------------------------------------------------------------------------------
1 | package pixiv
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "net"
8 | "net/http"
9 | "net/url"
10 | "os"
11 | "strconv"
12 | "strings"
13 | "sync"
14 |
15 | "github.com/FloatTech/floatbox/binary"
16 | "github.com/FloatTech/floatbox/math"
17 | "github.com/FloatTech/floatbox/process"
18 | "github.com/FloatTech/floatbox/web"
19 | )
20 |
21 | // CacheDir ...
22 | const CacheDir = "data/pixiv/"
23 |
24 | func init() {
25 | err := os.MkdirAll(CacheDir, 0755)
26 | if err != nil {
27 | panic(err)
28 | }
29 | }
30 |
31 | // Path 图片本地缓存路径
32 | func (i *Illust) Path(page int) string {
33 | u := i.ImageUrls[page]
34 | f := CacheDir + u[strings.LastIndex(u, "/")+1:]
35 | return f
36 | }
37 |
38 | // DownloadToCache 多线程下载第 page 页到 i.Path(page), 返回 error
39 | func (i *Illust) DownloadToCache(page int) error {
40 | return i.Download(page, i.Path(page))
41 | }
42 |
43 | // Download 多线程下载 page 页到 filepath, 返回 error
44 | func (i *Illust) Download(page int, path string) error {
45 | const slicecap int64 = 65536
46 | u := i.ImageUrls[page]
47 | // 获取IP地址
48 | domain, err := url.Parse(u)
49 | if err != nil {
50 | return err
51 | }
52 |
53 | header := http.Header{
54 | "Host": []string{domain.Host},
55 | "Referer": []string{"https://www.pixiv.net/"},
56 | "User-Agent": []string{"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0"},
57 | "Cache-Control": []string{"no-cache"},
58 | }
59 |
60 | // 请求 Header
61 | headreq, err := http.NewRequest("HEAD", u, nil)
62 | if err != nil {
63 | return err
64 | }
65 | headreq.Header = header.Clone()
66 | client := web.NewPixivClient()
67 | headresp, err := client.Do(headreq)
68 | if err != nil {
69 | return err
70 | }
71 |
72 | contentlength, err := strconv.ParseInt(headresp.Header.Get("Content-Length"), 10, 64)
73 | _ = headresp.Body.Close()
74 | if err != nil {
75 | return err
76 | }
77 |
78 | // 多线程下载
79 | var wg sync.WaitGroup
80 | var start int64
81 | errs := make(chan error, 8)
82 | buf := make(net.Buffers, 0, contentlength/slicecap+1)
83 | writers := make([]*binary.Writer, 0, contentlength/slicecap+1)
84 | index := 0
85 | for end := math.Min(start+slicecap, contentlength); ; end += slicecap {
86 | wg.Add(1)
87 | buf = append(buf, nil)
88 | writers = append(writers, nil)
89 | if end > contentlength {
90 | end = contentlength
91 | }
92 | go func(start int64, end int64, index int) {
93 | // fmt.Println(contentlength, start, end)
94 | for failedtimes := 0; failedtimes < 3; failedtimes++ {
95 | req, err := http.NewRequest("GET", u, nil)
96 | if err != nil {
97 | errs <- err
98 | process.SleepAbout1sTo2s()
99 | continue
100 | }
101 | req.Header = header.Clone()
102 | req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end-1))
103 | resp, err := client.Do(req)
104 | if err != nil {
105 | errs <- err
106 | process.SleepAbout1sTo2s()
107 | continue
108 | }
109 | w := binary.SelectWriter()
110 | _, err = io.CopyN(w, resp.Body, end-start)
111 | _ = resp.Body.Close()
112 | if err != nil {
113 | errs <- err
114 | binary.PutWriter(w)
115 | process.SleepAbout1sTo2s()
116 | continue
117 | }
118 | buf[index] = w.Bytes()
119 | writers[index] = w
120 | if err != nil {
121 | errs <- err
122 | process.SleepAbout1sTo2s()
123 | continue
124 | }
125 | break
126 | }
127 | wg.Done()
128 | }(start, end, index)
129 | if end == contentlength {
130 | break
131 | }
132 | start = end
133 | index++
134 | }
135 | msg := ""
136 | go func() {
137 | for err := range errs {
138 | msg += err.Error() + "&"
139 | }
140 | }()
141 | wg.Wait()
142 | close(errs)
143 | if msg != "" {
144 | err = errors.New(msg[:len(msg)-1])
145 | } else {
146 | f, err := os.Create(path)
147 | if err != nil {
148 | return err
149 | }
150 | _, err = io.Copy(f, &buf)
151 | _ = f.Close()
152 | if err != nil {
153 | _ = os.Remove(path)
154 | }
155 | }
156 | for _, w := range writers {
157 | if w != nil {
158 | binary.PutWriter(w)
159 | }
160 | }
161 | return err
162 | }
163 |
--------------------------------------------------------------------------------
/pixiv/download_test.go:
--------------------------------------------------------------------------------
1 | package pixiv
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "os"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestDownloadToCache(t *testing.T) {
13 | illust, err := Works(96415148)
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 | err = illust.DownloadToCache(0)
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 | data, err := os.ReadFile(illust.Path(0))
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 | m := md5.Sum(data)
26 | ms := hex.EncodeToString(m[:])
27 | assert.Equal(t, "bc635c27d278c414ca8347487d005b6d", ms)
28 | }
29 |
--------------------------------------------------------------------------------
/pixiv/pixiv.go:
--------------------------------------------------------------------------------
1 | // Package pixiv pixiv相关api
2 | package pixiv
3 |
4 | import (
5 | "fmt"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/FloatTech/floatbox/binary"
10 | "github.com/FloatTech/floatbox/web"
11 | "github.com/tidwall/gjson"
12 | )
13 |
14 | // Illust 插画结构体
15 | type Illust struct {
16 | Pid int64 `db:"pid"`
17 | Title string `db:"title"`
18 | Caption string `db:"caption"`
19 | Tags string `db:"tags"`
20 | ImageUrls []string `db:"image_urls"`
21 | AgeLimit string `db:"age_limit"`
22 | CreatedTime string `db:"created_time"`
23 | UserID int64 `db:"user_id"`
24 | UserName string `db:"user_name"`
25 | }
26 |
27 | // Works 获取插画信息
28 | func Works(id int64) (i *Illust, err error) {
29 | data, err := get("https://www.pixiv.net/ajax/illust/" + strconv.FormatInt(id, 10))
30 | if err != nil {
31 | return nil, err
32 | }
33 | json := gjson.ParseBytes(data).Get("body")
34 | // 如果有"R-18"tag则判断为R-18(暂时)
35 | var ageLimit = "all-age"
36 | for _, tag := range json.Get("tags.tags.#.tag").Array() {
37 | if tag.Str == "R-18" {
38 | ageLimit = "r18"
39 | break
40 | }
41 | }
42 | // 解决json返回带html格式
43 | var caption = strings.ReplaceAll(json.Get("illustComment").Str, "
", "\n")
44 | if index := strings.Index(caption, "<"); index != -1 {
45 | caption = caption[:index]
46 | }
47 | // 解析返回插画信息
48 | i = &Illust{}
49 | i.Pid = json.Get("illustId").Int()
50 | i.Title = json.Get("illustTitle").Str
51 | i.Caption = caption
52 | i.Tags = fmt.Sprintln(json.Get("tags.tags.#.tag").Array())
53 | orgs := json.Get("urls.original")
54 | if orgs.Type != gjson.Null {
55 | u := strings.ReplaceAll(orgs.Str, "_p0.", "_p%d.")
56 | for j := 0; j < int(json.Get("pageCount").Int()); j++ {
57 | i.ImageUrls = append(i.ImageUrls, fmt.Sprintf(u, j))
58 | }
59 | } else { // try third-party API
60 | g, err := Cat(id)
61 | if err == nil {
62 | if g.Multiple {
63 | i.ImageUrls = g.OriginalUrls
64 | } else {
65 | i.ImageUrls = []string{g.OriginalURL}
66 | }
67 | }
68 | }
69 | i.AgeLimit = ageLimit
70 | i.CreatedTime = json.Get("createDate").Str
71 | i.UserID = json.Get("userId").Int()
72 | i.UserName = json.Get("userName").Str
73 | return i, err
74 | }
75 |
76 | // RankValue 搜索元素
77 | type RankValue struct {
78 | /* required, possible rank modes:
79 | - daily (default)
80 | - weekly
81 | - monthly
82 | - rookie
83 | - original
84 | - male
85 | - female
86 | - daily_r18
87 | - weekly_r18
88 | - male_r18
89 | - female_r18
90 | - r18g
91 | */
92 | Mode string
93 | /* optional, possible rank type:
94 | - all (default)
95 | - illust
96 | - ugoira
97 | - manga
98 | */
99 | Type string
100 | Page int
101 | Date string
102 | }
103 |
104 | // Rank 画作排行榜
105 | func (value RankValue) Rank() (r [18]int, err error) {
106 | if value.Mode == "male_r18" || value.Mode == "male" || value.Mode == "female_r18" || value.Mode == "female" {
107 | value.Type = "all"
108 | }
109 | body, err := get(fmt.Sprintf("https://www.pixiv.net/touch/ajax/ranking/illust?mode=%s&type=%s&page=%d&date=%s", value.Mode, value.Type, value.Page, value.Date))
110 | i := 0
111 | gjson.Get(binary.BytesToString(body), "body.ranking").ForEach(func(_, value gjson.Result) bool {
112 | r[i] = int(value.Get("illustId").Int())
113 | i++
114 | return i != 18
115 | })
116 | return
117 | }
118 |
119 | // get 返回请求数据
120 | func get(link string) ([]byte, error) {
121 | return web.RequestDataWith(
122 | web.NewPixivClient(), link, "GET",
123 | "https://www.pixiv.net/",
124 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0",
125 | nil,
126 | )
127 | }
128 |
--------------------------------------------------------------------------------
/qzone/api.go:
--------------------------------------------------------------------------------
1 | // Package qzone QQ空间API
2 | package qzone
3 |
4 | import (
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "net/http"
10 | "net/url"
11 | "regexp"
12 | "strings"
13 |
14 | "github.com/FloatTech/floatbox/binary"
15 | )
16 |
17 | var (
18 | cRe = regexp.MustCompile(`_Callback\((.*)\)`)
19 | )
20 |
21 | const (
22 | userQzoneURL = "https://user.qzone.qq.com"
23 | ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"
24 | contentType = "application/x-www-form-urlencoded"
25 | params = "g_tk=%v"
26 | inpcqqURL = "https://h5.qzone.qq.com/feeds/inpcqq?uin=%v&qqver=5749×tamp=%v"
27 | emotionPublishURL = userQzoneURL + "/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_publish_v6?" + params
28 | uploadImageURL = "https://up.qzone.qq.com/cgi-bin/upload/cgi_upload_image?" + params
29 | msglistURL = userQzoneURL + "/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msglist_v6"
30 | likeURL = userQzoneURL + "/proxy/domain/w.qzone.qq.com/cgi-bin/likes/internal_dolike_app?" + params
31 | ptqrshowURL = "https://ssl.ptlogin2.qq.com/ptqrshow?appid=549000912&e=2&l=M&s=3&d=72&v=4&t=0.31232733520361844&daid=5&pt_3rd_aid=0"
32 | ptqrloginURL = "https://xui.ptlogin2.qq.com/ssl/ptqrlogin?u1=https://qzs.qq.com/qzone/v5/loginsucc.html?para=izone&ptqrtoken=%v&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-1656992258324&js_ver=22070111&js_type=1&login_sig=&pt_uistyle=40&aid=549000912&daid=5&has_onekey=1&&o1vId=1e61428d61cb5015701ad73d5fb59f73"
33 | checkSigURL = "https://ptlogin2.qzone.qq.com/check_sig?pttype=1&uin=%v&service=ptqrlogin&nodirect=1&ptsigx=%v&s_url=https://qzs.qq.com/qzone/v5/loginsucc.html?para=izone&f_url=&ptlang=2052&ptredirect=100&aid=549000912&daid=5&j_later=0&low_login_hour=0®master=0&pt_login_type=3&pt_aid=0&pt_aaid=16&pt_light=0&pt_3rd_aid=0"
34 | )
35 |
36 | // Ptqrshow 获得登录二维码
37 | func Ptqrshow() (data []byte, qrsig string, ptqrtoken string, err error) {
38 | client := &http.Client{
39 | CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
40 | return http.ErrUseLastResponse
41 | },
42 | }
43 | ptqrshowReq, err := http.NewRequest("GET", ptqrshowURL, nil)
44 | if err != nil {
45 | return
46 | }
47 | ptqrshowResp, err := client.Do(ptqrshowReq)
48 | if err != nil {
49 | return
50 | }
51 | defer ptqrshowResp.Body.Close()
52 | for _, v := range ptqrshowResp.Cookies() {
53 | if v.Name == "qrsig" {
54 | qrsig = v.Value
55 | break
56 | }
57 | }
58 | if qrsig == "" {
59 | return
60 | }
61 | ptqrtoken = genderGTK(qrsig, 0)
62 | data, err = io.ReadAll(ptqrshowResp.Body)
63 | return
64 | }
65 |
66 | // Ptqrlogin 登录回调
67 | func Ptqrlogin(qrsig string, qrtoken string) (data []byte, cookie string, err error) {
68 | client := &http.Client{
69 | CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
70 | return http.ErrUseLastResponse
71 | },
72 | }
73 | ptqrloginReq, err := http.NewRequest("GET", fmt.Sprintf(ptqrloginURL, qrtoken), nil)
74 | if err != nil {
75 | return
76 | }
77 | ptqrloginReq.Header.Add("cookie", "qrsig="+qrsig)
78 | ptqrloginResp, err := client.Do(ptqrloginReq)
79 | if err != nil {
80 | return
81 | }
82 | defer ptqrloginResp.Body.Close()
83 | for _, v := range ptqrloginReq.Cookies() {
84 | if v.Value != "" {
85 | cookie += v.Name + "=" + v.Value + ";"
86 | }
87 | }
88 | data, err = io.ReadAll(ptqrloginResp.Body)
89 | return
90 | }
91 |
92 | // LoginRedirect 登录成功回调
93 | func LoginRedirect(redirectURL string) (cookie string, err error) {
94 | client := &http.Client{
95 | CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
96 | return http.ErrUseLastResponse
97 | },
98 | }
99 | u, err := url.Parse(redirectURL)
100 | if err != nil {
101 | return
102 | }
103 | values, err := url.ParseQuery(u.RawQuery)
104 | if err != nil {
105 | return
106 | }
107 | redirectReq, err := http.NewRequest("GET", fmt.Sprintf(checkSigURL, values["uin"][0], values["ptsigx"][0]), nil)
108 | if err != nil {
109 | return
110 | }
111 | redirectResp, err := client.Do(redirectReq)
112 | if err != nil {
113 | return
114 | }
115 | defer redirectResp.Body.Close()
116 | for _, v := range redirectResp.Cookies() {
117 | if v.Value != "" {
118 | cookie += v.Name + "=" + v.Value + ";"
119 | }
120 | }
121 | return
122 | }
123 |
124 | // Manager qq空间信息管理
125 | type Manager struct {
126 | Cookie string
127 | QQ string
128 | Gtk string
129 | Gtk2 string
130 | PSkey string
131 | Skey string
132 | Uin string
133 | }
134 |
135 | // NewManager 初始化信息
136 | func NewManager(cookie string) (m Manager) {
137 | cookie = strings.ReplaceAll(cookie, " ", "")
138 | for _, v := range strings.Split(cookie, ";") {
139 | name, val, f := strings.Cut(v, "=")
140 | if f {
141 | switch name {
142 | case "uin":
143 | m.Uin = val
144 | case "skey":
145 | m.Skey = val
146 | case "p_skey":
147 | m.PSkey = val
148 | }
149 | }
150 | }
151 | m.Gtk = genderGTK(m.Skey, 5381)
152 | m.Gtk2 = genderGTK(m.PSkey, 5381)
153 | m.QQ = strings.TrimPrefix(m.Uin, "o")
154 | m.Cookie = cookie
155 | return
156 | }
157 |
158 | // EmotionPublishRaw 发送说说
159 | func (m *Manager) EmotionPublishRaw(epr EmotionPublishRequest) (result EmotionPublishVo, err error) {
160 | client := &http.Client{}
161 | payload := strings.NewReader(structToStr(epr))
162 | request, err := http.NewRequest("POST", fmt.Sprintf(emotionPublishURL, m.Gtk2), payload)
163 | if err != nil {
164 | return
165 | }
166 | request.Header.Add("referer", userQzoneURL)
167 | request.Header.Add("origin", userQzoneURL)
168 | request.Header.Add("cookie", m.Cookie)
169 | request.Header.Add("user-agent", ua)
170 | request.Header.Add("content-type", contentType)
171 | response, err := client.Do(request)
172 | if err != nil {
173 | return
174 | }
175 | defer response.Body.Close()
176 | err = json.NewDecoder(response.Body).Decode(&result)
177 | return
178 | }
179 |
180 | // UploadImage 上传图片
181 | func (m *Manager) UploadImage(base64img string) (result UploadImageVo, err error) {
182 | uir := UploadImageRequest{
183 | Filename: "filename",
184 | Uin: m.QQ,
185 | Skey: m.Skey,
186 | Zzpaneluin: m.QQ,
187 | PUin: m.QQ,
188 | PSkey: m.PSkey,
189 | Uploadtype: "1",
190 | Albumtype: "7",
191 | Exttype: "0",
192 | Refer: "shuoshuo",
193 | OutputType: "json",
194 | Charset: "utf-8",
195 | OutputCharset: "utf-8",
196 | UploadHd: "1",
197 | HdWidth: "2048",
198 | HdHeight: "10000",
199 | HdQuality: "96",
200 | BackUrls: "http://upbak.photo.qzone.qq.com/cgi-bin/upload/cgi_upload_image,http://119.147.64.75/cgi-bin/upload/cgi_upload_image",
201 | URL: fmt.Sprintf(uploadImageURL, m.Gtk2),
202 | Base64: "1",
203 | Picfile: base64img,
204 | Qzreferrer: userQzoneURL + "/" + m.QQ,
205 | }
206 |
207 | payload := strings.NewReader(structToStr(uir))
208 | client := &http.Client{}
209 | request, err := http.NewRequest("POST", fmt.Sprintf(uploadImageURL, m.Gtk2), payload)
210 | if err != nil {
211 | return
212 | }
213 | request.Header.Add("referer", userQzoneURL)
214 | request.Header.Add("origin", userQzoneURL)
215 | request.Header.Add("cookie", m.Cookie)
216 | request.Header.Add("user-agent", ua)
217 | request.Header.Add("content-type", contentType)
218 | response, err := client.Do(request)
219 | if err != nil {
220 | return
221 | }
222 | defer response.Body.Close()
223 | data, err := io.ReadAll(response.Body)
224 | r := cRe.FindStringSubmatch(binary.BytesToString(data))
225 | if len(r) < 2 {
226 | err = errors.New("上传失败")
227 | return
228 | }
229 | err = json.Unmarshal(binary.StringToBytes(r[1]), &result)
230 | return
231 | }
232 |
233 | // EmotionPublish 发送说说,content是文字,base64imgList是base64图片
234 | func (m *Manager) EmotionPublish(content string, base64imgList []string) (result EmotionPublishVo, err error) {
235 | var (
236 | uir UploadImageVo
237 | picBo string
238 | richval string
239 | richvalList = make([]string, 0, 9)
240 | picBoList = make([]string, 0, 9)
241 | )
242 |
243 | for _, base64img := range base64imgList {
244 | uir, err = m.UploadImage(base64img)
245 | if err != nil {
246 | return
247 | }
248 | picBo, richval, err = getPicBoAndRichval(uir)
249 | if err != nil {
250 | return
251 | }
252 | richvalList = append(richvalList, richval)
253 | picBoList = append(picBoList, picBo)
254 | }
255 |
256 | epr := EmotionPublishRequest{
257 | SynTweetVerson: "1",
258 | Paramstr: "1",
259 | Who: "1",
260 | Con: content,
261 | Feedversion: "1",
262 | Ver: "1",
263 | UgcRight: "1",
264 | ToSign: "0",
265 | Hostuin: m.QQ,
266 | CodeVersion: "1",
267 | Format: "json",
268 | Qzreferrer: userQzoneURL + "/" + m.QQ,
269 | }
270 | if len(base64imgList) > 0 {
271 | epr.Richtype = "1"
272 | epr.Richval = strings.Join(richvalList, "\t")
273 | epr.Subrichtype = "1"
274 | epr.PicBo = strings.Join(picBoList, ",")
275 | }
276 |
277 | result, err = m.EmotionPublishRaw(epr)
278 | return
279 | }
280 |
281 | // EmotionMsglist 获取说说列表
282 | func (m *Manager) EmotionMsglist(num string, replynum string) (mlv MsgListVo, err error) {
283 | mlr := MsgListRequest{
284 | Uin: m.QQ,
285 | Ftype: "0",
286 | Sort: "0",
287 | Num: num,
288 | Replynum: replynum,
289 | GTk: m.Gtk2,
290 | Callback: "_preloadCallback",
291 | CodeVersion: "1",
292 | Format: "json",
293 | NeedPrivateComment: "1",
294 | }
295 | mlv, err = m.EmotionMsglistRaw(mlr)
296 | return
297 | }
298 |
299 | // EmotionMsglistRaw 获取说说列表
300 | func (m *Manager) EmotionMsglistRaw(mlr MsgListRequest) (mlv MsgListVo, err error) {
301 | client := &http.Client{}
302 | request, err := http.NewRequest("GET", msglistURL+"?"+structToStr(mlr), nil)
303 | if err != nil {
304 | return
305 | }
306 | request.Header.Add("referer", userQzoneURL)
307 | request.Header.Add("origin", userQzoneURL)
308 | request.Header.Add("cookie", m.Cookie)
309 | request.Header.Add("user-agent", ua)
310 | request.Header.Add("content-type", contentType)
311 | response, err := client.Do(request)
312 | if err != nil {
313 | return
314 | }
315 | defer response.Body.Close()
316 | err = json.NewDecoder(response.Body).Decode(&mlv)
317 | return
318 | }
319 |
320 | // LikeRaw 空间点赞(貌似只能给自己点赞,预留)
321 | func (m *Manager) LikeRaw(lr LikeRequest) (err error) {
322 | client := &http.Client{}
323 | payload := strings.NewReader(structToStr(lr))
324 | request, err := http.NewRequest("POST", likeURL, payload)
325 | if err != nil {
326 | return
327 | }
328 | request.Header.Add("referer", userQzoneURL)
329 | request.Header.Add("origin", userQzoneURL)
330 | request.Header.Add("cookie", m.Cookie)
331 | request.Header.Add("user-agent", ua)
332 | request.Header.Add("content-type", contentType)
333 | response, err := client.Do(request)
334 | if err != nil {
335 | return
336 | }
337 | defer response.Body.Close()
338 | data, err := io.ReadAll(request.Body)
339 | if err != nil {
340 | return
341 | }
342 | fmt.Printf("data:%s\n", data)
343 | return
344 | }
345 |
346 | func getPicBoAndRichval(data UploadImageVo) (picBo, richval string, err error) {
347 | var flag bool
348 | if data.Ret != 0 {
349 | err = errors.New("上传失败")
350 | return
351 | }
352 | _, picBo, flag = strings.Cut(data.Data.URL, "&bo=")
353 | if !flag {
354 | err = errors.New("上传图片返回的地址错误")
355 | return
356 | }
357 | richval = fmt.Sprintf(",%s,%s,%s,%d,%d,%d,,%d,%d", data.Data.Albumid, data.Data.Lloc, data.Data.Sloc, data.Data.Type, data.Data.Height, data.Data.Width, data.Data.Height, data.Data.Width)
358 | return
359 | }
360 |
--------------------------------------------------------------------------------
/qzone/api_test.go:
--------------------------------------------------------------------------------
1 | package qzone
2 |
3 | import (
4 | "encoding/base64"
5 | "os"
6 | "strings"
7 | "testing"
8 | "time"
9 |
10 | "github.com/FloatTech/floatbox/binary"
11 | )
12 |
13 | var (
14 | cookie = "RK=u8/BwhugZ1; ptcz=a8641719c2bac979d89df84627a14407d3947821484c23765b1f692443c0d23f; pgv_pvid=6474350135; iip=0; logTrackKey=b4c60d611df64dfaa180ebd43aea3c6f; __Q_w_s_hat_seed=1; _tc_unionid=e66b4f13-ea92-4afb-b5d0-6694f8c2b443; pac_uid=1_1156544355; QZ_FE_WEBP_SUPPORT=1; __Q_w_s__QZN_TodoMsgCnt=1; o_cookie=1156544355; feeds_selector=2; zzpaneluin=; zzpanelkey=; _qpsvr_localtk=0.33152714365333447; pgv_info=ssid=s1907750736; uin=o1776620359; skey=@vwaG3O59W; p_uin=o1776620359; pt4_token=lRUQWfNSsWVim2G5hdqtVo2rtuGIyBZS26Bjf08gcJ8_; p_skey=iywhVczQpsvg6hcUH8bty53Ge0SBcdkBN7nM5eBp-Z0_; Loading=Yes; cpu_performance_v8=15"
15 | )
16 |
17 | func TestManager_PublishEmotion(t *testing.T) {
18 | type args struct {
19 | Content string
20 | }
21 | m := NewManager(cookie)
22 | gotResult, err := m.EmotionPublish("test", nil)
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 | t.Logf("gotResult:%#v\n", gotResult)
27 | }
28 |
29 | func TestManager_UploadImage(t *testing.T) {
30 | m := NewManager(cookie)
31 | path := `D:\Documents\Pictures\日南葵\日南葵1.jpg`
32 | srcByte, err := os.ReadFile(path)
33 | if err != nil {
34 | return
35 | }
36 | picBase64 := base64.StdEncoding.EncodeToString(srcByte)
37 | if err != nil {
38 | t.Fatal(err)
39 | }
40 | gotResult, err := m.UploadImage(picBase64)
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 | t.Logf("gotResult:%#v\n", gotResult)
45 | }
46 |
47 | func TestManager_Msglist(t *testing.T) {
48 | m := NewManager(cookie)
49 | gotResult, err := m.EmotionMsglist("20", "100")
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 | t.Logf("gotResult:%#v\n", gotResult)
54 | }
55 |
56 | func TestLogin(t *testing.T) {
57 | var (
58 | qrsig string
59 | ptqrtoken string
60 | ptqrloginCookie string
61 | redirectCookie string
62 | data []byte
63 | err error
64 | )
65 | data, qrsig, ptqrtoken, err = Ptqrshow()
66 | if err != nil {
67 | t.Fatal(err)
68 | }
69 | err = os.WriteFile("ptqrcode.png", data, 0666)
70 | if err != nil {
71 | t.Fatal(err)
72 | }
73 | LOOP:
74 | for {
75 | time.Sleep(2 * time.Second)
76 | data, ptqrloginCookie, err = Ptqrlogin(qrsig, ptqrtoken)
77 | if err != nil {
78 | t.Fatal(err)
79 | }
80 | t.Logf("ptqrloginCookie:%v\n", ptqrloginCookie)
81 | text := binary.BytesToString(data)
82 | t.Logf("text:%v\n", text)
83 | switch {
84 | case strings.Contains(text, "二维码已失效"):
85 | t.Fatal("二维码已失效, 登录失败")
86 | return
87 | case strings.Contains(text, "登录成功"):
88 | _ = os.Remove("ptqrcode.png")
89 | dealedCheckText := strings.ReplaceAll(text, "'", "")
90 | redirectURL := strings.Split(dealedCheckText, ",")[2]
91 | redirectCookie, err = LoginRedirect(redirectURL)
92 | if err != nil {
93 | t.Fatal(err)
94 | }
95 | t.Logf("ptqrloginCookie:%v\n", redirectCookie)
96 | break LOOP
97 | }
98 | }
99 | m := NewManager(ptqrloginCookie + redirectCookie)
100 | t.Logf("m:%#v\n", m)
101 | err = os.WriteFile("cookie.txt", binary.StringToBytes(ptqrloginCookie+redirectCookie), 0666)
102 | if err != nil {
103 | t.Fatal(err)
104 | }
105 | gotResult, err := m.EmotionPublish("真好", nil)
106 | if err != nil {
107 | t.Fatal(err)
108 | }
109 | t.Logf("gotResult:%#v\n", gotResult)
110 | }
111 |
--------------------------------------------------------------------------------
/qzone/types.go:
--------------------------------------------------------------------------------
1 | package qzone
2 |
3 | // EmotionPublishRequest 发说说请求体
4 | type EmotionPublishRequest struct {
5 | CodeVersion string `json:"code_version"`
6 | Con string `json:"con"`
7 | Feedversion string `json:"feedversion"`
8 | Format string `json:"format"`
9 | Hostuin string `json:"hostuin"`
10 | Paramstr string `json:"paramstr"`
11 | PicBo string `json:"pic_bo"`
12 | PicTemplate string `json:"pic_template"`
13 | Qzreferrer string `json:"qzreferrer"`
14 | Richtype string `json:"richtype"`
15 | Richval string `json:"richval"`
16 | SpecialURL string `json:"special_url"`
17 | Subrichtype string `json:"subrichtype"`
18 | SynTweetVerson string `json:"syn_tweet_verson"`
19 | ToSign string `json:"to_sign"`
20 | UgcRight string `json:"ugc_right"`
21 | Ver string `json:"ver"`
22 | Who string `json:"who"`
23 | }
24 |
25 | // EmotionPublishVo 发说说响应体
26 | type EmotionPublishVo struct {
27 | Activity []interface{} `json:"activity"`
28 | Attach interface{} `json:"attach"`
29 | AuthFlag int `json:"auth_flag"`
30 | Code int `json:"code"`
31 | Conlist []Conlist `json:"conlist"`
32 | Content string `json:"content"`
33 | Message string `json:"message"`
34 | OurlInfo interface{} `json:"ourl_info"`
35 | PicTemplate string `json:"pic_template"`
36 | Right int `json:"right"`
37 | Secret int `json:"secret"`
38 | Signin int `json:"signin"`
39 | Smoothpolicy Smoothpolicy1 `json:"smoothpolicy"`
40 | Subcode int `json:"subcode"`
41 | T1Icon int `json:"t1_icon"`
42 | T1Name string `json:"t1_name"`
43 | T1Ntime int `json:"t1_ntime"`
44 | T1Source int `json:"t1_source"`
45 | T1SourceName string `json:"t1_source_name"`
46 | T1SourceURL string `json:"t1_source_url"`
47 | T1Tid string `json:"t1_tid"`
48 | T1Time string `json:"t1_time"`
49 | T1Uin int `json:"t1_uin"`
50 | ToTweet int `json:"to_tweet"`
51 | UgcRight int `json:"ugc_right"`
52 | }
53 |
54 | // Conlist 说说文字消息
55 | type Conlist struct {
56 | Con string `json:"con"`
57 | Type int `json:"type"`
58 | }
59 |
60 | // Smoothpolicy1 暂定
61 | type Smoothpolicy1 struct {
62 | Smoothpolicy Smoothpolicy
63 | }
64 |
65 | // Smoothpolicy 暂定
66 | type Smoothpolicy struct {
67 | ComswDisableSosoSearch int `json:"comsw.disable_soso_search"`
68 | L1SwReadFirstCacheOnly int `json:"l1sw.read_first_cache_only"`
69 | L2SwDontGetReplyCmt int `json:"l2sw.dont_get_reply_cmt"`
70 | L2SwMixsvrFrdnumPerTime int `json:"l2sw.mixsvr_frdnum_per_time"`
71 | L3SwHideReplyCmt int `json:"l3sw.hide_reply_cmt"`
72 | L4SwReadTdbOnly int `json:"l4sw.read_tdb_only"`
73 | L5SwReadCacheOnly int `json:"l5sw.read_cache_only"`
74 | }
75 |
76 | // UploadImageRequest 上传图片请求体
77 | type UploadImageRequest struct {
78 | Albumtype string `json:"albumtype"`
79 | BackUrls string `json:"backUrls"`
80 | Base64 string `json:"base64"`
81 | Charset string `json:"charset"`
82 | Exttype string `json:"exttype"`
83 | Filename string `json:"filename"`
84 | HdHeight string `json:"hd_height"`
85 | HdQuality string `json:"hd_quality"`
86 | HdWidth string `json:"hd_width"`
87 | JsonhtmlCallback string `json:"jsonhtml_callback"`
88 | OutputCharset string `json:"output_charset"`
89 | OutputType string `json:"output_type"`
90 | PSkey string `json:"p_skey"`
91 | PUin string `json:"p_uin"`
92 | Picfile string `json:"picfile"`
93 | Qzonetoken string `json:"qzonetoken"`
94 | Qzreferrer string `json:"qzreferrer"`
95 | Refer string `json:"refer"`
96 | Skey string `json:"skey"`
97 | Uin string `json:"uin"`
98 | UploadHd string `json:"upload_hd"`
99 | Uploadtype string `json:"uploadtype"`
100 | URL string `json:"url"`
101 | Zzpanelkey string `json:"zzpanelkey"`
102 | Zzpaneluin string `json:"zzpaneluin"`
103 | }
104 |
105 | // UploadImageVo 上传图片响应体
106 | type UploadImageVo struct {
107 | Data struct {
108 | Pre string `json:"pre"`
109 | URL string `json:"url"`
110 | Lloc string `json:"lloc"`
111 | Sloc string `json:"sloc"`
112 | Type int `json:"type"`
113 | Width int `json:"width"`
114 | Height int `json:"height"`
115 | Albumid string `json:"albumid"`
116 | Totalpic int `json:"totalpic"`
117 | Limitpic int `json:"limitpic"`
118 | OriginURL string `json:"origin_url"`
119 | OriginUUID string `json:"origin_uuid"`
120 | OriginWidth int `json:"origin_width"`
121 | OriginHeight int `json:"origin_height"`
122 | Contentlen int `json:"contentlen"`
123 | } `json:"data"`
124 | Ret int `json:"ret"`
125 | }
126 |
127 | // MsgListRequest 说说列表请求体
128 | type MsgListRequest struct {
129 | Callback string `json:"callback"`
130 | CodeVersion string `json:"code_version"`
131 | Format string `json:"format"`
132 | Ftype string `json:"ftype"`
133 | GTk string `json:"g_tk"`
134 | NeedPrivateComment string `json:"need_private_comment"`
135 | Num string `json:"num"`
136 | Pos string `json:"pos"`
137 | Replynum string `json:"replynum"`
138 | Sort string `json:"sort"`
139 | Uin string `json:"uin"`
140 | }
141 |
142 | // MsgListVo 说说列表响应体
143 | type MsgListVo struct {
144 | AuthFlag int `json:"auth_flag"`
145 | CensorCount int `json:"censor_count"`
146 | CensorFlag int `json:"censor_flag"`
147 | CensorTotal int `json:"censor_total"`
148 | Cginame int `json:"cginame"`
149 | Code int `json:"code"`
150 | Logininfo Logininfo `json:"logininfo"`
151 | Mentioncount int `json:"mentioncount"`
152 | Message string `json:"message"`
153 | Msglist []Msglist `json:"msglist"`
154 | Name string `json:"name"`
155 | Num int `json:"num"`
156 | Sign int `json:"sign"`
157 | Smoothpolicy Smoothpolicy `json:"smoothpolicy"`
158 | Subcode int `json:"subcode"`
159 | Timertotal int `json:"timertotal"`
160 | Total int `json:"total"`
161 | Usrinfo Usrinfo `json:"usrinfo"`
162 | }
163 |
164 | // Logininfo 登录信息
165 | type Logininfo struct {
166 | Name string `json:"name"`
167 | Uin int `json:"uin"`
168 | }
169 |
170 | // Lbs 位置信息
171 | type Lbs struct {
172 | ID string `json:"id"`
173 | Idname string `json:"idname"`
174 | Name string `json:"name"`
175 | PosX string `json:"pos_x"`
176 | PosY string `json:"pos_y"`
177 | }
178 |
179 | // Pic 图片信息
180 | type Pic struct {
181 | AbsolutePosition int `json:"absolute_position"`
182 | BHeight int `json:"b_height"`
183 | BWidth int `json:"b_width"`
184 | Curlikekey string `json:"curlikekey"`
185 | Height int `json:"height"`
186 | PicID string `json:"pic_id"`
187 | Pictype int `json:"pictype"`
188 | Richsubtype int `json:"richsubtype"`
189 | Rtype int `json:"rtype"`
190 | Smallurl string `json:"smallurl"`
191 | Unilikekey string `json:"unilikekey"`
192 | URL1 string `json:"url1"`
193 | URL2 string `json:"url2"`
194 | URL3 string `json:"url3"`
195 | Who int `json:"who"`
196 | Width int `json:"width"`
197 | }
198 |
199 | // Msglist 单个说说的详细信息
200 | type Msglist struct {
201 | Certified int `json:"certified"`
202 | Cmtnum int `json:"cmtnum"`
203 | Conlist []Conlist `json:"conlist"`
204 | Content string `json:"content"`
205 | CreateTime string `json:"createTime"`
206 | CreatedTime int `json:"created_time"`
207 | EditMask int64 `json:"editMask"`
208 | Fwdnum int `json:"fwdnum"`
209 | HasMoreCon int `json:"has_more_con"`
210 | IsEditable int `json:"isEditable"`
211 | Issigin int `json:"issigin"`
212 | Lastmodify int `json:"lastmodify"`
213 | Lbs Lbs `json:"lbs"`
214 | Name string `json:"name"`
215 | PicTemplate string `json:"pic_template"`
216 | Right int `json:"right"`
217 | RtSum int `json:"rt_sum"`
218 | Secret int `json:"secret"`
219 | SourceAppid string `json:"source_appid"`
220 | SourceName string `json:"source_name"`
221 | SourceURL string `json:"source_url"`
222 | T1Source int `json:"t1_source"`
223 | T1Subtype int `json:"t1_subtype"`
224 | T1Termtype int `json:"t1_termtype"`
225 | Tid string `json:"tid"`
226 | UgcRight int `json:"ugc_right"`
227 | Uin int `json:"uin"`
228 | Wbid int `json:"wbid"`
229 | Pic []Pic `json:"pic,omitempty"`
230 | Pictotal int `json:"pictotal,omitempty"`
231 | }
232 |
233 | // Usrinfo 个人信息
234 | type Usrinfo struct {
235 | Concern int `json:"concern"`
236 | CreateTime string `json:"createTime"`
237 | Fans int `json:"fans"`
238 | Followed int `json:"followed"`
239 | Msg string `json:"msg"`
240 | Msgnum int `json:"msgnum"`
241 | Name string `json:"name"`
242 | Uin int `json:"uin"`
243 | }
244 |
245 | // LikeRequest 空间点赞请求体
246 | type LikeRequest struct {
247 | Curkey string `json:"curkey"`
248 | Face string `json:"face"`
249 | From string `json:"from"`
250 | Fupdate string `json:"fupdate"`
251 | Opuin string `json:"opuin"`
252 | Qzreferrer string `json:"qzreferrer"`
253 | Unikey string `json:"unikey"`
254 | Format string `json:"format"`
255 | }
256 |
--------------------------------------------------------------------------------
/qzone/util.go:
--------------------------------------------------------------------------------
1 | package qzone
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "reflect"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | // genderGTK 生成GTK
12 | func genderGTK(sKey string, hash int) string {
13 | for _, s := range sKey {
14 | us, _ := strconv.Atoi(fmt.Sprintf("%d", s))
15 | hash += (hash << 5) + us
16 | }
17 | return fmt.Sprintf("%d", hash&0x7fffffff)
18 | }
19 |
20 | func structToStr(in interface{}) (payload string) {
21 | keys := make([]string, 0, 16)
22 | v := reflect.ValueOf(in)
23 | if v.Kind() == reflect.Ptr {
24 | v = v.Elem()
25 | }
26 | t := v.Type()
27 | for i := 0; i < t.NumField(); i++ {
28 | field := t.Field(i)
29 | get := field.Tag.Get("json")
30 | if get != "" {
31 | keys = append(keys, get+"="+url.QueryEscape(v.Field(i).Interface().(string)))
32 | }
33 | }
34 | payload = strings.Join(keys, "&")
35 | return
36 | }
37 |
--------------------------------------------------------------------------------
/runoob/runoob.go:
--------------------------------------------------------------------------------
1 | // Package runoob 在线运行代码
2 | package runoob
3 |
4 | import (
5 | "encoding/json"
6 | "errors"
7 | "net/http"
8 | "net/url"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | "github.com/FloatTech/floatbox/web"
14 | )
15 |
16 | var (
17 | // Templates ...
18 | Templates = map[string]string{
19 | "py2": "print 'Hello World!'",
20 | "ruby": "puts \"Hello World!\";",
21 | "rb": "puts \"Hello World!\";",
22 | "php": "",
23 | "javascript": "console.log(\"Hello World!\");",
24 | "js": "console.log(\"Hello World!\");",
25 | "node.js": "console.log(\"Hello World!\");",
26 | "scala": "object Main {\n def main(args:Array[String])\n {\n println(\"Hello World!\")\n }\n\t\t\n}",
27 | "go": "package main\n\nimport \"fmt\"\n\nfunc main() {\n fmt.Println(\"Hello, World!\")\n}",
28 | "c": "#include \n\nint main()\n{\n printf(\"Hello, World! \n\");\n return 0;\n}",
29 | "c++": "#include \nusing namespace std;\n\nint main()\n{\n cout << \"Hello World\";\n return 0;\n}",
30 | "cpp": "#include \nusing namespace std;\n\nint main()\n{\n cout << \"Hello World\";\n return 0;\n}",
31 | "java": "public class HelloWorld {\n public static void main(String []args) {\n System.out.println(\"Hello World!\");\n }\n}",
32 | "rust": "fn main() {\n println!(\"Hello World!\");\n}",
33 | "rs": "fn main() {\n println!(\"Hello World!\");\n}",
34 | "c#": "using System;\nnamespace HelloWorldApplication\n{\n class HelloWorld\n {\n static void Main(string[] args)\n {\n Console.WriteLine(\"Hello World!\");\n }\n }\n}",
35 | "cs": "using System;\nnamespace HelloWorldApplication\n{\n class HelloWorld\n {\n static void Main(string[] args)\n {\n Console.WriteLine(\"Hello World!\");\n }\n }\n}",
36 | "csharp": "using System;\nnamespace HelloWorldApplication\n{\n class HelloWorld\n {\n static void Main(string[] args)\n {\n Console.WriteLine(\"Hello World!\");\n }\n }\n}",
37 | "shell": "echo 'Hello World!'",
38 | "bash": "echo 'Hello World!'",
39 | "erlang": "% escript will ignore the first line\n\nmain(_) ->\n io:format(\"Hello World!~n\").",
40 | "perl": "print \"Hello, World!\n\";",
41 | "python": "print(\"Hello, World!\")",
42 | "py": "print(\"Hello, World!\")",
43 | "swift": "var myString = \"Hello, World!\"\nprint(myString)",
44 | "lua": "var myString = \"Hello, World!\"\nprint(myString)",
45 | "pascal": "runcode Hello;\nbegin\n writeln ('Hello, world!')\nend.",
46 | "kotlin": "fun main(args : Array){\n println(\"Hello World!\")\n}",
47 | "kt": "fun main(args : Array){\n println(\"Hello World!\")\n}",
48 | "r": "myString <- \"Hello, World!\"\nprint ( myString)",
49 | "vb": "Module Module1\n\n Sub Main()\n Console.WriteLine(\"Hello World!\")\n End Sub\n\nEnd Module",
50 | "typescript": "const hello : string = \"Hello World!\"\nconsole.log(hello)",
51 | "ts": "const hello : string = \"Hello World!\"\nconsole.log(hello)",
52 | }
53 | // LangTable ...
54 | LangTable = map[string][2]string{
55 | "py2": {"0", "py"},
56 | "ruby": {"1", "rb"},
57 | "rb": {"1", "rb"},
58 | "php": {"3", "php"},
59 | "javascript": {"4", "js"},
60 | "js": {"4", "js"},
61 | "node.js": {"4", "js"},
62 | "scala": {"5", "scala"},
63 | "go": {"6", "go"},
64 | "c": {"7", "c"},
65 | "c++": {"7", "cpp"},
66 | "cpp": {"7", "cpp"},
67 | "java": {"8", "java"},
68 | "rust": {"9", "rs"},
69 | "rs": {"9", "rs"},
70 | "c#": {"10", "cs"},
71 | "cs": {"10", "cs"},
72 | "csharp": {"10", "cs"},
73 | "shell": {"10", "sh"},
74 | "bash": {"10", "sh"},
75 | "erlang": {"12", "erl"},
76 | "perl": {"14", "pl"},
77 | "python": {"15", "py3"},
78 | "py": {"15", "py3"},
79 | "swift": {"16", "swift"},
80 | "lua": {"17", "lua"},
81 | "pascal": {"18", "pas"},
82 | "kotlin": {"19", "kt"},
83 | "kt": {"19", "kt"},
84 | "r": {"80", "r"},
85 | "vb": {"84", "vb"},
86 | "typescript": {"1010", "ts"},
87 | "ts": {"1010", "ts"},
88 | }
89 | )
90 |
91 | // RunOOB ...
92 | type RunOOB string
93 |
94 | // NewRunOOB ...
95 | func NewRunOOB(token string) RunOOB {
96 | return RunOOB(token)
97 | }
98 |
99 | type result struct {
100 | Output string `json:"output"`
101 | Errors string `json:"errors"`
102 | }
103 |
104 | // Run ...
105 | func (ro RunOOB) Run(code string, lang string, stdin string) (string, error) {
106 | // 对菜鸟api发送数据并返回结果
107 | api := "https://www.runoob.com/try/compile2.php"
108 | runType, ok := LangTable[lang]
109 | if !ok {
110 | return "", errors.New("no such language")
111 | }
112 |
113 | header := http.Header{
114 | "Content-Type": []string{"application/x-www-form-urlencoded; charset=UTF-8"},
115 | "Origin": []string{"https://www.runoob.com"},
116 | "Referer": []string{"https://www.runoob.com/try/runcode.php?"},
117 | "User-Agent": []string{web.RandUA()},
118 | }
119 |
120 | val := url.Values{
121 | "code": []string{code},
122 | "token": []string{string(ro)},
123 | "stdin": []string{stdin},
124 | "language": []string{runType[0]},
125 | "fileext": []string{runType[1]},
126 | }
127 |
128 | // 发送请求
129 | client := &http.Client{
130 | Timeout: time.Minute,
131 | }
132 |
133 | request, _ := http.NewRequest("POST", api, strings.NewReader(val.Encode()))
134 | request.Header = header
135 | resp, err := client.Do(request)
136 | if err != nil {
137 | return "", err
138 | }
139 | defer resp.Body.Close()
140 | if resp.StatusCode != http.StatusOK {
141 | return "", errors.New("status code " + strconv.Itoa(resp.StatusCode))
142 | }
143 | var r result
144 | err = json.NewDecoder(resp.Body).Decode(&r)
145 | if err != nil {
146 | return "", err
147 | }
148 | // 结果处理
149 | e := strings.Trim(r.Errors, "\n")
150 | if e != "" {
151 | return "", errors.New(e)
152 | }
153 | return r.Output, nil
154 | }
155 |
--------------------------------------------------------------------------------
/runoob/runoob_test.go:
--------------------------------------------------------------------------------
1 | package runoob
2 |
3 | import "testing"
4 |
5 | func TestRun(t *testing.T) {
6 | ro := NewRunOOB("066417defb80d038228de76ec581a50a")
7 | r, err := ro.Run(`package main
8 |
9 | import "fmt"
10 |
11 | func main() {
12 | fmt.Println("Hello, World!aaa")
13 | }`, "go", "")
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 | if r != "Hello, World!aaa\n" {
18 | t.Fail()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/setu/default.go:
--------------------------------------------------------------------------------
1 | package setu
2 |
3 | import (
4 | "os"
5 | "time"
6 |
7 | "github.com/FloatTech/floatbox/file"
8 | "github.com/FloatTech/floatbox/web"
9 | )
10 |
11 | // DefaultPoolDir ...
12 | const DefaultPoolDir = "data/setupool"
13 |
14 | // DefaultPool ...
15 | var DefaultPool = func() *Pool {
16 | p, err := NewPool(DefaultPoolDir, func(s string) (string, error) {
17 | typ := DefaultPoolDir + "/" + s
18 | if file.IsNotExist(typ) {
19 | err := os.MkdirAll(typ, 0755)
20 | if err != nil {
21 | return "", err
22 | }
23 | }
24 | return "https://img.moehu.org/pic.php?id=pc", nil
25 | }, web.GetData, time.Minute)
26 | if err != nil {
27 | panic(err)
28 | }
29 | return p
30 | }()
31 |
--------------------------------------------------------------------------------
/setu/pool.go:
--------------------------------------------------------------------------------
1 | // Package setu 统一图床
2 | package setu
3 |
4 | import (
5 | "bytes"
6 | "encoding/binary"
7 | "errors"
8 | "image"
9 | _ "image/gif" // register decode gif
10 | _ "image/jpeg" // register decode jpeg
11 | _ "image/png" // register decode png
12 | "math/rand"
13 | "os"
14 | "time"
15 |
16 | _ "golang.org/x/image/webp" // register decode webp
17 |
18 | "github.com/FloatTech/floatbox/file"
19 | "github.com/corona10/goimagehash"
20 | base14 "github.com/fumiama/go-base16384"
21 | "github.com/sirupsen/logrus"
22 | )
23 |
24 | // Pool ...
25 | type Pool struct {
26 | folder string
27 | rolimg func(string) (string, error) // (typ) path
28 | getdat func(string) ([]byte, error) // (path) imgbytes
29 | timeout time.Duration
30 | }
31 |
32 | var (
33 | // ErrNilFolder ...
34 | ErrNilFolder = errors.New("nil folder")
35 | // ErrNoSuchType ...
36 | ErrNoSuchType = errors.New("no such type")
37 | // ErrEmptyType ...
38 | ErrEmptyType = errors.New("empty type")
39 | )
40 |
41 | // NewPool 新的缓存池
42 | func NewPool(folder string, rolimg func(string) (string, error), getdat func(string) ([]byte, error), timeout time.Duration) (*Pool, error) {
43 | if folder == "" {
44 | return nil, ErrNilFolder
45 | }
46 | if file.IsNotExist(folder) {
47 | err := os.MkdirAll(folder, 0755)
48 | if err != nil {
49 | return nil, err
50 | }
51 | }
52 | if folder[len(folder)-1] != '/' {
53 | folder += "/"
54 | }
55 | return &Pool{
56 | folder: folder,
57 | rolimg: rolimg,
58 | getdat: getdat,
59 | timeout: timeout,
60 | }, nil
61 | }
62 |
63 | // Roll 从缓存池随机挑一张
64 | func (p *Pool) Roll(typ string) (string, error) {
65 | d := p.folder + typ
66 | if p.rolimg == nil {
67 | return p.rollLocal(d)
68 | }
69 | var err error
70 | ch := make(chan string, 1)
71 | go func() {
72 | s := ""
73 | s, err = p.rolimg(typ)
74 | ch <- s
75 | close(ch)
76 | }()
77 | select {
78 | case s := <-ch:
79 | if err != nil {
80 | logrus.Warnln("[setu.pool] roll img err:", err)
81 | return p.rollLocal(d)
82 | }
83 | ch := make(chan []byte, 1)
84 | go func() {
85 | var data []byte
86 | data, err = p.getdat(s)
87 | ch <- data
88 | close(ch)
89 | }()
90 | select {
91 | case data := <-ch:
92 | if err != nil {
93 | logrus.Warnln("[setu.pool] get img err:", err)
94 | return p.rollLocal(d)
95 | }
96 | im, ext, err := image.Decode(bytes.NewReader(data))
97 | if err != nil {
98 | logrus.Warnln("[setu.pool] decode img err:", err)
99 | return p.rollLocal(d)
100 | }
101 | dh, err := goimagehash.DifferenceHash(im)
102 | if err != nil {
103 | logrus.Warnln("[setu.pool] hash img err:", err)
104 | return p.rollLocal(d)
105 | }
106 | var buf [8]byte
107 | binary.BigEndian.PutUint64(buf[:], dh.GetHash())
108 | es := base14.EncodeToString(buf[:])
109 | if len(es) != 6*3 {
110 | return p.rollLocal(d)
111 | }
112 | es = es[:5*3] + "." + ext
113 | s = d + "/" + es
114 | if file.IsExist(s) {
115 | return s, nil
116 | }
117 | return s, os.WriteFile(s, data, 0644)
118 | case <-time.After(p.timeout):
119 | return p.rollLocal(d)
120 | }
121 | case <-time.After(p.timeout):
122 | return p.rollLocal(d)
123 | }
124 | }
125 |
126 | // RollLocal ...
127 | func (p *Pool) RollLocal(typ string) (string, error) {
128 | d := p.folder + typ
129 | if file.IsNotExist(d) {
130 | return "", ErrNoSuchType
131 | }
132 | return p.rollLocal(d)
133 | }
134 |
135 | func (p *Pool) rollLocal(d string) (string, error) {
136 | files, err := os.ReadDir(d)
137 | if err != nil {
138 | return "", err
139 | }
140 | if len(files) == 0 {
141 | return "", ErrEmptyType
142 | }
143 | if len(files) == 1 {
144 | if files[0].IsDir() {
145 | return "", ErrEmptyType
146 | }
147 | return d + "/" + files[0].Name(), nil
148 | }
149 | for c := 0; c < 128; c++ {
150 | f := files[rand.Intn(len(files))]
151 | if !f.IsDir() {
152 | return d + "/" + f.Name(), nil
153 | }
154 | }
155 | return "", ErrEmptyType
156 | }
157 |
--------------------------------------------------------------------------------
/setu/pool_test.go:
--------------------------------------------------------------------------------
1 | package setu
2 |
3 | import (
4 | "os"
5 | "testing"
6 | "time"
7 |
8 | "github.com/FloatTech/floatbox/web"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestPool(t *testing.T) {
13 | p, err := NewPool("pool",
14 | func(s string) (string, error) {
15 | return "https://pic.moehu.org/large/ec43126fgy1grkj3zrrxsj24dk2k81l1.jpg", nil
16 | }, web.GetData, time.Minute)
17 | if err != nil {
18 | t.Fatal(err)
19 | }
20 | defer os.RemoveAll("pool")
21 | _, err = p.Roll("a")
22 | if err == nil {
23 | t.Fatal("unexpected success")
24 | }
25 | err = os.Mkdir("pool/a", 0755)
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | s, err := p.Roll("a")
30 | if err != nil {
31 | t.Fatal(err)
32 | }
33 | assert.Equal(t, "pool/a/燃相縸沌慀.jpeg", s)
34 | }
35 |
--------------------------------------------------------------------------------
/shindanmaker/shindanmaker.go:
--------------------------------------------------------------------------------
1 | // Package shindanmaker 基于 https://shindanmaker.com 的 API
2 | package shindanmaker
3 |
4 | import (
5 | "bytes"
6 | "errors"
7 | "fmt"
8 | "mime/multipart"
9 | "net/http"
10 | "strings"
11 | "time"
12 |
13 | xpath "github.com/antchfx/htmlquery"
14 | )
15 |
16 | var (
17 | token = ""
18 | cookie = ""
19 | )
20 |
21 | // Shindanmaker 基于 https://shindanmaker.com 的 API
22 | // id 是的不同页面的 url 里的数字, 例如 https://shindanmaker.com/a/162207 里的 162207
23 | // name 是要被测定的人的名字, 影响测定结果
24 | func Shindanmaker(id int64, name string) (string, error) {
25 | url := fmt.Sprintf("https://shindanmaker.com/%d", id)
26 | // seed 使每一天的结果都不同
27 | now := time.Now()
28 | seed := fmt.Sprintf("%d%d%d", now.Year(), now.Month(), now.Day())
29 | name += seed
30 |
31 | // 刷新 cookie 和 token
32 | if err := refresh(url); err != nil {
33 | return "", err
34 | }
35 |
36 | // 组装参数
37 | client := &http.Client{}
38 | payload := &bytes.Buffer{}
39 | writer := multipart.NewWriter(payload)
40 | _ = writer.WriteField("_token", token)
41 | _ = writer.WriteField("user_input_value_1", name)
42 | _ = writer.WriteField("randname", "名無しのR")
43 | _ = writer.WriteField("type", "name")
44 | _ = writer.Close()
45 | // 发送请求
46 | req, _ := http.NewRequest("POST", url, payload)
47 | req.Header.Add("Cookie", cookie)
48 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
49 | req.Header.Set("Content-Type", writer.FormDataContentType())
50 | resp, err := client.Do(req)
51 | if err != nil {
52 | return "", err
53 | }
54 | defer resp.Body.Close()
55 | // 解析XPATH
56 | doc, err := xpath.Parse(resp.Body)
57 | if err != nil {
58 | return "", err
59 | }
60 | // 取出每个返回的结果
61 | list := xpath.Find(doc, `//*[@id="shindanResult"]`)
62 | if len(list) == 0 {
63 | token = ""
64 | cookie = ""
65 | return "", errors.New("无法查找到结果, 请稍后再试")
66 | }
67 | output := []string{}
68 | for child := list[0].FirstChild; child != nil; child = child.NextSibling {
69 | text := xpath.InnerText(child)
70 | switch {
71 | case text != "":
72 | output = append(output, text)
73 | case child.Data == "img":
74 | img := child.Attr[1].Val
75 | if strings.Contains(img, "http") {
76 | output = append(output, "[CQ:image,file="+img[strings.Index(img, ",")+1:]+"]")
77 | } else {
78 | output = append(output, "[CQ:image,file=base64://"+img[strings.Index(img, ",")+1:]+"]")
79 | }
80 | default:
81 | output = append(output, "\n")
82 | }
83 | }
84 | return strings.ReplaceAll(strings.Join(output, ""), seed, ""), nil
85 | }
86 |
87 | // refresh 刷新 cookie 和 token
88 | func refresh(url string) error {
89 | client := &http.Client{}
90 | req, _ := http.NewRequest("GET", url, nil)
91 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
92 | resp, err := client.Do(req)
93 | if err != nil {
94 | return err
95 | }
96 | // 获取 cookie
97 | doc, err := xpath.Parse(resp.Body)
98 | if err != nil {
99 | return err
100 | }
101 | if token == "" || cookie == "" {
102 | if temp := resp.Header.Values("Set-Cookie"); len(temp) == 0 {
103 | return errors.New("刷新 cookie 时发生错误")
104 | } else if cookie = temp[len(temp)-1]; !strings.Contains(cookie, "_session") {
105 | return errors.New("刷新 cookie 时发生错误")
106 | }
107 | // 获取 token
108 | defer resp.Body.Close()
109 |
110 | list := xpath.Find(doc, `//*[@id="shindanForm"]/input`)
111 | if len(list) == 0 {
112 | return errors.New("刷新 token 时发生错误")
113 | }
114 | token = list[0].Attr[2].Val
115 | }
116 | return nil
117 | }
118 |
--------------------------------------------------------------------------------
/tl/tl.go:
--------------------------------------------------------------------------------
1 | // Package tl 翻译api
2 | package tl
3 |
4 | import (
5 | "github.com/FloatTech/floatbox/binary"
6 | "github.com/FloatTech/floatbox/web"
7 | "github.com/tidwall/gjson"
8 | )
9 |
10 | // Translate ...
11 | func Translate(target string) (string, error) {
12 | data, err := web.GetData("http://api.cloolc.club/fanyi?data=" + target)
13 | if err != nil {
14 | return "", err
15 | }
16 | return binary.BytesToString(binary.NewWriterF(func(w *binary.Writer) {
17 | meanings := gjson.ParseBytes(data).Get("translation").Array()
18 | if len(meanings) == 0 {
19 | w.WriteString("ERROR: 无返回")
20 | return
21 | }
22 | w.WriteString(meanings[0].String())
23 | for _, v := range meanings[1:] {
24 | w.WriteString(", ")
25 | w.WriteString(v.String())
26 | }
27 | })), nil
28 | }
29 |
--------------------------------------------------------------------------------
/tts/baidutts/baidutts.go:
--------------------------------------------------------------------------------
1 | // Package baidutts 百度文字转语音
2 | package baidutts
3 |
4 | import (
5 | "crypto/md5"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "os"
10 | "strconv"
11 | "strings"
12 | "time"
13 |
14 | "github.com/FloatTech/floatbox/binary"
15 | "github.com/FloatTech/floatbox/file"
16 | "github.com/FloatTech/floatbox/web"
17 | "github.com/tidwall/gjson"
18 | )
19 |
20 | const (
21 | grantType = "client_credentials"
22 | tokenURL = "https://aip.baidubce.com/oauth/2.0/token?grant_type=%s&client_id=%s&client_secret=%s"
23 | dbpath = "data/baidutts/"
24 | cachePath = dbpath + "cache/"
25 | ttsURL = "http://tsn.baidu.com/text2audio"
26 | ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
27 | modeName = "百度"
28 | )
29 |
30 | var (
31 | // BaiduttsModes ...
32 | BaiduttsModes = map[int]string{0: "女声", 1: "男声", 3: "度逍遥", 4: "度丫丫"}
33 | )
34 |
35 | // BaiduTTS 百度类
36 | type BaiduTTS struct {
37 | per int
38 | name string
39 | clientID string
40 | clientSecret string
41 | }
42 |
43 | // String 服务名
44 | func (tts *BaiduTTS) String() string {
45 | return modeName + tts.name
46 | }
47 |
48 | // NewBaiduTTS 新的百度语音
49 | func NewBaiduTTS(per int, clientID, clientSecret string) *BaiduTTS {
50 | switch per {
51 | case 0, 1, 3, 4:
52 | return &BaiduTTS{per, BaiduttsModes[per], clientID, clientSecret}
53 | default:
54 | return &BaiduTTS{4, BaiduttsModes[4], clientID, clientSecret}
55 | }
56 | }
57 |
58 | // Speak 返回音频本地路径
59 | func (tts *BaiduTTS) Speak(uid int64, text func() string) (fileName string, err error) {
60 | // 异步
61 | rch := make(chan string, 1)
62 | tch := make(chan string, 1)
63 | // 获得回复
64 | go func() {
65 | rch <- text()
66 | }()
67 | // 取到token
68 | go func() {
69 | var tok string
70 | tok, err = tts.getToken()
71 | tch <- tok
72 | }()
73 | tok := <-tch
74 | if tok == "" {
75 | return
76 | }
77 | fileName, err = getWav(<-rch, tok, 5, tts.per, 5, 5, uid)
78 | if err != nil {
79 | return
80 | }
81 | // 回复
82 | return "file:///" + file.BOTPATH + "/" + cachePath + fileName, nil
83 | }
84 |
85 | func (tts *BaiduTTS) getToken() (accessToken string, err error) {
86 | data, err := web.RequestDataWith(web.NewDefaultClient(), fmt.Sprintf(tokenURL, grantType, tts.clientID, tts.clientSecret), "GET", "", ua, nil)
87 | if err != nil {
88 | return
89 | }
90 | accessToken = gjson.Get(binary.BytesToString(data), "access_token").String()
91 | return
92 | }
93 |
94 | func getWav(tex, tok string, vol, per, spd, pit int, uid int64) (fileName string, err error) {
95 | fileName = strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_baidu.wav"
96 |
97 | cuid := fmt.Sprintf("%x", md5.Sum(binary.StringToBytes(tok)))
98 | payload := strings.NewReader(fmt.Sprintf("tex=%s&lan=zh&ctp=1&vol=%d&per=%d&spd=%d&pit=%d&cuid=%s&tok=%s", tex, vol, per, spd, pit, cuid, tok))
99 |
100 | data, err := web.PostData(ttsURL, "application/x-www-form-urlencoded", payload)
101 | if err != nil {
102 | return
103 | }
104 | if json.Valid(data) {
105 | err = errors.New(gjson.ParseBytes(data).Get("err_msg").String())
106 | return
107 | }
108 | err = os.WriteFile(cachePath+fileName, data, 0666)
109 | return
110 | }
111 |
--------------------------------------------------------------------------------
/tts/baidutts/baidutts_test.go:
--------------------------------------------------------------------------------
1 | package baidutts
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestNewBaiduTTS(t *testing.T) {
9 | clientID := "6ACWsLOg3b7OyGUKGfHZfbXa"
10 | clientSecret := "nA6WP1d05qBoUYqxplNAV1inf8IHGwj9"
11 | tts := NewBaiduTTS(0, clientID, clientSecret)
12 | fmt.Println(tts.Speak(int64(1), func() string {
13 | return "我爱你"
14 | }))
15 | tts = NewBaiduTTS(1, clientID, clientSecret)
16 | fmt.Println(tts.Speak(int64(1), func() string {
17 | return "我爱你"
18 | }))
19 | tts = NewBaiduTTS(3, clientID, clientSecret)
20 | fmt.Println(tts.Speak(int64(1), func() string {
21 | return "我爱你"
22 | }))
23 | tts = NewBaiduTTS(4, clientID, clientSecret)
24 | fmt.Println(tts.Speak(int64(1), func() string {
25 | return "我爱你"
26 | }))
27 | }
28 |
--------------------------------------------------------------------------------
/tts/baidutts/data.go:
--------------------------------------------------------------------------------
1 | package baidutts
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | func init() {
8 | _ = os.MkdirAll(dbpath, 0755)
9 | _ = os.RemoveAll(cachePath)
10 | err := os.MkdirAll(cachePath, 0755)
11 | if err != nil {
12 | panic(err)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tts/lolimi/api.go:
--------------------------------------------------------------------------------
1 | package lolimi
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/FloatTech/floatbox/binary"
8 | "github.com/FloatTech/floatbox/web"
9 | "github.com/tidwall/gjson"
10 | )
11 |
12 | const (
13 | lolimiURL = "https://api.lolimi.cn"
14 | genshinURL = lolimiURL + "/API/yyhc/y.php?msg=%v&speaker=%v"
15 | jiaranURL = lolimiURL + "/API/yyhc/jr.php?msg=%v"
16 | tafeiURL = lolimiURL + "/API/yyhc/taf.php?msg=%v"
17 | dxlURL = lolimiURL + "/API/yyhc/dxl.php?msg=%v"
18 | lyyURL = lolimiURL + "/API/yyhc/lyy.php?msg=%v"
19 | kbURL = lolimiURL + "/API/yyhc/kb.php?msg=%v"
20 | sxcURL = lolimiURL + "/API/yyhc/sxc.php?msg=%v"
21 | czURL = lolimiURL + "/API/yyhc/cz.php?msg=%v"
22 | dzURL = lolimiURL + "/API/yyhc/dz.php?msg=%v"
23 | )
24 |
25 | var (
26 | lolimiMap = map[string]string{
27 | "嘉然": jiaranURL,
28 | "塔菲": tafeiURL,
29 | "东雪莲": dxlURL,
30 | "懒羊羊": lyyURL,
31 | "科比": kbURL,
32 | "孙笑川": sxcURL,
33 | "陈泽": czURL,
34 | "丁真": dzURL,
35 | }
36 | // SoundList ...
37 | SoundList = [...]string{"嘉然", "塔菲", "东雪莲", "懒羊羊", "科比", "孙笑川", "陈泽", "丁真", "空", "荧", "派蒙", "纳西妲", "阿贝多", "温迪", "枫原万叶", "钟离", "荒泷一斗", "八重神子", "艾尔海森", "提纳里", "迪希雅", "卡维", "宵宫", "莱依拉", "赛诺", "诺艾尔", "托马", "凝光", "莫娜", "北斗", "神里绫华", "雷电将军", "芭芭拉", "鹿野院平藏", "五郎", "迪奥娜", "凯亚", "安柏", "班尼特", "琴", "柯莱", "夜兰", "妮露", "辛焱", "珐露珊", "魈", "香菱", "达达利亚", "砂糖", "早柚", "云堇", "刻晴", "丽莎", "迪卢克", "烟绯", "重云", "珊瑚宫心海", "胡桃", "可莉", "流浪者", "久岐忍", "神里绫人", "甘雨", "戴因斯雷布", "优菈", "菲谢尔", "行秋", "白术", "九条裟罗", "雷泽", "申鹤", "迪娜泽黛", "凯瑟琳", "多莉", "坎蒂丝", "萍姥姥", "罗莎莉亚", "留云借风真君", "绮良良", "瑶瑶", "七七", "奥兹", "米卡", "夏洛蒂", "埃洛伊", "博士", "女士", "大慈树王", "三月七", "娜塔莎", "希露瓦", "虎克", "克拉拉", "丹恒", "希儿", "布洛妮娅", "瓦尔特", "杰帕德", "佩拉", "姬子", "艾丝妲", "白露", "星", "穹", "桑博", "伦纳德", "停云", "罗刹", "卡芙卡", "彦卿", "史瓦罗", "螺丝咕姆", "阿兰", "银狼", "素裳", "丹枢", "黑塔", "景元", "帕姆", "可可利亚", "半夏", "符玄", "公输师傅", "奥列格", "青雀", "大毫", "青镞", "费斯曼", "绿芙蓉", "镜流", "信使", "丽塔", "失落迷迭", "缭乱星棘", "伊甸", "伏特加女孩", "狂热蓝调", "莉莉娅", "萝莎莉娅", "八重樱", "八重霞", "卡莲", "第六夜想曲", "卡萝尔", "姬子", "极地战刃", "布洛妮娅", "次生银翼", "理之律者", "真理之律者", "迷城骇兔", "希儿", "魇夜星渊", "黑希儿", "帕朵菲莉丝", "天元骑英", "幽兰黛尔", "德丽莎", "月下初拥", "朔夜观星", "暮光骑士", "明日香", "李素裳", "格蕾修", "梅比乌斯", "渡鸦", "人之律者", "爱莉希雅", "爱衣", "天穹游侠", "琪亚娜", "空之律者", "终焉之律者", "薪炎之律者", "云墨丹心", "符华", "识之律者", "维尔薇", "始源之律者", "芽衣", "雷之律者", "苏莎娜", "阿波尼亚", "陆景和", "莫弈", "夏彦", "左然"}
38 | )
39 |
40 | // TTS 文字转语音最上层方法
41 | func TTS(name string, text string) (recURL string, err error) {
42 | var (
43 | ttsURL string
44 | data []byte
45 | ok bool
46 | )
47 | text = strings.ReplaceAll(text, " ", "")
48 | ttsURL, ok = lolimiMap[name]
49 | if !ok {
50 | ttsURL = fmt.Sprintf(genshinURL, text, name)
51 | } else {
52 | ttsURL = fmt.Sprintf(ttsURL, text)
53 | }
54 | data, err = web.GetData(ttsURL)
55 | if err != nil {
56 | return
57 | }
58 | recURL = gjson.Get(binary.BytesToString(data), "music").String()
59 | return
60 | }
61 |
--------------------------------------------------------------------------------
/tts/lolimi/tts.go:
--------------------------------------------------------------------------------
1 | // Package lolimi https://api.lolimi.cn/
2 | package lolimi
3 |
4 | import (
5 | goBinary "encoding/binary"
6 | "fmt"
7 | "hash/crc64"
8 | "os"
9 | "regexp"
10 | "strconv"
11 |
12 | "github.com/FloatTech/floatbox/binary"
13 | "github.com/FloatTech/floatbox/file"
14 | "github.com/FloatTech/floatbox/web"
15 | "github.com/pkumza/numcn"
16 | "github.com/sirupsen/logrus"
17 | )
18 |
19 | const (
20 | modeName = "桑帛云"
21 | cachePath = "data/lolimi/"
22 | )
23 |
24 | var (
25 | re = regexp.MustCompile(`(\-|\+)?\d+(\.\d+)?`)
26 | )
27 |
28 | func init() {
29 | // _ = os.RemoveAll(cachePath)
30 | err := os.MkdirAll(cachePath, 0755)
31 | if err != nil {
32 | panic(err)
33 | }
34 | }
35 |
36 | // Lolimi 桑帛云 API
37 | type Lolimi struct {
38 | mode int
39 | name string
40 | }
41 |
42 | // String 服务名
43 | func (tts *Lolimi) String() string {
44 | return modeName + tts.name
45 | }
46 |
47 | // NewLolimi 新的桑帛云语音
48 | func NewLolimi(mode int) *Lolimi {
49 | return &Lolimi{
50 | mode: mode,
51 | name: SoundList[mode],
52 | }
53 | }
54 |
55 | // Speak 返回音频 url
56 | func (tts *Lolimi) Speak(_ int64, text func() string) (fileName string, err error) {
57 | t := text()
58 | // 将数字转文字
59 | t = re.ReplaceAllStringFunc(t, func(s string) string {
60 | f, err := strconv.ParseFloat(s, 64)
61 | if err != nil {
62 | logrus.Errorln("[tts]", err)
63 | return s
64 | }
65 | return numcn.EncodeFromFloat64(f)
66 | })
67 | var (
68 | b [8]byte
69 | data []byte
70 | ttsURL string
71 | )
72 | goBinary.LittleEndian.PutUint64(b[:], uint64(tts.mode))
73 | h := crc64.New(crc64.MakeTable(crc64.ISO))
74 | h.Write(b[:])
75 | ttsURL, err = TTS(tts.name, t)
76 | if err != nil {
77 | return
78 | }
79 | _, _ = h.Write(binary.StringToBytes(ttsURL))
80 | n := fmt.Sprintf(cachePath+"%016x.wav", h.Sum64())
81 | if file.IsExist(n) {
82 | fileName = "file:///" + file.BOTPATH + "/" + n
83 | return
84 | }
85 | data, err = web.GetData(ttsURL)
86 | if err != nil {
87 | return
88 | }
89 | err = os.WriteFile(n, data, 0644)
90 | if err != nil {
91 | return
92 | }
93 | fileName = "file:///" + file.BOTPATH + "/" + n
94 | return
95 | }
96 |
--------------------------------------------------------------------------------
/tts/tts.go:
--------------------------------------------------------------------------------
1 | // Package tts 文字转语音库
2 | package tts
3 |
4 | // TTS ...
5 | type TTS interface {
6 | // Speak 返回音频本地路径
7 | Speak(key int64, text func() string) (fileName string, err error)
8 | // String 获得实际使用的回复服务名
9 | String() string
10 | }
11 |
--------------------------------------------------------------------------------
/tts/ttscn/tts.go:
--------------------------------------------------------------------------------
1 | // Package ttscn https://www.text-to-speech.cn/
2 | package ttscn
3 |
4 | import (
5 | "bytes"
6 | _ "embed"
7 | "encoding/json"
8 | "errors"
9 | "net/http"
10 | "net/url"
11 | "strconv"
12 |
13 | "github.com/FloatTech/floatbox/binary"
14 | "github.com/FloatTech/floatbox/web"
15 | )
16 |
17 | // TTS 类
18 | type TTS struct {
19 | language string // 中文(普通话,简体)
20 | voice string // zh-CN-XiaoxiaoNeural
21 | role int
22 | style int
23 | rate int
24 | pitch int
25 | kbitrate string // audio-16khz-32kbitrate-mono-mp3
26 | silence string
27 | styledegree float64 // 1
28 | }
29 |
30 | // LangSpeakers ...
31 | type LangSpeakers struct {
32 | ShortName []string // ShortName 用于 API
33 | LocalName []string // LocalName 用于显示
34 | }
35 |
36 | const (
37 | speakerlistapi = "https://www.text-to-speech.cn/getSpeekList.php"
38 | ttsapi = "https://www.text-to-speech.cn/getSpeek.php"
39 | )
40 |
41 | //go:embed speakers.json
42 | var embededspeakers []byte
43 |
44 | var (
45 | // Langs 语言
46 | Langs = func() (m map[string]*LangSpeakers) {
47 | data, err := web.GetData(speakerlistapi)
48 | if err != nil {
49 | _ = json.Unmarshal(embededspeakers, &m)
50 | return
51 | }
52 | err = json.Unmarshal(data, &m)
53 | if err != nil {
54 | _ = json.Unmarshal(embededspeakers, &m)
55 | return
56 | }
57 | return
58 | }()
59 | // KBRates 质量
60 | KBRates = [...]string{
61 | "audio-16khz-32kbitrate-mono-mp3",
62 | "audio-16khz-128kbitrate-mono-mp3",
63 | "audio-24khz-160kbitrate-mono-mp3",
64 | "audio-48khz-192kbitrate-mono-mp3",
65 | "riff-16khz-16bit-mono-pcm",
66 | "riff-24khz-16bit-mono-pcm",
67 | "riff-48khz-16bit-mono-pcm",
68 | }
69 | )
70 |
71 | // String 服务名
72 | func (tts *TTS) String() string {
73 | return tts.language + tts.voice
74 | }
75 |
76 | // NewTTSCN ...
77 | func NewTTSCN(lang, speaker, kbrate string) (*TTS, error) {
78 | spks, ok := Langs[lang]
79 | if !ok {
80 | return nil, errors.New("no language named " + lang)
81 | }
82 | hasfound := false
83 | for i, s := range spks.LocalName {
84 | if s == speaker {
85 | speaker = spks.ShortName[i]
86 | hasfound = true
87 | break
88 | }
89 | }
90 | if !hasfound {
91 | for _, s := range spks.ShortName {
92 | if s == speaker {
93 | hasfound = true
94 | break
95 | }
96 | }
97 | }
98 | if !hasfound {
99 | return nil, errors.New("no speaker named " + speaker)
100 | }
101 | hasfound = false
102 | for _, s := range KBRates {
103 | if s == kbrate {
104 | hasfound = true
105 | break
106 | }
107 | }
108 | if !hasfound {
109 | return nil, errors.New("no kbrate named " + kbrate)
110 | }
111 | return &TTS{
112 | language: lang,
113 | voice: speaker,
114 | kbitrate: kbrate,
115 | styledegree: 1,
116 | }, nil
117 | }
118 |
119 | type result struct {
120 | Code int `json:"code"`
121 | Msg string `json:"msg"`
122 | Download string `json:"download"`
123 | Author string `json:"author"`
124 | URL string `json:"url"`
125 | }
126 |
127 | // Speak 返回音频本地路径
128 | func (tts *TTS) Speak(_ int64, text func() string) (fileName string, err error) {
129 | q, cl := binary.OpenWriterF(func(w *binary.Writer) {
130 | w.WriteString("language=")
131 | w.WriteString(url.QueryEscape(tts.language))
132 | w.WriteString("&voice=")
133 | w.WriteString(url.QueryEscape(tts.voice))
134 | w.WriteString("&text=")
135 | w.WriteString(url.QueryEscape(text()))
136 | w.WriteString("&role=")
137 | w.WriteString(strconv.Itoa(tts.role))
138 | w.WriteString("&style=")
139 | w.WriteString(strconv.Itoa(tts.style))
140 | w.WriteString("&rate=")
141 | w.WriteString(strconv.Itoa(tts.rate))
142 | w.WriteString("&pitch=")
143 | w.WriteString(strconv.Itoa(tts.pitch))
144 | w.WriteString("&kbitrate=")
145 | w.WriteString(tts.kbitrate)
146 | w.WriteString("&silence=")
147 | w.WriteString(tts.silence)
148 | w.WriteString("&styledegree=")
149 | w.WriteString(strconv.FormatFloat(tts.styledegree, 'f', 2, 64))
150 | })
151 | defer cl()
152 | data, err := web.RequestDataWithHeaders(
153 | web.NewTLS12Client(), ttsapi, "POST", func(r *http.Request) error {
154 | r.Header.Add("accept", "*/*")
155 | r.Header.Add("content-length", strconv.Itoa(len(q)))
156 | r.Header.Add("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
157 | r.Header.Add("origin", "https://www.text-to-speech.cn")
158 | r.Header.Add("referer", "https://www.text-to-speech.cn/")
159 | r.Header.Add("user-agent", web.RandUA())
160 | r.Header.Add("x-requested-with", "XMLHttpRequest")
161 | return nil
162 | }, bytes.NewReader(q))
163 | if err != nil {
164 | return
165 | }
166 | var re result
167 | err = json.Unmarshal(data, &re)
168 | if err != nil {
169 | return
170 | }
171 | if re.Code != 200 {
172 | err = errors.New(re.Msg)
173 | return
174 | }
175 | fileName = re.Download
176 | return
177 | }
178 |
--------------------------------------------------------------------------------
/tts/ttscn/tts_test.go:
--------------------------------------------------------------------------------
1 | package ttscn
2 |
3 | import "testing"
4 |
5 | func TestTTS(t *testing.T) {
6 | tts, err := NewTTSCN("中文(普通话,简体)", "晓双(女 - 儿童)", KBRates[0])
7 | if err != nil {
8 | t.Fatal(err)
9 | }
10 | if tts.voice != "zh-CN-XiaoshuangNeural" {
11 | t.Fatal(tts.voice)
12 | }
13 | fn, err := tts.Speak(0, func() string { return "测试一下。" })
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 | t.Log(fn)
18 | }
19 |
--------------------------------------------------------------------------------
/wallet/wallet.go:
--------------------------------------------------------------------------------
1 | // Package wallet 货币系统
2 | package wallet
3 |
4 | import (
5 | "os"
6 | "strconv"
7 | "sync"
8 | "time"
9 |
10 | "github.com/FloatTech/floatbox/file"
11 | sql "github.com/FloatTech/sqlite"
12 | )
13 |
14 | // Storage 货币系统
15 | type Storage struct {
16 | sync.RWMutex
17 | db sql.Sqlite
18 | }
19 |
20 | // Wallet 钱包
21 | type Wallet struct {
22 | UID int64
23 | Money int
24 | }
25 |
26 | var (
27 | sdb = &Storage{
28 | db: sql.New("data/wallet/wallet.db"),
29 | }
30 | walletName = "Atri币"
31 | )
32 |
33 | func init() {
34 | if file.IsNotExist("data/wallet") {
35 | err := os.MkdirAll("data/wallet", 0755)
36 | if err != nil {
37 | panic(err)
38 | }
39 | }
40 | err := sdb.db.Open(time.Hour * 24)
41 | if err != nil {
42 | panic(err)
43 | }
44 | err = sdb.db.Create("storage", &Wallet{})
45 | if err != nil {
46 | panic(err)
47 | }
48 | }
49 |
50 | // GetWalletName 获取货币名称
51 | func GetWalletName() string {
52 | return walletName
53 | }
54 |
55 | // SetWalletName 设置货币名称
56 | func SetWalletName(name string) {
57 | walletName = name
58 | }
59 |
60 | // GetWalletOf 获取钱包数据
61 | func GetWalletOf(uid int64) (money int) {
62 | return sdb.getWalletOf(uid).Money
63 | }
64 |
65 | // GetGroupWalletOf 获取多人钱包数据
66 | //
67 | // if sort == true,由高到低排序; if sort == false,由低到高排序
68 | func GetGroupWalletOf(sortable bool, uids ...int64) (wallets []Wallet, err error) {
69 | return sdb.getGroupWalletOf(sortable, uids...)
70 | }
71 |
72 | // InsertWalletOf 更新钱包(money > 0 增加,money < 0 减少)
73 | func InsertWalletOf(uid int64, money int) error {
74 | sdb.Lock()
75 | defer sdb.Unlock()
76 | lastMoney := sdb.getWalletOf(uid)
77 | newMoney := lastMoney.Money + money
78 | if newMoney < 0 {
79 | newMoney = 0
80 | }
81 | return sdb.updateWalletOf(uid, newMoney)
82 | }
83 |
84 | // 获取钱包数据 no lock
85 | func (s *Storage) getWalletOf(uid int64) (wallet Wallet) {
86 | uidstr := strconv.FormatInt(uid, 10)
87 | _ = s.db.Find("storage", &wallet, "WHERE uid = ?", uidstr)
88 | return
89 | }
90 |
91 | // 获取钱包数据组
92 | func (s *Storage) getGroupWalletOf(sortable bool, uids ...int64) (wallets []Wallet, err error) {
93 | s.RLock()
94 | defer s.RUnlock()
95 | wallets = make([]Wallet, 0, len(uids))
96 | sort := "ASC"
97 | if sortable {
98 | sort = "DESC"
99 | }
100 | info := Wallet{}
101 | q, sl := sql.QuerySet("WHERE uid", "IN", uids)
102 | err = s.db.FindFor("storage", &info, q+" ORDER BY money "+sort, func() error {
103 | wallets = append(wallets, info)
104 | return nil
105 | }, sl...)
106 | return
107 | }
108 |
109 | // 更新钱包 no lock
110 | func (s *Storage) updateWalletOf(uid int64, money int) (err error) {
111 | return s.db.Insert("storage", &Wallet{
112 | UID: uid,
113 | Money: money,
114 | })
115 | }
116 |
--------------------------------------------------------------------------------
/wenxinAI/erniemodle/erniemodle.go:
--------------------------------------------------------------------------------
1 | // Package erniemodel 百度文心AI大模型
2 | package erniemodel
3 |
4 | import (
5 | "encoding/json"
6 | "errors"
7 | "net/url"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/FloatTech/floatbox/web"
12 | )
13 |
14 | type tokendata struct {
15 | Code int `json:"code"`
16 | Msg string `json:"msg"`
17 | Data string `json:"data"`
18 | }
19 |
20 | // GetToken 获取当天的token
21 | //
22 | // 申请账号链接:https://wenxin.baidu.com/moduleApi/key
23 | //
24 | // clientID为API key,clientSecret为Secret key
25 | //
26 | // token有效时间为24小时
27 | func GetToken(clientID, clientSecret string) (token string, err error) {
28 | requestURL := "https://wenxin.baidu.com/moduleApi/portal/api/oauth/token?grant_type=client_credentials&client_id=" + url.QueryEscape(clientID) + "&client_secret=" + url.QueryEscape(clientSecret)
29 | postData := url.Values{}
30 | postData.Add("name", "ATRI")
31 | postData.Add("language", "golang")
32 | data, err := web.PostData(requestURL, "application/x-www-form-urlencoded", strings.NewReader(postData.Encode()))
33 | if err != nil {
34 | return
35 | }
36 | var parsed tokendata
37 | err = json.Unmarshal(data, &parsed)
38 | if err != nil {
39 | return
40 | }
41 | if parsed.Msg != "success" {
42 | err = errors.New(parsed.Msg + ",code:" + strconv.Itoa(parsed.Code))
43 | return
44 | }
45 | return parsed.Data, nil
46 | }
47 |
48 | type parsed struct {
49 | Code int `json:"code"`
50 | Msg string `json:"msg"`
51 | Data struct {
52 | Result string `json:"result"`
53 | RequestID string `json:"requestId"`
54 | } `json:"data"`
55 | }
56 |
57 | // GetResult 创建任务
58 | //
59 | // token:GetToken函数获取,
60 | //
61 | // model:请求类型
62 | //
63 | // txt:用户输入文本
64 | //
65 | // mindeclen:最小生成长度[1, seq_len]
66 | //
67 | // seqlen:最大生成长度[1, 1000]
68 | //
69 | // seqlen决定生成时间:生成512需要16.3s,生成256需要8.1s,生成128需要4.1s
70 | //
71 | // taskprompt:任务类型(非必需)
72 | //
73 | // model:写作文: 1; 写文案: 2; 写摘要: 3; 对对联: 4; 自由问答: 5; 写小说: 6; 补全文本: 7; 自定义: 8;
74 | //
75 | // task_prompt只支持以下:
76 | // PARAGRAPH:引导模型生成一段文章; SENT:引导模型生成一句话; ENTITY:引导模型生成词组; Summarization:摘要; MT:翻译; Text2Annotation:抽取; Correction:纠错; QA_MRC:阅读理解; Dialogue:对话; QA_Closed_book: 闭卷问答; QA_Multi_Choice:多选问答; QuestionGeneration:问题生成; Paraphrasing:复述; NLI:文本蕴含识别; SemanticMatching:匹配; Text2SQL:文本描述转SQL;TextClassification:文本分类; SentimentClassification:情感分析; zuowen:写作文; adtext:写文案; couplet:对对联; novel:写小说; cloze:文
77 | func GetResult(token string, model int, txt string, mindeclen, seqlen int, taskprompt ...string) (result string, err error) {
78 | requestURL := "https://wenxin.baidu.com/moduleApi/portal/api/rest/1.0/ernie/3.0.2" + strconv.Itoa(model) + "/zeus?" +
79 | "access_token=" + url.QueryEscape(token)
80 | postData := url.Values{}
81 | postData.Add("text", txt)
82 | postData.Add("min_dec_len", strconv.Itoa(mindeclen))
83 | postData.Add("seq_len", strconv.Itoa(seqlen))
84 | postData.Add("topp", "1.0")
85 | postData.Add("task_prompt", taskprompt[0])
86 | data, err := web.PostData(requestURL, "application/x-www-form-urlencoded", strings.NewReader(postData.Encode()))
87 | if err != nil {
88 | return
89 | }
90 | var parsed parsed
91 | err = json.Unmarshal(data, &parsed)
92 | if err != nil {
93 | return
94 | }
95 | if parsed.Msg != "success" {
96 | err = errors.New(parsed.Msg + ",code:" + strconv.Itoa(parsed.Code))
97 | return
98 | }
99 | return parsed.Data.Result, nil
100 | }
101 |
--------------------------------------------------------------------------------
/wenxinAI/ernievilg/ernievilg.go:
--------------------------------------------------------------------------------
1 | // Package ernievilg 百度文心AI画图
2 | package ernievilg
3 |
4 | import (
5 | "encoding/json"
6 | "errors"
7 | "net/url"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/FloatTech/floatbox/web"
12 | )
13 |
14 | type tokendata struct {
15 | Code int `json:"code"`
16 | Msg string `json:"msg"`
17 | Data string `json:"data"`
18 | }
19 |
20 | // GetToken 获取当天的token
21 | //
22 | // 申请账号链接:https://wenxin.baidu.com/moduleApi/key
23 | //
24 | // clientID为API key,clientSecret为Secret key
25 | //
26 | // token有效时间为24小时
27 | func GetToken(clientID, clientSecret string) (token string, err error) {
28 | requestURL := "https://wenxin.baidu.com/moduleApi/portal/api/oauth/token?grant_type=client_credentials&client_id=" + url.QueryEscape(clientID) + "&client_secret=" + url.QueryEscape(clientSecret)
29 | postData := url.Values{}
30 | postData.Add("name", "ATRI")
31 | postData.Add("language", "golang")
32 | data, err := web.PostData(requestURL, "application/x-www-form-urlencoded", strings.NewReader(postData.Encode()))
33 | if err != nil {
34 | return
35 | }
36 | var parsed tokendata
37 | err = json.Unmarshal(data, &parsed)
38 | if err != nil {
39 | return
40 | }
41 | if parsed.Msg != "success" {
42 | err = errors.New(parsed.Msg + ",code:" + strconv.Itoa(parsed.Code))
43 | return
44 | }
45 | return parsed.Data, nil
46 | }
47 |
48 | type workstate struct {
49 | Code int `json:"code"`
50 | Msg string `json:"msg"`
51 | Data struct {
52 | RequestID string `json:"requestId"`
53 | TaskID int `json:"taskId"`
54 | } `json:"data"`
55 | }
56 |
57 | // BuildWork 创建画图任务
58 | //
59 | // token:GetToken函数获取,
60 | //
61 | // keyword:图片描述,长度不超过64个字,prompt指南:https://wenxin.baidu.com/wenxin/docs#Ol7ece95m
62 | //
63 | // picType:图片风格,目前支持风格有:油画、水彩画、卡通、粉笔画、儿童画、蜡笔画
64 | //
65 | // picSize:图片尺寸,目前支持的有:1024*1024 方图、1024*1536 长图、1536*1024 横图。
66 | // 传入的是尺寸数值,非文字。
67 | //
68 | // taskID:任务ID,用于查询结果
69 | func BuildWork(token, keyword, picType, picSize string) (taskID int, err error) {
70 | requestURL := "https://wenxin.baidu.com/moduleApi/portal/api/rest/1.0/ernievilg/v1/txt2img?access_token=" + url.QueryEscape(token) +
71 | "&text=" + url.QueryEscape(keyword) + "&style=" + url.QueryEscape(picType) + "&resolution=" + url.QueryEscape(picSize)
72 | postData := url.Values{}
73 | postData.Add("name", "ATRI")
74 | postData.Add("language", "golang")
75 | data, err := web.PostData(requestURL, "application/x-www-form-urlencoded", strings.NewReader(postData.Encode()))
76 | if err != nil {
77 | return
78 | }
79 | var parsed workstate
80 | err = json.Unmarshal(data, &parsed)
81 | if err != nil {
82 | return
83 | }
84 | if parsed.Msg != "success" {
85 | err = errors.New(parsed.Msg + ",code:" + strconv.Itoa(parsed.Code))
86 | return
87 | }
88 | return parsed.Data.TaskID, nil
89 | }
90 |
91 | type picdata struct {
92 | Code int `json:"code"`
93 | Msg string `json:"msg"`
94 | Data struct {
95 | Img string `json:"img"`
96 | Waiting string `json:"waiting"`
97 | ImgUrls []PicURL `json:"imgUrls"`
98 | CreateTime string `json:"createTime"`
99 | RequestID string `json:"requestId"`
100 | Style string `json:"style"`
101 | Text string `json:"text"`
102 | Resolution string `json:"resolution"`
103 | TaskID int `json:"taskId"`
104 | Status int `json:"status"`
105 | } `json:"data"`
106 | }
107 |
108 | // PicURL ...
109 | type PicURL struct {
110 | Image string `json:"image"`
111 | Score interface{} `json:"score"`
112 | }
113 |
114 | // GetPic 获取图片内容
115 | //
116 | // token由GetToken函数获取,taskID由BuildWork函数获取
117 | //
118 | // PicURL:[x]struct{Image:图片链接,Score:评分}
119 | //
120 | // API会返回x张图片,数量不确定的,随机的。
121 | //
122 | // 评分目前都是null,我不知道有什么用,既然API预留了,我也预留吧
123 | //
124 | // stauts:结果状态,"30s"代表还在排队生成,"0"表示结果OK
125 | func GetPic(token string, taskID int) (picurls []PicURL, status string, err error) {
126 | requestURL := "https://wenxin.baidu.com/moduleApi/portal/api/rest/1.0/ernievilg/v1/getImg?access_token=" + url.QueryEscape(token) +
127 | "&taskId=" + strconv.Itoa(taskID)
128 | postData := url.Values{}
129 | postData.Add("name", "ATRI")
130 | postData.Add("language", "golang")
131 | data, err := web.PostData(requestURL, "application/x-www-form-urlencoded", strings.NewReader(postData.Encode()))
132 | if err != nil {
133 | return
134 | }
135 | var parsed picdata
136 | err = json.Unmarshal(data, &parsed)
137 | if err != nil {
138 | return
139 | }
140 | if parsed.Msg != "success" {
141 | err = errors.New(parsed.Msg + ",code:" + strconv.Itoa(parsed.Code))
142 | return
143 | }
144 | status = parsed.Data.Waiting
145 | picurls = parsed.Data.ImgUrls
146 | return
147 | }
148 |
--------------------------------------------------------------------------------
/yandex/yandex.go:
--------------------------------------------------------------------------------
1 | // Package yandex yandex搜图
2 | package yandex
3 |
4 | import (
5 | "errors"
6 | "net/http"
7 | "net/url"
8 | "strconv"
9 |
10 | xpath "github.com/antchfx/htmlquery"
11 | trshttp "github.com/fumiama/terasu/http"
12 |
13 | "github.com/FloatTech/AnimeAPI/pixiv"
14 | )
15 |
16 | // Yandex yandex搜图
17 | func Yandex(image string) (*pixiv.Illust, error) {
18 | search, _ := url.Parse("https://yandex.com/images/search")
19 | search.RawQuery = url.Values{
20 | "rpt": []string{"imageview"},
21 | "url": []string{image},
22 | "site": []string{"pixiv.net"},
23 | }.Encode()
24 |
25 | // 网络请求
26 | req, _ := http.NewRequest("GET", search.String(), nil)
27 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36")
28 | resp, err := trshttp.DefaultClient.Do(req)
29 | if err != nil {
30 | return nil, err
31 | }
32 | defer resp.Body.Close()
33 |
34 | doc, err := xpath.Parse(resp.Body)
35 | if err != nil {
36 | return nil, err
37 | }
38 | // 取出每个返回的结果
39 | list := xpath.Find(doc, `/html/body/div[3]/div[2]/div[1]/div/div[1]/div[1]/div[2]/div[2]/div/section/div[2]/div[1]/div[1]/a`)
40 | if len(list) != 1 {
41 | return nil, errors.New("Yandex not found")
42 | }
43 | link := list[0].Attr[1].Val
44 | dest, _ := url.Parse(link)
45 | rawid := dest.Query().Get("illust_id")
46 | if rawid == "" {
47 | return nil, errors.New("Yandex not found")
48 | }
49 | // 链接取出PIXIV id
50 | id, _ := strconv.ParseInt(rawid, 10, 64)
51 | if id == 0 {
52 | return nil, errors.New("convert to pid error")
53 | }
54 |
55 | illust, err := pixiv.Works(id)
56 | if err != nil {
57 | return nil, err
58 | }
59 | // 待完善
60 | return illust, nil
61 | }
62 |
--------------------------------------------------------------------------------