├── .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 | --------------------------------------------------------------------------------