├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── Dockerfile
├── LICENSE
├── README.md
├── assert
└── fonts
│ ├── Symbola.ttf
│ └── yasqht.ttf
├── cmd
└── main.go
├── go.mod
├── go.sum
├── k8s
└── deploy.yaml
├── pkg
├── config
│ └── log.go
├── fortune
│ ├── fortune.go
│ └── fortune_test.go
├── plugins
│ ├── almanac
│ │ └── almanac.go
│ ├── bilifan
│ │ ├── bilifan.go
│ │ └── bilifan_test.go
│ ├── caihongpi
│ │ └── caihongpi.go
│ ├── calendar
│ │ ├── calendar.go
│ │ └── calendar_test.go
│ ├── cosplay
│ │ └── cosplay.go
│ ├── dictionary
│ │ └── dictionary.go
│ ├── dujitang
│ │ └── dujitang.go
│ ├── facesave
│ │ └── facesave.go
│ ├── forward
│ │ └── plugin.go
│ ├── haimage
│ │ ├── haimage.go
│ │ └── haimage_test.go
│ ├── hitokoto
│ │ └── hitokoto.go
│ ├── jrrp
│ │ └── jrrp.go
│ ├── lpl
│ │ ├── lpl.go
│ │ └── lpl_test.go
│ ├── mc
│ │ └── mc.go
│ ├── pixiv
│ │ ├── http.go
│ │ ├── pixiv.go
│ │ ├── pixiv.go.bak
│ │ └── random.go.bak
│ ├── random
│ │ └── random.go
│ ├── rss
│ │ ├── manage.go
│ │ ├── manage_test.go
│ │ └── rss.go
│ ├── segmentation
│ │ └── plugin.go
│ ├── sovietjokes
│ │ ├── sj_test.go
│ │ └── sovietjokes.go
│ ├── thecat
│ │ └── thecat.go
│ ├── thedog
│ │ └── thedog.go
│ ├── tiangou
│ │ └── tiangou.go
│ ├── tips
│ │ └── tips.go
│ ├── todayFortune
│ │ ├── test.jpg
│ │ └── todayFortune.go
│ ├── translate
│ │ ├── tr_test.go
│ │ └── translate.go
│ ├── vader
│ │ └── vader.go
│ ├── weather
│ │ ├── weather.go
│ │ └── weather_test.go
│ └── weibolisten
│ │ └── weibo.go
├── rss
│ ├── rss.go
│ └── rss_test.go
└── tools
│ └── image
│ ├── image.go
│ └── image_test.go
└── scripts
└── deploy_docker_test.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 | face
6 | pixiv
7 | mc
8 | # User-specific files
9 | *.rsuser
10 | *.suo
11 | *.user
12 | *.userosscache
13 | *.sln.docstates
14 |
15 | # User-specific files (MonoDevelop/Xamarin Studio)
16 | *.userprefs
17 |
18 | # Mono auto generated files
19 | mono_crash.*
20 |
21 | # Build results
22 | [Dd]ebug/
23 | [Dd]ebugPublic/
24 | [Rr]elease/
25 | [Rr]eleases/
26 | x64/
27 | x86/
28 | [Aa][Rr][Mm]/
29 | [Aa][Rr][Mm]64/
30 | bld/
31 | [Bb]in/
32 | [Oo]bj/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # StyleCop
67 | StyleCopReport.xml
68 |
69 | # Files built by Visual Studio
70 | *_i.c
71 | *_p.c
72 | *_h.h
73 | *.ilk
74 | *.meta
75 | *.obj
76 | *.iobj
77 | *.pch
78 | *.pdb
79 | *.ipdb
80 | *.pgc
81 | *.pgd
82 | *.rsp
83 | *.sbr
84 | *.tlb
85 | *.tli
86 | *.tlh
87 | *.tmp
88 | *.tmp_proj
89 | *_wpftmp.csproj
90 | *.log
91 | *.vspscc
92 | *.vssscc
93 | .builds
94 | *.pidb
95 | *.svclog
96 | *.scc
97 |
98 | # Chutzpah Test files
99 | _Chutzpah*
100 |
101 | # Visual C++ cache files
102 | ipch/
103 | *.aps
104 | *.ncb
105 | *.opendb
106 | *.opensdf
107 | *.sdf
108 | *.cachefile
109 | *.VC.db
110 | *.VC.VC.opendb
111 |
112 | # Visual Studio profiler
113 | *.psess
114 | *.vsp
115 | *.vspx
116 | *.sap
117 |
118 | # Visual Studio Trace Files
119 | *.e2e
120 |
121 | # TFS 2012 Local Workspace
122 | $tf/
123 |
124 | # Guidance Automation Toolkit
125 | *.gpState
126 |
127 | # ReSharper is a .NET coding add-in
128 | _ReSharper*/
129 | *.[Rr]e[Ss]harper
130 | *.DotSettings.user
131 |
132 | # TeamCity is a build add-in
133 | _TeamCity*
134 |
135 | # DotCover is a Code Coverage Tool
136 | *.dotCover
137 |
138 | # AxoCover is a Code Coverage Tool
139 | .axoCover/*
140 | !.axoCover/settings.json
141 |
142 | # Visual Studio code coverage results
143 | *.coverage
144 | *.coveragexml
145 |
146 | # NCrunch
147 | _NCrunch_*
148 | .*crunch*.local.xml
149 | nCrunchTemp_*
150 |
151 | # MightyMoose
152 | *.mm.*
153 | AutoTest.Net/
154 |
155 | # Web workbench (sass)
156 | .sass-cache/
157 |
158 | # Installshield output folder
159 | [Ee]xpress/
160 |
161 | # DocProject is a documentation generator add-in
162 | DocProject/buildhelp/
163 | DocProject/Help/*.HxT
164 | DocProject/Help/*.HxC
165 | DocProject/Help/*.hhc
166 | DocProject/Help/*.hhk
167 | DocProject/Help/*.hhp
168 | DocProject/Help/Html2
169 | DocProject/Help/html
170 |
171 | # Click-Once directory
172 | publish/
173 |
174 | # Publish Web Output
175 | *.[Pp]ublish.xml
176 | *.azurePubxml
177 | # Note: Comment the next line if you want to checkin your web deploy settings,
178 | # but database connection strings (with potential passwords) will be unencrypted
179 | *.pubxml
180 | *.publishproj
181 |
182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
183 | # checkin your Azure Web App publish settings, but sensitive information contained
184 | # in these scripts will be unencrypted
185 | PublishScripts/
186 |
187 | # NuGet Packages
188 | *.nupkg
189 | # NuGet Symbol Packages
190 | *.snupkg
191 | # The packages folder can be ignored because of Package Restore
192 | **/[Pp]ackages/*
193 | # except build/, which is used as an MSBuild target.
194 | !**/[Pp]ackages/build/
195 | # Uncomment if necessary however generally it will be regenerated when needed
196 | #!**/[Pp]ackages/repositories.config
197 | # NuGet v3's project.json files produces more ignorable files
198 | *.nuget.props
199 | *.nuget.targets
200 |
201 | # Microsoft Azure Build Output
202 | csx/
203 | *.build.csdef
204 |
205 | # Microsoft Azure Emulator
206 | ecf/
207 | rcf/
208 |
209 | # Windows Store app package directories and files
210 | AppPackages/
211 | BundleArtifacts/
212 | Package.StoreAssociation.xml
213 | _pkginfo.txt
214 | *.appx
215 | *.appxbundle
216 | *.appxupload
217 |
218 | # Visual Studio cache files
219 | # files ending in .cache can be ignored
220 | *.[Cc]ache
221 | # but keep track of directories ending in .cache
222 | !?*.[Cc]ache/
223 |
224 | # Others
225 | ClientBin/
226 | ~$*
227 | *~
228 | *.dbmdl
229 | *.dbproj.schemaview
230 | *.jfm
231 | *.pfx
232 | *.publishsettings
233 | orleans.codegen.cs
234 |
235 | # Including strong name files can present a security risk
236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
237 | #*.snk
238 |
239 | # Since there are multiple workflows, uncomment next line to ignore bower_components
240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
241 | #bower_components/
242 |
243 | # RIA/Silverlight projects
244 | Generated_Code/
245 |
246 | # Backup & report files from converting an old project file
247 | # to a newer Visual Studio version. Backup files are not needed,
248 | # because we have git ;-)
249 | _UpgradeReport_Files/
250 | Backup*/
251 | UpgradeLog*.XML
252 | UpgradeLog*.htm
253 | ServiceFabricBackup/
254 | *.rptproj.bak
255 |
256 | # SQL Server files
257 | *.mdf
258 | *.ldf
259 | *.ndf
260 |
261 | # Business Intelligence projects
262 | *.rdl.data
263 | *.bim.layout
264 | *.bim_*.settings
265 | *.rptproj.rsuser
266 | *- [Bb]ackup.rdl
267 | *- [Bb]ackup ([0-9]).rdl
268 | *- [Bb]ackup ([0-9][0-9]).rdl
269 |
270 | # Microsoft Fakes
271 | FakesAssemblies/
272 |
273 | # GhostDoc plugin setting file
274 | *.GhostDoc.xml
275 |
276 | # Node.js Tools for Visual Studio
277 | .ntvs_analysis.dat
278 | node_modules/
279 |
280 | # Visual Studio 6 build log
281 | *.plg
282 |
283 | # Visual Studio 6 workspace options file
284 | *.opt
285 |
286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
287 | *.vbw
288 |
289 | # Visual Studio LightSwitch build output
290 | **/*.HTMLClient/GeneratedArtifacts
291 | **/*.DesktopClient/GeneratedArtifacts
292 | **/*.DesktopClient/ModelManifest.xml
293 | **/*.Server/GeneratedArtifacts
294 | **/*.Server/ModelManifest.xml
295 | _Pvt_Extensions
296 |
297 | # Paket dependency manager
298 | .paket/paket.exe
299 | paket-files/
300 |
301 | # FAKE - F# Make
302 | .fake/
303 |
304 | # CodeRush personal settings
305 | .cr/personal
306 |
307 | # Python Tools for Visual Studio (PTVS)
308 | __pycache__/
309 | *.pyc
310 |
311 | # Cake - Uncomment if you are using it
312 | # tools/**
313 | # !tools/packages.config
314 |
315 | # Tabs Studio
316 | *.tss
317 |
318 | # Telerik's JustMock configuration file
319 | *.jmconfig
320 |
321 | # BizTalk build output
322 | *.btp.cs
323 | *.btm.cs
324 | *.odx.cs
325 | *.xsd.cs
326 |
327 | # OpenCover UI analysis results
328 | OpenCover/
329 |
330 | # Azure Stream Analytics local run output
331 | ASALocalRun/
332 |
333 | # MSBuild Binary and Structured Log
334 | *.binlog
335 |
336 | # NVidia Nsight GPU debugger configuration file
337 | *.nvuser
338 |
339 | # MFractors (Xamarin productivity tool) working folder
340 | .mfractor/
341 |
342 | # Local History for Visual Studio
343 | .localhistory/
344 |
345 | # BeatPulse healthcheck temp database
346 | healthchecksdb
347 |
348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
349 | MigrationBackup/
350 |
351 | # Ionide (cross platform F# VS Code tools) working folder
352 | .ionide/
353 | plugin-storage.db
354 | plugin-storage.db.lock
355 | device.json
356 | application.yaml
357 | cmd/__debug_bin
358 | cmd/__debug_bin.exe
359 | scripts/deploy_docker_test.sh
360 | health
361 | jrrp
362 | fortune
363 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch",
9 | "type": "go",
10 | "request": "launch",
11 | "mode": "debug",
12 | "program": "${workspaceFolder}/cmd/main.go",
13 | "cwd": "${workspaceFolder}",
14 | "env": {
15 | "BOT_FORWARD_ADMIN": "1179551960",
16 | "BOT_PIXIV_TOKEN": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJTZG5pdSIsInV1aWQiOiJjNTQ3OGY3MGUxY2E0NmVjODJiOTNiZGVlOWNiYjU2MSIsImlhdCI6MTYxNzA5Mjk0MSwiYWNjb3VudCI6IntcImVtYWlsXCI6XCIxMTc5NTUxOTYwQHFxLmNvbVwiLFwiZ2VuZGVyXCI6LTEsXCJoYXNQcm9uXCI6MCxcImlkXCI6ODE2LFwicGFzc1dvcmRcIjpcIjFkZmViY2NjNmJjNzI4ZTc1MGExZjQ1MjhlY2Q2NjcxXCIsXCJzdGF0dXNcIjowLFwidXNlck5hbWVcIjpcIlNkbml1XCJ9IiwianRpIjoiODE2In0.VgJxTsHwXLMRApBXEW0WOuicsoc3WLLT6BrcN4lmeDk"
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.detectIndentation": false,
3 | "editor.tabSize": 4,
4 | "editor.formatOnPaste": true,
5 | "editor.formatOnSave": true
6 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.16.0-alpine3.13 AS builder
2 | RUN go env -w GO111MODULE=auto \
3 | && go env -w GOPROXY=https://goproxy.cn,direct
4 | WORKDIR /build
5 | COPY ./ .
6 | RUN cd /build && go build -tags netgo -ldflags="-w -s" -o miraigo cmd/main.go
7 |
8 | FROM alpine:latest
9 | LABEL MAINTAINER=github.com/dezhiShen
10 | WORKDIR /data
11 | RUN apk add -U --repository http://mirrors.ustc.edu.cn/alpine/v3.13/main/ tzdata
12 | COPY --from=builder /build/miraigo /usr/bin/miraigo
13 | COPY --from=builder /build/assert /assert
14 | RUN chmod +x /usr/bin/miraigo
15 | VOLUME /data
16 | HEALTHCHECK --interval=5s --timeout=1s --start-period=5s --retries=3 CMD cat /data/health
17 | ENTRYPOINT ["/usr/bin/miraigo"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MiraiGo-Bot-Plugins
2 | MiraiGo-Bot插件库
3 |
4 |
5 | ## 已实现
6 | 名称|描述
7 | -|-
8 | [运势插件](./pkg/plugins/todayFortune)|`.tf` 随机运势,素材等来源[`https://github.com/FloatTech/Plugin-Fortune`](https://github.com/FloatTech/Plugin-Fortune),为了避免流量占用过多,素材复制至-> https://github.com/dezhishen/raw/tree/master/fortune
9 | [彩虹屁](./pkg/plugins/caihongpi)|`.chp` 随机发送一条彩虹屁
10 | [日历](./pkg/plugins/calendar)|`.calendar` 展示今日的日历(阳历,周几,阳历节日,农历,农历节日)
`.calendar Y/N` 启用/禁用定时(早6点)发送
11 | [毒鸡汤](./pkg/plugins/dujitang)|`.djt` 随机发送一条毒鸡汤
12 | [古风小姐姐](./pkg/plugins/haimage)|`.hapic` 随机发送一张古风小姐姐图
13 | [一言](./pkg/plugins/hitokoto)|`.hitokoto`,调用一言接口,随机发一句骚话
14 | [今日人品](./pkg/plugins/jrrp)|`.jrrp`展示今日人品
`.jrrp 7` 显示最多7日的历史人品
15 | [lpl赛事](./pkg/plugins/lpl)|`.lpl`查看最近的赛事
16 | [menhera图片](./pkg/plugins/mc)|`.mc`随机一张menhera酱,她真可爱.jpg
17 | [pixiv](./pkg/plugins/pixiv)|`.pixiv`,随机一张 懂的都懂
18 | [掷骰](./pkg/plugins/random)|`.r` 100内的整数
19 | [苏联笑话](./pkg/plugins/sovietjokes)|`.sj`随机一条人类政治精华
20 | [猫猫图](./pkg/plugins/thecat)|`.thecat`/`.cat` 随机发送一条猫猫图
21 | [狗狗图](./pkg/plugins/thedog)|`.thedog`/`.dog` 随机发送一条狗狗图
22 | ~~[舔狗语录](./pkg/plugins/tiangou)~~|`.tg` 随机发送一条舔狗语录(重复率较高),已停用
23 | [提醒](./pkg/plugins/tips)|`.tips 10:10 提示内容` 10:10时,@该发送群友+提示内容
24 | [天气](./pkg/plugins/weather)|`.weather 所在地` 调用 https://github.com/schachmat/wego
25 | ~~[微博监听](./pkg/plugins/weibolisten)~~|由于访问限制,暂时不可用
26 | [B站粉丝数查询](./pkg/plugins/bilifan)|`.bilifan UID` 发送该UID的粉丝数量
27 | [翻译插件](./pkg/plugins/translate)|`.tr test` 翻译文本
`.tr -f zh 三点多,先喝茶 -t yue`
28 | [表情包](./pkg/plugins/facesave)|`.face-save -n/--name xxx` 紧跟着发送一张图片,以后发送 xxx(图片名称),bot会发出该图片
29 |
30 | ## 启动方式
31 | ### 宿主机方式
32 | 1.在[releases](https://github.com/dezhiShen/MiraiGo-Bot-Plugins/releases)中下载对应的包
33 |
34 | 2.执行
35 | ### docker
36 | - 1.选择对应的镜像
37 | - 2.第一次执行时,需要生成设备信息,验证设备合法性等
38 | - 2.1.`docker run -it -v ${数据目录}:/data dezhishen/miraigo-bot:${version}`
39 | - 2.2.按照提示,输入账户/密码,验证设备等
40 | - 2.3.关闭
41 | - 3.`docker run -d --restart=always -v ${数据目录}:/data dezhishen/miraigo-bot:${version}`
42 |
43 | ### 二次开发
44 | 参考本项目启动方式 [cmd/main](./cmd/main.go)
45 | ```
46 | package main
47 |
48 | import (
49 | //引入你要使用的插件
50 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/calendar"
51 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/haimage"
52 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/hitokoto"
53 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/jrrp"
54 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/lpl"
55 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/mc"
56 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/pixiv"
57 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/random"
58 |
59 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/tiangou"
60 |
61 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/segmentation"
62 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/thecat"
63 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/thedog"
64 |
65 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/tips"
66 |
67 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/vader"
68 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/caihongpi"
69 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/dujitang"
70 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/weather"
71 |
72 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/weibolisten"
73 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/sovietjokes"
74 | "github.com/dezhiShen/MiraiGo-Bot/pkg/server"
75 | )
76 |
77 | func main() {
78 | //启动
79 | server.Start()
80 | }
81 |
82 | ```
83 |
--------------------------------------------------------------------------------
/assert/fonts/Symbola.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dezhishen/MiraiGo-Bot-Plugins/24519dd79fed87fbd54d6d9828cf8954afa6a5c7/assert/fonts/Symbola.ttf
--------------------------------------------------------------------------------
/assert/fonts/yasqht.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dezhishen/MiraiGo-Bot-Plugins/24519dd79fed87fbd54d6d9828cf8954afa6a5c7/assert/fonts/yasqht.ttf
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/config"
5 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/almanac"
6 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/calendar"
7 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/haimage"
8 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/hitokoto"
9 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/lpl"
10 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/mc"
11 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/pixiv"
12 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/random"
13 |
14 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/tiangou"
15 |
16 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/segmentation"
17 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/thecat"
18 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/thedog"
19 |
20 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/tips"
21 |
22 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/vader"
23 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/caihongpi"
24 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/dujitang"
25 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/forward"
26 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/weather"
27 |
28 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/weibolisten"
29 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/dictionary"
30 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/sovietjokes"
31 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/translate"
32 |
33 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/bilifan"
34 | // _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/facesave"
35 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/rss"
36 | _ "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/plugins/todayFortune"
37 | "github.com/dezhiShen/MiraiGo-Bot/pkg/server"
38 | )
39 |
40 | func main() {
41 | config.InitLog()
42 | server.Start()
43 | }
44 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/dezhiShen/MiraiGo-Bot-Plugins
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/FloatTech/ZeroBot-Plugin v1.2.0
7 | github.com/Logiase/MiraiGo-Template v0.0.0-20210524064918-229c83f88d32
8 | github.com/Mrs4s/MiraiGo v0.0.0-20210525010101-8f0cd9494d64
9 | github.com/PuerkitoBio/goquery v1.7.1
10 | github.com/SlyMarbo/rss v1.0.1
11 | github.com/andybalholm/cascadia v1.3.1 // indirect
12 | github.com/antchfx/htmlquery v1.2.3
13 | github.com/dezhiShen/MiraiGo-Bot v0.0.0-20210623004334-664e971f99d6
14 | github.com/fogleman/gg v1.3.0
15 | github.com/go-basic/uuid v1.0.0
16 | github.com/go-ego/gse v0.66.0
17 | github.com/jonreiter/govader v0.0.0-20210224072402-ab79f4c25a36
18 | github.com/json-iterator/go v1.1.12 // indirect
19 | github.com/mmcdole/gofeed v1.1.3
20 | github.com/mmcdole/goxpp v0.0.0-20200921145534-2f3784f67354 // indirect
21 | github.com/sirupsen/logrus v1.8.1
22 | golang.org/x/net v0.0.0-20211013171255-e13a2654a71e // indirect
23 | golang.org/x/text v0.3.7 // indirect
24 | gonum.org/v1/gonum v0.9.0 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/k8s/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: bot
5 | ---
6 | apiVersion: apps/v1 # 1.9.0 之前的版本使用 apps/v1beta2,可通过命令 kubectl api-versions 查看
7 | kind: Deployment #指定创建资源的角色/类型
8 | metadata: #资源的元数据/属性
9 | name: miraigo #资源的名字,在同一个namespace中必须唯一
10 | namespace: bot #命名空间
11 | labels:
12 | app: miraigo #标签
13 | spec:
14 | replicas: 1 #副本数量3
15 | strategy:
16 | rollingUpdate: ##由于replicas为3,则整个升级,pod个数在2-4个之间
17 | maxSurge: 1 #滚动升级时会先启动1个pod
18 | maxUnavailable: 1 #滚动升级时允许的最大Unavailable的pod个数
19 | selector: #定义标签选择器,部署需要管理的pod(带有该标签的的会被管理)需在pod 模板中定义
20 | matchLabels:
21 | app: miraigo
22 | template: #这里Pod的定义
23 | metadata:
24 | labels: #Pod的label
25 | app: miraigo
26 | spec: # 模板的规范
27 | containers:
28 | - name: miraigo
29 | image: dezhishen/miraigo-bot:0.06
30 | imagePullPolicy: IfNotPresent
31 | env: ##通过环境变量的方式,直接传递pod=自定义Linux OS环境变量
32 | - name: BOT_FORWARD_ADMIN
33 | valueFrom:
34 | configMapKeyRef:
35 | name: bot-config
36 | key: BOT_FORWARD_ADMIN
37 | - name: BOT_BAIDU_FANYI_ID
38 | valueFrom:
39 | configMapKeyRef:
40 | name: bot-config
41 | key: BOT_BAIDU_FANYI_ID
42 | - name: BOT_BAIDU_FANYI_KEY
43 | valueFrom:
44 | configMapKeyRef:
45 | name: bot-config
46 | key: BOT_BAIDU_FANYI_KEY
47 | - name: TZ #本地Key
48 | value: Asia/Shanghai
49 | - name: LANG #本地Key
50 | value: zh_CN.UTF-8
51 | - name: BOT_PIXIV_TOKEN
52 | value: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJTZG5pdSIsInV1aWQiOiJjNTQ3OGY3MGUxY2E0NmVjODJiOTNiZGVlOWNiYjU2MSIsImlhdCI6MTYxNzA5Mjk0MSwiYWNjb3VudCI6IntcImVtYWlsXCI6XCIxMTc5NTUxOTYwQHFxLmNvbVwiLFwiZ2VuZGVyXCI6LTEsXCJoYXNQcm9uXCI6MCxcImlkXCI6ODE2LFwicGFzc1dvcmRcIjpcIjFkZmViY2NjNmJjNzI4ZTc1MGExZjQ1MjhlY2Q2NjcxXCIsXCJzdGF0dXNcIjowLFwidXNlck5hbWVcIjpcIlNkbml1XCJ9IiwianRpIjoiODE2In0.VgJxTsHwXLMRApBXEW0WOuicsoc3WLLT6BrcN4lmeDk
53 | - name: BOT_DJT_KEY
54 | value: 9bc86e70f1c8f0d9
55 | - name: BOT_CHP_KEY
56 | value: ce3552aa350641f0
57 | volumeMounts:
58 | - name: miraigo-bot-data
59 | mountPath: /data
60 | readinessProbe:
61 | exec:
62 | command:
63 | - cat
64 | - /data/health
65 | initialDelaySeconds: 10
66 | successThreshold: 1
67 | failureThreshold: 3
68 | livenessProbe:
69 | exec:
70 | command:
71 | - cat
72 | - /data/health
73 | initialDelaySeconds: 10
74 | successThreshold: 1
75 | failureThreshold: 3
76 | volumes:
77 | - name: miraigo-bot-data
78 | hostPath:
79 | path: /docker_data/miraigo
80 |
--------------------------------------------------------------------------------
/pkg/config/log.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "io"
5 | "os"
6 |
7 | log "github.com/sirupsen/logrus"
8 | )
9 |
10 | func InitLog() {
11 | //设置输出样式,自带的只有两种样式logrus.JSONFormatter{}和logrus.TextFormatter{}
12 | log.SetOutput(os.Stdout)
13 | //设置output,默认为stderr,可以为任何io.Writer,比如文件*os.File
14 | file, err := os.OpenFile("bot.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
15 | writers := []io.Writer{
16 | file,
17 | os.Stdout}
18 | //同时写文件和屏幕
19 | fileAndStdoutWriter := io.MultiWriter(writers...)
20 | if err == nil {
21 | log.SetOutput(fileAndStdoutWriter)
22 | } else {
23 | log.Info("failed to log to file.")
24 | }
25 | //设置最低loglevel
26 | log.SetLevel(log.InfoLevel)
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/fortune/fortune.go:
--------------------------------------------------------------------------------
1 | package fortune
2 |
3 | import (
4 | "archive/zip"
5 | "bytes"
6 | "encoding/base64"
7 | "encoding/json"
8 | "fmt"
9 | "image/jpeg"
10 | "io"
11 | "io/ioutil"
12 | "math/rand"
13 | "net/http"
14 | "os"
15 | "strings"
16 | "time"
17 |
18 | "github.com/FloatTech/ZeroBot-Plugin/utils/math"
19 | "github.com/fogleman/gg"
20 | "github.com/sirupsen/logrus"
21 | )
22 |
23 | type FortuneResult struct {
24 | Title string `json:"title"`
25 | Content string `json:"content"`
26 | }
27 |
28 | var logger = logrus.WithField("bot-plugin", "fortune")
29 | var root = "./fortune"
30 | var site = "https://ghproxy.com/https://github.com/dezhishen/raw/blob/master/fortune"
31 | var table = [...]string{
32 | "车万",
33 | "DC4",
34 | "爱因斯坦",
35 | "星空列车",
36 | "樱云之恋",
37 | "富婆妹",
38 | "李清歌",
39 | "公主连结",
40 | "原神",
41 | "明日方舟",
42 | "碧蓝航线",
43 | "碧蓝幻想",
44 | "战双",
45 | "阴阳师",
46 | }
47 |
48 | // @function randtext 随机选取签文
49 | // @param file 文件路径
50 | // @param seed 随机数种子
51 | // @return 运势结果 & 错误信息
52 | func Randtext() (*FortuneResult, error) {
53 | file := "运势签文.json"
54 | seed := time.Now().UnixNano()
55 | path, err := getFile(file)
56 | if err != nil {
57 | return nil, err
58 | }
59 | data, err := ioutil.ReadFile(path)
60 | if err != nil {
61 | return nil, err
62 | }
63 | temp := []map[string]string{}
64 | if err := json.Unmarshal(data, &temp); err != nil {
65 | return nil, err
66 | }
67 | rand.Seed(seed)
68 | r := rand.Intn(len(temp))
69 | return &FortuneResult{
70 | Title: temp[r]["title"],
71 | Content: temp[r]["content"],
72 | }, nil
73 | }
74 |
75 | func RandTheme() string {
76 | seed := time.Now().UnixNano()
77 | rand.Seed(seed)
78 | r := rand.Intn(len(table))
79 | return table[r]
80 | }
81 |
82 | // @function getBackgroundByTheme 获取背景图片
83 | // @param theme 背景主体
84 | // @return 图片地址,错误信息
85 | func getBackgroundByTheme(theme string) (string, error) {
86 | //如果文件夹不存在
87 | dirPath := root + "/" + theme + "/"
88 | if ok, _ := pathExists(dirPath); !ok {
89 | path, err := getFile(theme + ".zip")
90 | if err != nil {
91 | return "", err
92 | }
93 | //解压
94 | err = unpack(path, dirPath)
95 | if err != nil {
96 | return "", err
97 | }
98 | }
99 | //获取文件夹下随机图片一张
100 | // 生成种子
101 | return randimage(dirPath, time.Now().UnixNano())
102 | }
103 |
104 | // @function Draw 绘制运势图
105 | // @param theme 背景主体
106 | // @param title 签名
107 | // @param text 签文
108 | // @return 错误信息
109 | func Draw(theme string, fortuneResult *FortuneResult) ([]byte, error) {
110 | // 加载背景
111 | background, err := getBackgroundByTheme(theme)
112 | if err != nil {
113 | return nil, err
114 | }
115 | back, err := gg.LoadImage(background)
116 | if err != nil {
117 | return nil, err
118 | }
119 | //加载字体文件
120 | fontPath, err := getFile("sakura.ttf")
121 | if err != nil {
122 | return nil, err
123 | }
124 | canvas := gg.NewContext(back.Bounds().Size().Y, back.Bounds().Size().X)
125 | canvas.DrawImage(back, 0, 0)
126 | // 写标题
127 | canvas.SetRGB(1, 1, 1)
128 | if err := canvas.LoadFontFace(fontPath, 45); err != nil {
129 | return nil, err
130 | }
131 | sw, _ := canvas.MeasureString(fortuneResult.Title)
132 | canvas.DrawString(fortuneResult.Title, 140-sw/2, 112)
133 | // 写正文
134 | canvas.SetRGB(0, 0, 0)
135 | if err := canvas.LoadFontFace(fontPath, 23); err != nil {
136 | return nil, err
137 | }
138 | tw, th := canvas.MeasureString("测")
139 | tw, th = tw+10, th+10
140 | r := []rune(fortuneResult.Content)
141 | xsum := rowsnum(len(r), 9)
142 | switch xsum {
143 | default:
144 | for i, o := range r {
145 | xnow := rowsnum(i+1, 9)
146 | ysum := math.Min(len(r)-(xnow-1)*9, 9)
147 | ynow := i%9 + 1
148 | canvas.DrawString(string(o), -offest(xsum, xnow, tw)+115, offest(ysum, ynow, th)+320.0)
149 | }
150 | case 2:
151 | div := rowsnum(len(r), 2)
152 | for i, o := range r {
153 | xnow := rowsnum(i+1, div)
154 | ysum := math.Min(len(r)-(xnow-1)*div, div)
155 | ynow := i%div + 1
156 | switch xnow {
157 | case 1:
158 | canvas.DrawString(string(o), -offest(xsum, xnow, tw)+115, offest(9, ynow, th)+320.0)
159 | case 2:
160 | canvas.DrawString(string(o), -offest(xsum, xnow, tw)+115, offest(9, ynow+(9-ysum), th)+320.0)
161 | }
162 | }
163 | }
164 | // 转成 base64
165 | buffer := new(bytes.Buffer)
166 | encoder := base64.NewEncoder(base64.StdEncoding, buffer)
167 | var opt jpeg.Options
168 | opt.Quality = 70
169 | err = jpeg.Encode(encoder, canvas.Image(), &opt)
170 | if err != nil {
171 | return nil, err
172 | }
173 | encoder.Close()
174 | return buffer.Bytes(), nil
175 | }
176 |
177 | func rowsnum(total, div int) int {
178 | temp := total / div
179 | if total%div != 0 {
180 | temp++
181 | }
182 | return temp
183 | }
184 |
185 | func offest(total, now int, distance float64) float64 {
186 | if total%2 == 0 {
187 | return (float64(now-total/2) - 1) * distance
188 | }
189 | return (float64(now-total/2) - 1.5) * distance
190 | }
191 |
192 | func getFileName(name string) string {
193 | i := strings.LastIndex(name, "/")
194 | return fmt.Sprintf(root+"/%v", name[i+1:])
195 | }
196 | func getSiteUrl(name string) string {
197 | i := strings.LastIndex(name, "/")
198 | return fmt.Sprintf(site+"/%v", name[i+1:])
199 | }
200 |
201 | func getFile(name string) (string, error) {
202 | path := getFileName(name)
203 | exists, _ := pathExists(path)
204 | if exists {
205 | return path, nil
206 | }
207 | err := downloadFile(name)
208 | if err != nil {
209 | return "", err
210 | }
211 | return path, nil
212 | }
213 |
214 | func downloadFile(name string) error {
215 | url := getSiteUrl(name)
216 | logger.Info("下载文件..." + url)
217 | r, err := http.DefaultClient.Get(url)
218 | if err != nil {
219 | return err
220 | }
221 | content, err := ioutil.ReadAll(r.Body)
222 | if err != nil {
223 | return err
224 | }
225 | _ = ioutil.WriteFile(getFileName(name), content, 0644)
226 | return nil
227 | }
228 |
229 | func pathExists(path string) (bool, error) {
230 | _, err := os.Stat(path)
231 | if err == nil {
232 | return true, nil
233 | }
234 | if os.IsNotExist(err) {
235 | return false, nil
236 | }
237 | return false, err
238 | }
239 |
240 | func init() {
241 | exists, _ := pathExists(root)
242 | if !exists {
243 | os.Mkdir(root, 0777)
244 | }
245 | }
246 |
247 | // @function unpack 解压资源包
248 | // @param tgt 压缩文件位置
249 | // @param dest 解压位置
250 | // @return 错误信息
251 | func unpack(tgt, dest string) error {
252 | // 路径目录不存在则创建目录
253 | if _, err := os.Stat(dest); err != nil && !os.IsExist(err) {
254 | if err := os.MkdirAll(dest, 0755); err != nil {
255 | panic(err)
256 | }
257 | }
258 | reader, err := zip.OpenReader(tgt)
259 | if err != nil {
260 | return err
261 | }
262 | defer reader.Close()
263 | // 遍历解压到文件
264 | for _, file := range reader.File {
265 | // 打开解压文件
266 | rc, err := file.Open()
267 | if err != nil {
268 | return err
269 | }
270 | // 打开目标文件
271 | w, err := os.OpenFile(dest+file.Name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
272 | if err != nil {
273 | rc.Close()
274 | return err
275 | }
276 | // 复制到文件
277 | _, err = io.Copy(w, rc)
278 | rc.Close()
279 | w.Close()
280 | if err != nil {
281 | return err
282 | }
283 | }
284 | return nil
285 | }
286 |
287 | // @function randimage 随机选取文件夹下的文件
288 | // @param path 文件夹路径
289 | // @param seed 随机数种子
290 | // @return 文件路径 & 错误信息
291 | func randimage(path string, seed int64) (string, error) {
292 | rd, err := ioutil.ReadDir(path)
293 | if err != nil {
294 | return "", err
295 | }
296 | rand.Seed(seed)
297 | return path + rd[rand.Intn(len(rd))].Name(), nil
298 | }
299 |
--------------------------------------------------------------------------------
/pkg/fortune/fortune_test.go:
--------------------------------------------------------------------------------
1 | package fortune
2 |
3 | import "testing"
4 |
5 | func Test_download(t *testing.T) {
6 | downloadFile("DC4.zip")
7 | }
8 |
--------------------------------------------------------------------------------
/pkg/plugins/almanac/almanac.go:
--------------------------------------------------------------------------------
1 | package almanac
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "image/color"
7 | "io/ioutil"
8 | "math/rand"
9 | "os"
10 | "strings"
11 | "time"
12 |
13 | "github.com/fogleman/gg"
14 |
15 | "github.com/Mrs4s/MiraiGo/message"
16 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
17 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
18 | )
19 |
20 | // Plugin jrrp
21 | type Plugin struct {
22 | plugins.NoSortPlugin
23 | plugins.NoInitPlugin
24 | plugins.AlwaysNotFireNextEventPlugin
25 | }
26 |
27 | // PluginInfo PluginInfo
28 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
29 | return &plugins.PluginInfo{
30 | ID: "jrrp",
31 | Name: "jrrp",
32 | }
33 | }
34 |
35 | // IsFireEvent 是否触发
36 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
37 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
38 | v := msg.Elements[0]
39 | field, ok := v.(*message.TextElement)
40 | if !ok {
41 | return false
42 | }
43 | if strings.HasPrefix(field.Content, "签到") {
44 | return true
45 | }
46 | if strings.HasPrefix(field.Content, ".jrrp") {
47 | return true
48 | }
49 | }
50 | return false
51 | }
52 |
53 | // OnMessageEvent OnMessageEvent
54 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
55 | result := &plugins.MessageResponse{
56 | Elements: make([]message.IMessageElement, 1),
57 | }
58 | b, err := getImage(request.Sender.Uin)
59 | if err != nil {
60 | return nil, err
61 | }
62 | var image message.IMessageElement
63 | if plugins.GroupMessage == request.MessageType {
64 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(b))
65 | } else {
66 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(b))
67 | }
68 | if err != nil {
69 | return nil, err
70 | }
71 | result.Elements[0] = image
72 | return result, nil
73 | }
74 |
75 | func getImage(id int64) ([]byte, error) {
76 | timeNow := time.Now().Local()
77 | path := getFileName(id, timeNow)
78 | b, err := getFile(id, path)
79 | if err != nil {
80 | return nil, err
81 | }
82 | if b != nil {
83 | return b, err
84 | }
85 | err = randomFile(timeNow, "jrrp", id, true, path)
86 | if err != nil {
87 | return nil, err
88 | }
89 | return getFile(id, path)
90 | }
91 |
92 | func getFileName(id int64, t time.Time) string {
93 | return fmt.Sprintf("./jrrp/%v-%v.png", id, timeToStr(t))
94 | }
95 |
96 | func getFile(id int64, path string) ([]byte, error) {
97 | // url = strings.Replace(url, "large", "original", -1)
98 | exists, _ := pathExists(path)
99 | if exists {
100 | file, err := os.Open(path)
101 | if err != nil {
102 | panic(err)
103 | }
104 | defer file.Close()
105 | content, err := ioutil.ReadAll(file)
106 | return content, err
107 | }
108 | return nil, nil
109 | }
110 |
111 | func randomFile(t time.Time, pid string, uid int64, genIfNil bool, path string) error {
112 | r, err := getScore(t, pid, uid, true)
113 | if err != nil {
114 | return err
115 | }
116 | var score = r / 20
117 | full := "★"
118 | empty := "☆"
119 | var text string
120 | for i := 1; i <= score; i++ {
121 | text += full
122 | }
123 | for i := 1; i <= 5-score; i++ {
124 | text += empty
125 | }
126 | err = CreatImage(text, path)
127 | if err != nil {
128 | return err
129 | }
130 | return err
131 | }
132 |
133 | func getScore(t time.Time, pid string, uid int64, genIfNil bool) (int, error) {
134 | timestr := timeToStr(t)
135 | key := []byte(fmt.Sprintf("jrrp.%v.%v", uid, timestr))
136 | var score int
137 | err := storage.Get([]byte(pid), key, func(b []byte) error {
138 | if b != nil {
139 | score = storage.BytesToInt(b)
140 | }
141 | return nil
142 | })
143 | if err != nil {
144 | return 0, err
145 | }
146 | if genIfNil && score == 0 {
147 | rand.Seed(time.Now().UnixNano())
148 | score = rand.Intn(100) + 1
149 | storage.Put([]byte(pid), key, storage.IntToBytes(score))
150 | theTime := t.Add(-7 * 24 * time.Hour)
151 | theTimestr := timeToStr(theTime)
152 | keyLast7Day := fmt.Sprintf("jrrp.%v.%v", uid, theTimestr)
153 | storage.Delete([]byte(pid), []byte(keyLast7Day))
154 | }
155 | return score, nil
156 | }
157 |
158 | func timeToStr(t time.Time) string {
159 | return fmt.Sprintf("%v-%v-%v", t.Year(), int(t.Month()), t.Day())
160 | }
161 |
162 | func init() {
163 | exists, _ := pathExists("./jrrp")
164 | if !exists {
165 | os.Mkdir("./jrrp", 0777)
166 | }
167 | plugins.RegisterOnMessagePlugin(Plugin{})
168 | }
169 |
170 | func pathExists(path string) (bool, error) {
171 | _, err := os.Stat(path)
172 | if err == nil {
173 | return true, nil
174 | }
175 | if os.IsNotExist(err) {
176 | return false, nil
177 | }
178 | return false, err
179 | }
180 |
181 | func CreatImage(text string, path string) error {
182 | //图片的宽度
183 | var srcWidth float64 = 100
184 | //图片的高度
185 | var srcHeight float64 = 100
186 | dc := gg.NewContext(int(srcWidth), int(srcHeight))
187 | //设置背景色
188 | dc.SetColor(color.White)
189 | dc.Clear()
190 | dc.SetRGB255(255, 0, 0)
191 | if err := dc.LoadFontFace("/assert/fonts/Symbola.ttf", 25); err != nil {
192 | return err
193 | }
194 | sWidth, sHeight := dc.MeasureString(text)
195 | dc.DrawString(text, (srcWidth-sWidth)/2, (srcHeight-sHeight)/2)
196 | err := dc.SavePNG(path)
197 | if err != nil {
198 | return err
199 | }
200 | return nil
201 | }
202 |
--------------------------------------------------------------------------------
/pkg/plugins/bilifan/bilifan.go:
--------------------------------------------------------------------------------
1 | package bilifan
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "io/ioutil"
7 | "encoding/json"
8 | "strings"
9 |
10 | "github.com/Mrs4s/MiraiGo/message"
11 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
12 | )
13 |
14 | // Plugin b站粉丝数查看插件
15 | type Plugin struct{
16 | plugins.NoSortPlugin
17 | plugins.NoInitPlugin
18 | plugins.AlwaysNotFireNextEventPlugin
19 | }
20 |
21 | // PluginInfo PluginInfo
22 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
23 | return &plugins.PluginInfo{
24 | ID: "bilifan",
25 | Name: "b站粉丝数查看插件",
26 | }
27 | }
28 |
29 | // IsFireEvent 是否触发
30 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
31 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
32 | v := msg.Elements[0]
33 | field, ok := v.(*message.TextElement)
34 | return ok && strings.HasPrefix(field.Content, ".bilifan")
35 | }
36 | return false
37 | }
38 |
39 | // OnMessageEvent OnMessageEvent
40 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
41 | result := &plugins.MessageResponse{
42 | Elements: make([]message.IMessageElement, 1),
43 | }
44 | // fetch and split the parameter
45 | v := request.Elements[0]
46 | field, _ := v.(*message.TextElement)
47 | context := field.Content
48 | params := strings.Split(context, " ")
49 | uid := ""
50 | if params[1] == "help"{
51 | result.Elements[0] = message.NewText(".bilifan UP主UID")
52 | }
53 | if len(params) < 2 {
54 | // return nil, errors.New("请输入需要查询的UP主的uid")
55 | // 不输入查询目标那就查我的吧嘤嘤嘤,走过路过点个关注不迷路鸭
56 | uid = "7528659"
57 | } else {
58 | uid = params[1]
59 | }
60 | txt, _ := getBiliFan(uid)
61 | result.Elements[0] = message.NewText(txt)
62 | return result, nil
63 | }
64 |
65 | func init() {
66 | plugins.RegisterOnMessagePlugin(Plugin{})
67 | }
68 |
69 | func getBiliFan(uid string) (string, error){
70 | // request
71 | upNameUri := fmt.Sprintf("https://api.bilibili.com/x/space/acc/info?mid=%v&jsonp=jsonp", uid)
72 | fanUri := fmt.Sprintf("https://api.bilibili.com/x/relation/stat?vmid=%v&jsonp=jsonp", uid)
73 |
74 | // fetch the name of UP
75 | resp, err := http.DefaultClient.Get(upNameUri)
76 | if err != nil {
77 | return "", nil
78 | }
79 | // read the Body of requested data
80 | robots, err := ioutil.ReadAll(resp.Body)
81 | resp.Body.Close()
82 | if err != nil {
83 | return "", nil
84 | }
85 | // convert the code to string
86 | respBodyStr := string(robots)
87 | if respBodyStr == "" {
88 | return "", nil
89 | }
90 | // decode json
91 | var biliupdata BiliUpData
92 | err = json.Unmarshal([]byte(respBodyStr), &biliupdata)
93 | if err != nil {
94 | return "", err
95 | }
96 |
97 | // fetch number of fans
98 | resp, err = http.DefaultClient.Get(fanUri)
99 | if err != nil {
100 | return "", nil
101 | }
102 | // read the Body of requested data
103 | robots, err = ioutil.ReadAll(resp.Body)
104 | resp.Body.Close()
105 | if err != nil {
106 | return "", nil
107 | }
108 | // convert the code to string
109 | respBodyStr = string(robots)
110 | if respBodyStr == "" {
111 | return "", nil
112 | }
113 | // decode json
114 | var bilifandata BiliFanData
115 | err = json.Unmarshal([]byte(respBodyStr), &bilifandata)
116 | if err != nil {
117 | return "", err
118 | }
119 |
120 | // Produce the prefix
121 | prefix := ""
122 | switch uid {
123 | case "7528659":
124 | prefix = "脑子有饼的"
125 | case "2929582":
126 | prefix = "折纸之光"
127 | }
128 |
129 | txt := fmt.Sprintf(
130 | "%v %v\nuid:%v\n当前粉丝数:%v",
131 | prefix,
132 | biliupdata.Data.Name,
133 | uid,
134 | bilifandata.Data.Follower,
135 | )
136 | return txt, nil
137 | }
138 |
139 | type FanData struct{
140 | Mid uint64
141 | Following uint16
142 | Whisper uint16
143 | Blcak uint16
144 | Follower uint64
145 | }
146 |
147 | type UpData struct{
148 | Mid uint64
149 | Name string
150 | Sex string
151 | Face string
152 | Sign string
153 | Rank uint32
154 | Level uint8
155 | Jointime uint8
156 | Moral uint8
157 | Silence uint8
158 | Birthday string
159 | Coins int8
160 | Fans_badge bool
161 | Official interface{}
162 | Vip interface{}
163 | Pendant interface{}
164 | Live_room interface{}
165 | }
166 |
167 |
168 | type BiliFanData struct{
169 | Code int8
170 | Message string
171 | Ttl int8
172 | Data FanData
173 | }
174 |
175 | type BiliUpData struct{
176 | Code int8
177 | Messages string
178 | Ttl int8
179 | Data UpData
180 | }
--------------------------------------------------------------------------------
/pkg/plugins/bilifan/bilifan_test.go:
--------------------------------------------------------------------------------
1 | package bilifan
2 |
3 | import (
4 | "testing"
5 | "fmt"
6 | )
7 |
8 | func TestR(t *testing.T) {
9 | r, err := getBiliFan("1")
10 | if err != nil {
11 | t.Error(err)
12 | }
13 | fmt.Println(r)
14 | t.Log(r)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/plugins/caihongpi/caihongpi.go:
--------------------------------------------------------------------------------
1 | package caihongpi
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "os"
10 | "strings"
11 |
12 | "github.com/Mrs4s/MiraiGo/message"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
14 | )
15 |
16 | // Plugin Random插件
17 | type Plugin struct {
18 | plugins.NoSortPlugin
19 | plugins.NoInitPlugin
20 | plugins.AlwaysNotFireNextEventPlugin
21 | }
22 |
23 | var pluginID = "caihongpi"
24 |
25 | // PluginInfo PluginInfo
26 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
27 | return &plugins.PluginInfo{
28 | ID: pluginID,
29 | Name: "彩虹屁",
30 | }
31 | }
32 |
33 | // IsFireEvent 是否触发
34 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
35 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
36 | v := msg.Elements[0]
37 | field, ok := v.(*message.TextElement)
38 | return ok && strings.HasPrefix(field.Content, ".chp")
39 | }
40 | return false
41 | }
42 |
43 | // OnMessageEvent OnMessageEvent
44 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
45 | result := &plugins.MessageResponse{}
46 | caihongpi, err := getcaihongpi()
47 | if err != nil {
48 | return nil, err
49 | }
50 | result.Elements = append(result.Elements, message.NewText(fmt.Sprintf(
51 | "For %v : %v",
52 | request.GetNickName(),
53 | caihongpi,
54 | )))
55 | return result, nil
56 | }
57 |
58 | func init() {
59 | plugins.RegisterOnMessagePlugin(Plugin{})
60 | }
61 |
62 | var url = "https://api.muxiaoguo.cn/api/caihongpi?api_key=%v"
63 |
64 | type resp struct {
65 | Data *data `json:"data"`
66 | Code string `json:"code"`
67 | Msg string `json:"msg"`
68 | }
69 |
70 | type data struct {
71 | Comment string `json:"comment"`
72 | }
73 |
74 | func getcaihongpi() (string, error) {
75 | key, err := getKey()
76 | if err != nil {
77 | return "", err
78 | }
79 | r, err := http.DefaultClient.Get(fmt.Sprintf(url, key))
80 | if err != nil {
81 | return "", err
82 | }
83 | defer r.Body.Close()
84 | robots, err := ioutil.ReadAll(r.Body)
85 | if err != nil {
86 | return "", err
87 | }
88 | var resp resp
89 | err = json.Unmarshal(robots, &resp)
90 | if err != nil {
91 | return "", err
92 | }
93 | if resp.Code != "200" {
94 | return "", errors.New(resp.Msg)
95 | }
96 | return resp.Data.Comment, nil
97 | }
98 |
99 | func getKey() (string, error) {
100 | str := os.Getenv("BOT_CHP_KEY")
101 | if str == "" {
102 | return "", errors.New("未配置毒鸡汤的key")
103 | }
104 | return str, nil
105 | }
106 |
--------------------------------------------------------------------------------
/pkg/plugins/calendar/calendar.go:
--------------------------------------------------------------------------------
1 | package calendar
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "strconv"
11 | "strings"
12 | "time"
13 |
14 | "github.com/Logiase/MiraiGo-Template/bot"
15 | "github.com/Mrs4s/MiraiGo/message"
16 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
17 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
18 | )
19 |
20 | // Plugin 日历插件
21 | type Plugin struct {
22 | plugins.NoSortPlugin
23 | plugins.NoInitPlugin
24 | plugins.AlwaysNotFireNextEventPlugin
25 | }
26 |
27 | // PluginInfo PluginInfo
28 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
29 | return &plugins.PluginInfo{
30 | ID: "calendar",
31 | Name: "日期插件",
32 | }
33 | }
34 |
35 | // IsFireEvent 是否触发
36 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
37 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
38 | v := msg.Elements[0]
39 | field, ok := v.(*message.TextElement)
40 | return ok && strings.HasPrefix(field.Content, ".calendar")
41 | }
42 | return false
43 | }
44 |
45 | // OnMessageEvent OnMessageEvent
46 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
47 | msg, err := getDate(time.Now())
48 | if err != nil {
49 | return nil, err
50 | }
51 | v := request.Elements[0]
52 | field, _ := v.(*message.TextElement)
53 | context := field.Content
54 | params := strings.Split(context, " ")
55 | if len(params) > 1 && request.MessageType == plugins.GroupMessage {
56 | bucket := []byte(p.PluginInfo().ID)
57 | key := []byte(fmt.Sprintf("calendar.enable.%v", request.GroupCode))
58 | if params[1] == "Y" {
59 | storage.Put(bucket, key, storage.IntToBytes(1))
60 | msg += "\n已启用定时发送日历"
61 | } else if params[1] == "N" {
62 | storage.Delete(bucket, key)
63 | msg += "\n已禁用定时发送日历"
64 | }
65 | }
66 | return &plugins.MessageResponse{
67 | Elements: []message.IMessageElement{message.NewText(msg)},
68 | }, nil
69 | }
70 |
71 | // Cron cron表达式
72 | func (p Plugin) Cron() string {
73 | return "0 0 6 * * ?"
74 | }
75 |
76 | // Run 回调
77 | func (p Plugin) Run(bot *bot.Bot) error {
78 | text, _ := getDate(time.Now())
79 | sendingMessage := &message.SendingMessage{}
80 | sendingMessage.Append(message.NewText(text))
81 | groups, err := bot.GetGroupList()
82 | if err != nil {
83 | fmt.Printf("calendar send msg err %v", err)
84 | }
85 | bucket := []byte(p.PluginInfo().ID)
86 | for _, g := range groups {
87 | key := []byte(fmt.Sprintf("calendar.enable.%v", g.Code))
88 | value, _ := storage.GetValue(bucket, key)
89 | if value != nil && storage.BytesToInt(value) == 1 {
90 | go bot.QQClient.SendGroupMessage(g.Code, sendingMessage)
91 | }
92 | }
93 | return nil
94 | }
95 |
96 | type resp struct {
97 | Code int `json:"code"`
98 | Msg string `json:"msg"`
99 | Time int `json:"time"`
100 | Data *data `json:"data"`
101 | }
102 |
103 | type data struct {
104 | // 农历年
105 | IYear int `json:"iYear"`
106 | // 农历月
107 | IMonth int `json:"iMonth"`
108 | // 农历日
109 | IDay int `json:"iDay"`
110 | // 农历月 汉字
111 | IMonthChinese string `json:"iMonthChinese"`
112 | // 农历日 汉字
113 | IDayChinese string `json:"iDayChinese"`
114 | // 阳历年
115 | SYear int `json:"sYear"`
116 | // 阳历月
117 | SMonth int `json:"sMonth"`
118 | // 阳历日
119 | SDay int `json:"sDay"`
120 | // 天干地支年
121 | CYear string `json:"cYear"`
122 | // 天干地支月
123 | CMonth string `json:"cMonth"`
124 | // 天干地支日
125 | CDay string `json:"cDay"`
126 | // 是否为节假日
127 | IsHoliday bool `json:"isHoliday"`
128 | IsLeap bool `json:"isLeap"`
129 | // 阳历假日
130 | SolarFestival string `json:"solarFestival"`
131 | SolarTerms string `json:"solarTerms"`
132 | // 农历假日
133 | LunarFestival string `json:"lunarFestival"`
134 | // 周,汉字 一~日
135 | Week string `json:"week"`
136 | }
137 |
138 | func getDate(time time.Time) (string, error) {
139 | url := fmt.Sprintf("http://www.autmone.com/openapi/icalendar/queryDate?date=%v-%v-%v", time.Local().Year(), int(time.Local().Month()), time.Local().Day())
140 | r, err := http.DefaultClient.Get(url)
141 | if err != nil {
142 | return "", err
143 | }
144 | robots, err := ioutil.ReadAll(r.Body)
145 | r.Body.Close()
146 | if err != nil {
147 | return "", err
148 | }
149 | if robots == nil {
150 | return "", nil
151 | }
152 |
153 | var resp resp
154 | err = json.Unmarshal(robots, &resp)
155 | if err != nil {
156 | return "", err
157 | }
158 | if resp.Code != 0 {
159 | return "", errors.New(resp.Msg)
160 | }
161 | if resp.Data == nil {
162 | return "", errors.New("data is nil")
163 | }
164 | buf := new(bytes.Buffer)
165 | str := strings.Join([]string{
166 | strconv.Itoa(resp.Data.SYear),
167 | "年",
168 | strconv.Itoa(resp.Data.SMonth),
169 | "月",
170 | strconv.Itoa(resp.Data.SDay),
171 | "日,周",
172 | resp.Data.Week,
173 | }, "")
174 | buf.WriteString(str)
175 | if resp.Data.SolarFestival != "" {
176 | buf.WriteString("\n")
177 | buf.WriteString(strings.TrimSpace(resp.Data.SolarFestival))
178 | }
179 | buf.WriteString("\n农历")
180 | buf.WriteString(resp.Data.IMonthChinese)
181 | buf.WriteString(resp.Data.IDayChinese)
182 | if resp.Data.LunarFestival != "" {
183 | buf.WriteString("\n")
184 | buf.WriteString(strings.TrimSpace(resp.Data.LunarFestival))
185 | }
186 | return buf.String(), nil
187 | }
188 | func init() {
189 | plugin := Plugin{}
190 | plugins.RegisterOnMessagePlugin(plugin)
191 | plugins.RegisterSchedulerPlugin(plugin)
192 | }
193 |
--------------------------------------------------------------------------------
/pkg/plugins/calendar/calendar_test.go:
--------------------------------------------------------------------------------
1 | package calendar
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func TestR(t *testing.T) {
9 | r, err := getDate(time.Now())
10 | if err != nil {
11 | t.Error(err)
12 | }
13 | t.Log(r)
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/plugins/cosplay/cosplay.go:
--------------------------------------------------------------------------------
1 | package cosplay
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io/ioutil"
9 | "math/rand"
10 | "net/http"
11 |
12 | "github.com/Mrs4s/MiraiGo/message"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
14 | )
15 |
16 | // Plugin cosplay图片
17 | type Plugin struct {
18 | plugins.NoSortPlugin
19 | plugins.NoInitPlugin
20 | plugins.AlwaysNotFireNextEventPlugin
21 | }
22 |
23 | // PluginInfo PluginInfo
24 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
25 | return &plugins.PluginInfo{
26 | ID: "cosplay",
27 | Name: "cosplay图片",
28 | }
29 | }
30 |
31 | // IsFireEvent 是否触发
32 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
33 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
34 | v := msg.Elements[0]
35 | field, ok := v.(*message.TextElement)
36 | return ok && field.Content == ".cosplay"
37 | }
38 | return false
39 | }
40 |
41 | // OnMessageEvent OnMessageEvent
42 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
43 | result := &plugins.MessageResponse{
44 | Elements: make([]message.IMessageElement, 1),
45 | }
46 | b, err := getPic()
47 | if err != nil {
48 | return nil, err
49 | }
50 | var image message.IMessageElement
51 | if plugins.GroupMessage == request.MessageType {
52 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(b))
53 | } else {
54 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(b))
55 | }
56 | if err != nil {
57 | return nil, err
58 | }
59 | result.Elements[0] = image
60 | return result, nil
61 | }
62 |
63 | func init() {
64 | plugins.RegisterOnMessagePlugin(Plugin{})
65 | }
66 |
67 | type response struct {
68 | Msg string `json:"msg"`
69 | Code int `json:"code"`
70 | Data []data `json:"data"`
71 | }
72 |
73 | type data struct {
74 | ID string `json:"id"`
75 | Url string `json:"url"`
76 | }
77 |
78 | var url = "http://api.isoyu.com/api/picture/index?page=%v"
79 |
80 | func getPic() ([]byte, error) {
81 | page := rand.Intn(190)
82 | r, err := http.DefaultClient.Get(fmt.Sprintf(url, page))
83 | if err != nil {
84 | return nil, err
85 | }
86 | robots, err := ioutil.ReadAll(r.Body)
87 | if err != nil {
88 | return nil, err
89 | }
90 | if robots == nil {
91 | return nil, errors.New("抓取列表失败")
92 | }
93 | r.Body.Close()
94 | resp := string(robots)
95 | if resp == "" {
96 | return nil, errors.New("请稍后重试")
97 | }
98 |
99 | var data response
100 | err = json.Unmarshal(robots, &data)
101 | if err != nil {
102 | return nil, err
103 | }
104 | return nil, nil
105 | }
106 |
--------------------------------------------------------------------------------
/pkg/plugins/dictionary/dictionary.go:
--------------------------------------------------------------------------------
1 | package dictionary
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "net/http"
7 | "strings"
8 |
9 | "github.com/Mrs4s/MiraiGo/message"
10 | "github.com/antchfx/htmlquery"
11 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
12 | )
13 |
14 | type Plugin struct {
15 | plugins.NoSortPlugin
16 | plugins.NoInitPlugin
17 | plugins.AlwaysNotFireNextEventPlugin
18 | }
19 |
20 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
21 | return &plugins.PluginInfo{
22 | ID: "dictionary",
23 | Name: "字典插件",
24 | }
25 | }
26 |
27 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
28 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
29 | v := msg.Elements[0]
30 | field, ok := v.(*message.TextElement)
31 | return ok && strings.HasPrefix(field.Content, ".dict ")
32 | }
33 | return false
34 | }
35 |
36 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
37 |
38 | var elements []message.IMessageElement
39 |
40 | v := request.Elements[0]
41 | field, _ := v.(*message.TextElement)
42 | context := field.Content
43 | params := strings.Split(context, " ")
44 | q := params[1]
45 |
46 | uri := "http://dict-co.iciba.com/search.php?word=" + q
47 | resp, err := http.DefaultClient.Get(uri)
48 | if err != nil {
49 | return nil, err
50 | }
51 | robots, err := ioutil.ReadAll(resp.Body)
52 | resp.Body.Close()
53 | if err != nil {
54 | return nil, err
55 | }
56 | respBodyStr := string(robots)
57 | root, _ := htmlquery.Parse(strings.NewReader(respBodyStr))
58 | brs := htmlquery.Find(root, "/html/body/text()")
59 | for _, row := range brs {
60 | ele := htmlquery.InnerText(row)
61 | ele = strings.TrimSpace(ele)
62 | if ele != "" {
63 | vals := strings.Split(ele, " ")
64 | for _, val := range vals {
65 | elements = append(elements, message.NewText(fmt.Sprintf("%v\n", val)))
66 | }
67 | }
68 |
69 | }
70 |
71 | result := &plugins.MessageResponse{
72 | Elements: elements,
73 | }
74 |
75 | return result, nil
76 | }
77 |
78 | func init() {
79 | plugins.RegisterOnMessagePlugin(Plugin{})
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/plugins/dujitang/dujitang.go:
--------------------------------------------------------------------------------
1 | package dujitang
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "os"
10 | "strings"
11 |
12 | "github.com/Mrs4s/MiraiGo/message"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
14 | )
15 |
16 | // Plugin Random插件
17 | type Plugin struct {
18 | plugins.NoSortPlugin
19 | plugins.NoInitPlugin
20 | plugins.AlwaysNotFireNextEventPlugin
21 | }
22 |
23 | var pluginID = "dujitang"
24 |
25 | // PluginInfo PluginInfo
26 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
27 | return &plugins.PluginInfo{
28 | ID: pluginID,
29 | Name: "毒鸡汤",
30 | }
31 | }
32 |
33 | // IsFireEvent 是否触发
34 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
35 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
36 | v := msg.Elements[0]
37 | field, ok := v.(*message.TextElement)
38 | return ok && strings.HasPrefix(field.Content, ".djt")
39 | }
40 | return false
41 | }
42 |
43 | // OnMessageEvent OnMessageEvent
44 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
45 | result := &plugins.MessageResponse{}
46 | dujitang, err := getDujitang()
47 | if err != nil {
48 | return nil, err
49 | }
50 | result.Elements = append(result.Elements, message.NewText(fmt.Sprintf(
51 | "For %v : %v",
52 | request.GetNickName(),
53 | dujitang,
54 | )))
55 | return result, nil
56 | }
57 |
58 | func init() {
59 | plugins.RegisterOnMessagePlugin(Plugin{})
60 | }
61 |
62 | var url = "https://api.muxiaoguo.cn/api/dujitang?api_key=%v"
63 |
64 | type resp struct {
65 | Data *data `json:"data"`
66 | Code string `json:"code"`
67 | Msg string `json:"msg"`
68 | }
69 |
70 | type data struct {
71 | Comment string `json:"comment"`
72 | }
73 |
74 | func getDujitang() (string, error) {
75 | key, err := getKey()
76 | if err != nil {
77 | return "", err
78 | }
79 | r, err := http.DefaultClient.Get(fmt.Sprintf(url, key))
80 | if err != nil {
81 | return "", err
82 | }
83 | defer r.Body.Close()
84 | robots, err := ioutil.ReadAll(r.Body)
85 | if err != nil {
86 | return "", err
87 | }
88 | var resp resp
89 | err = json.Unmarshal(robots, &resp)
90 | if err != nil {
91 | return "", err
92 | }
93 | if resp.Code != "200" {
94 | return "", errors.New(resp.Msg)
95 | }
96 | return resp.Data.Comment, nil
97 | }
98 |
99 | func getKey() (string, error) {
100 | str := os.Getenv("BOT_DJT_KEY")
101 | if str == "" {
102 | return "", errors.New("未配置毒鸡汤的key")
103 | }
104 | return str, nil
105 | }
106 |
--------------------------------------------------------------------------------
/pkg/plugins/facesave/facesave.go:
--------------------------------------------------------------------------------
1 | package facesave
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "os"
8 | "strings"
9 | "time"
10 |
11 | "github.com/Mrs4s/MiraiGo/message"
12 | "github.com/dezhiShen/MiraiGo-Bot/pkg/cache"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/command"
14 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
15 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
16 | )
17 |
18 | // Plugin Random插件
19 | type Plugin struct {
20 | plugins.NoSortPlugin
21 | plugins.NoInitPlugin
22 | plugins.AlwaysNotFireNextEventPlugin
23 | }
24 |
25 | var pluginID = "face-save"
26 |
27 | // PluginInfo PluginInfo
28 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
29 | return &plugins.PluginInfo{
30 | ID: pluginID,
31 | Name: "表情包收集",
32 | }
33 | }
34 |
35 | // IsFireEvent 是否触发
36 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
37 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
38 | return true
39 | } else if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Image {
40 | var key string
41 | if msg.MessageType == plugins.GroupMessage {
42 | key = fmt.Sprintf("%v:%v", msg.MessageType, msg.GroupCode)
43 | } else {
44 | key = fmt.Sprintf("%v:%v", msg.MessageType, msg.Sender.Uin)
45 | }
46 | _, exists := cache.Get(key)
47 | return exists
48 | }
49 | return false
50 | }
51 |
52 | type FaceSaveReq struct {
53 | Name string `short:"n" long:"name" description:"表情的名称" required:"true"`
54 | }
55 |
56 | type ImageInfo struct {
57 | Filename string `json:"filename"`
58 | Size int32 `json:"size"`
59 | Width int32 `json:"width"`
60 | Height int32 `json:"height"`
61 | Url string `json:"url"`
62 | Md5 []byte `json:"md5"`
63 | Data []byte `json:"data"`
64 | }
65 |
66 | // OnMessageEvent OnMessageEvent
67 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
68 | result := &plugins.MessageResponse{}
69 | var key string
70 | if request.MessageType == plugins.GroupMessage {
71 | key = fmt.Sprintf("%v:%v", request.MessageType, request.GroupCode)
72 | } else {
73 | key = fmt.Sprintf("%v:%v", request.MessageType, request.Sender.Uin)
74 | }
75 | if len(request.Elements) == 1 && request.Elements[0].Type() == message.Text {
76 | //标记
77 | v := request.Elements[0]
78 | field, _ := v.(*message.TextElement)
79 | context := field.Content
80 | if strings.HasPrefix(context, ".face-save") {
81 | req := FaceSaveReq{}
82 | _, err := command.Parse(".face-save", &req, strings.Split(context, " "))
83 | if err != nil {
84 | return nil, err
85 | }
86 | cache.Set(key, req.Name, 1*time.Minute)
87 | result.Elements = append(result.Elements, message.NewText(fmt.Sprintf("表情名称为:%v,请于一分钟之内发送一张图片", req.Name)))
88 | } else {
89 | faceKey := strings.TrimSpace(context)
90 | imageInfo, err := getImage(faceKey)
91 | if err != nil || imageInfo == nil {
92 | return nil, nil
93 | }
94 | if plugins.GroupMessage == request.MessageType {
95 | // if err != nil {
96 | // return nil, err
97 | // }
98 | imageElement := message.NewGroupImage(
99 | "",
100 | imageInfo.Md5,
101 | 0,
102 | imageInfo.Size,
103 | imageInfo.Width,
104 | imageInfo.Height,
105 | 2000,
106 | )
107 | imageElement.Url = imageInfo.Url
108 | result.Elements = append(result.Elements, imageElement)
109 | } else {
110 | // imageElement, err := request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(*image))
111 | // if err != nil {
112 | // return nil, err
113 | // }
114 | imageElement := message.NewGroupImage(
115 | "",
116 | imageInfo.Md5,
117 | 0,
118 | imageInfo.Size,
119 | imageInfo.Width,
120 | imageInfo.Height,
121 | 2000,
122 | )
123 | imageElement.Url = imageInfo.Url
124 | result.Elements = append(result.Elements, imageElement)
125 | }
126 | }
127 | } else if len(request.Elements) == 1 && request.Elements[0].Type() == message.Image {
128 | cacheValue, exists := cache.Get(key)
129 | if !exists {
130 | return nil, errors.New("已经超过一分钟啦,请重新开始保持吧")
131 | }
132 | fileName := cacheValue.(string)
133 | if fileName == "" {
134 | return nil, errors.New("已经超过一分钟啦,请重新开始保持吧")
135 | }
136 | cache.Delete(key)
137 | v := request.Elements[0]
138 | field, _ := v.(*message.ImageElement)
139 | // println(fmt.Sprintf("\nFilename %v\nSize %v\nWidth %v\nHeight %v\nUrl %v\nMd5 %v\nData %v\n",
140 | // field.Filename,
141 | // field.Size,
142 | // field.Width,
143 | // field.Height,
144 | // field.Url,
145 | // field.Md5,
146 | // field.Data,
147 | // ))
148 | // println("url: " + field.Url)
149 | // reqest, _ := http.NewRequest("GET", field.Url, nil)
150 | // // Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
151 | // // Accept-Encoding: gzip, deflate, br
152 | // // Accept-Language: zh-CN
153 | // // Cache-Control: no-cache
154 | // // Connection: keep-alive
155 | // // Host: gchat.qpic.cn
156 | // // Pragma: no-cache
157 | // // Sec-Fetch-Dest: image
158 | // // Sec-Fetch-Mode: no-cors
159 | // // Sec-Fetch-Site: cross-site
160 | // // User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) electron-qq/1.4.7 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36
161 | // reqest.Header.Add("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
162 | // reqest.Header.Add("Accept-Encoding", "gzip, deflate, br")
163 | // reqest.Header.Add("Cache-Control", "no-cache")
164 | // reqest.Header.Add("Connection", "keep-alive")
165 | // reqest.Header.Add("Host", "gchat.qpic.cn")
166 | // reqest.Header.Add("Pragma", "no-cache")
167 | // reqest.Header.Add("Sec-Fetch-Dest", "image")
168 | // reqest.Header.Add("Sec-Fetch-Mode", "no-cors")
169 | // reqest.Header.Add("Sec-Fetch-Site", "cross-site")
170 | // reqest.Host = "gchat.qpic.cn"
171 | // r, err := http.DefaultClient.Do(reqest)
172 | // if err != nil {
173 | // return nil, err
174 | // }
175 | // defer r.Body.Close()
176 | // robots, err := ioutil.ReadAll(r.Body)
177 | // if err != nil {
178 | // return nil, err
179 | // }
180 | _, err := saveImage(field, fileName)
181 | if err != nil {
182 | return nil, err
183 | }
184 | result.Elements = append(result.Elements, message.NewText(fmt.Sprintf("保存成功,发送[%v]试试吧", fileName)))
185 | }
186 | return result, nil
187 | }
188 |
189 | func pathExists(path string) (bool, error) {
190 | _, err := os.Stat(path)
191 | if err == nil {
192 | return true, nil
193 | }
194 | if os.IsNotExist(err) {
195 | return false, nil
196 | }
197 | return false, err
198 | }
199 | func init() {
200 | plugins.RegisterOnMessagePlugin(Plugin{})
201 | exists, _ := pathExists("./face")
202 | if !exists {
203 | os.Mkdir("./face", 0777)
204 | }
205 | }
206 |
207 | func saveImage(file *message.ImageElement, fileName string) ([]byte, error) {
208 | // id, _ := uuid.GenerateUUID()
209 | // path := fmt.Sprintf("./face/%v.jpg", id)
210 | // // run shell `wget URL -O filepath`
211 | // cmd := exec.Command("wget", url, "-O", path)
212 | // cmd.Stdout = os.Stdout
213 | // cmd.Stderr = os.Stderr
214 | // err := cmd.Run()
215 | // if err != nil {
216 | // return "", err
217 | // }
218 | fileInfo := &ImageInfo{
219 | Filename: file.Filename,
220 | Size: file.Size, //int32 `json:"size"`
221 | Width: file.Width, //int32 `json:"width"`
222 | Height: file.Height, //int32 `json:"height"`
223 | Url: file.Url, //string `json:"url"`
224 | Md5: file.Md5, //[]byte `json:"md5"`
225 | Data: file.Data, //[]byte `json:"data"`
226 | }
227 | jsonBytes, _ := json.Marshal(fileInfo)
228 | storage.Put([]byte(pluginID), []byte(fileName), jsonBytes)
229 | return jsonBytes, nil
230 | }
231 |
232 | func getImage(fileName string) (*ImageInfo, error) {
233 | var result ImageInfo
234 | err := storage.Get([]byte(pluginID), []byte(fileName), func(b []byte) error {
235 | if b != nil {
236 | err := json.Unmarshal(b, &result)
237 | return err
238 | }
239 | return errors.New("图片不存在")
240 | })
241 | if err != nil {
242 | return nil, err
243 | }
244 | return &result, nil
245 | // ok, _ := pathExists(filePath)
246 | // if ok {
247 | // scaleFilePath := strings.ReplaceAll(filePath, ".jpg", fmt.Sprintf("_%v.jpg", width))
248 | // ok, _ := pathExists(scaleFilePath)
249 | // if !ok {
250 | // datatype, err := imgtype.Get(filePath)
251 | // if err != nil {
252 | // return nil, err
253 | // }
254 | // if datatype == `image/jpeg` || datatype == `image/png` {
255 | // file, err := os.Open(filePath)
256 | // if err != nil {
257 | // return nil, err
258 | // }
259 | // defer file.Close()
260 | // src, _, err := image.Decode(file)
261 | // if err != nil {
262 | // return nil, err
263 | // }
264 | // bound := src.Bounds()
265 | // dx := bound.Dx()
266 | // scaleFile, _ := os.Create(scaleFilePath)
267 | // defer scaleFile.Close()
268 | // if dx > width {
269 | // dy := bound.Dy()
270 | // dst := image.NewRGBA(image.Rect(0, 0, width, width*dy/dx))
271 | // err = graphics.Scale(dst, src)
272 | // if err != nil {
273 | // return nil, err
274 | // }
275 | // err = jpeg.Encode(scaleFile, dst, &jpeg.Options{Quality: 100})
276 | // if err != nil {
277 | // return nil, err
278 | // }
279 | // } else {
280 | // io.Copy(scaleFile, file)
281 | // }
282 | // } else {
283 | // file, err := os.Open(filePath)
284 | // if err != nil {
285 | // return nil, err
286 | // }
287 | // defer file.Close()
288 | // scaleFile, _ := os.Create(scaleFilePath)
289 | // defer scaleFile.Close()
290 | // io.Copy(scaleFile, file)
291 | // }
292 |
293 | // }
294 | // file, err := os.Open(scaleFilePath)
295 | // if err != nil {
296 | // return nil, err
297 | // }
298 | // defer file.Close()
299 | // content, err := ioutil.ReadAll(file)
300 | // return &content, err
301 | // }
302 | // return nil, errors.New("图片不存在")
303 | }
304 |
--------------------------------------------------------------------------------
/pkg/plugins/forward/plugin.go:
--------------------------------------------------------------------------------
1 | package forward
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "os"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/Mrs4s/MiraiGo/message"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/command"
14 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
15 | "github.com/sirupsen/logrus"
16 | )
17 |
18 | var logger = logrus.WithField("plugins", "forward")
19 |
20 | // Plugin 消息转发插件
21 | type Plugin struct {
22 | plugins.NoSortPlugin
23 | plugins.NoInitPlugin
24 | plugins.AlwaysNotFireNextEventPlugin
25 | }
26 |
27 | var pluginID = "forward"
28 |
29 | // PluginInfo PluginInfo
30 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
31 | return &plugins.PluginInfo{
32 | ID: pluginID,
33 | Name: "消息转发插件",
34 | }
35 | }
36 |
37 | // IsFireEvent 是否触发
38 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
39 | // 验证消息来源
40 | if !contains(accouts, msg.Sender.Uin) {
41 | return false
42 | }
43 | if msg.Elements[0].Type() == message.Text {
44 | v := msg.Elements[0]
45 | field, ok := v.(*message.TextElement)
46 | return ok && strings.HasPrefix(field.Content, ".forward")
47 | }
48 | return false
49 | }
50 |
51 | type forwardCommand struct {
52 | To int64 `short:"t" long:"to" description:"转发对象" required:"true"`
53 | Group bool `short:"g" long:"group" description:"是否转发到群组"`
54 | }
55 |
56 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
57 | v := request.Elements[0]
58 | field, _ := v.(*message.TextElement)
59 | context := field.Content
60 | fc := forwardCommand{}
61 | commands, err := command.Parse(".forward", &fc, strings.Split(context, " "))
62 | if err != nil {
63 | return nil, err
64 | }
65 | var q string
66 | for i := 1; i < len(commands); i++ {
67 | q = q + " " + commands[i]
68 | }
69 | m := message.NewSendingMessage()
70 |
71 | if plugins.GroupMessage == request.MessageType {
72 | m.Append(message.NewText(fmt.Sprintf("来自群[%v(%v)]的转发消息:\n", request.GroupName, request.GroupCode)))
73 | } else {
74 | m.Append(message.NewText(fmt.Sprintf("来自私聊[%v(%v)]的转发消息:\n", request.GetNickName(), request.Sender.Uin)))
75 | }
76 | if q != "" {
77 | m.Append(message.NewText(strings.TrimSpace(q)))
78 | }
79 | for i := 1; i < len(request.Elements); i++ {
80 | if request.Elements[i].Type() == message.Image {
81 | field, _ := request.Elements[i].(*message.ImageElement)
82 | b, _ := getImage(field.Url)
83 | var image message.IMessageElement
84 | if fc.Group {
85 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(b))
86 | if err != nil {
87 | return nil, err
88 | }
89 | // request.QQClient.SendGroupMessage(fc.To, m)
90 | } else {
91 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(b))
92 | if err != nil {
93 | return nil, err
94 | }
95 | // request.QQClient.SendPrivateMessage(fc.To, m)
96 | }
97 | m.Append(image)
98 | } else {
99 | m.Append(request.Elements[i])
100 | }
101 | }
102 | if fc.Group {
103 | request.QQClient.SendGroupMessage(fc.To, m)
104 | } else {
105 | request.QQClient.SendPrivateMessage(fc.To, m)
106 | }
107 | return nil, nil
108 | }
109 |
110 | var accouts []int64
111 |
112 | func contains(s []int64, e int64) bool {
113 | for _, a := range s {
114 | if a == e {
115 | return true
116 | }
117 | }
118 | return false
119 | }
120 | func init() {
121 | accouts = getAdminUid()
122 | logger.Info("当前管理员为:", accouts)
123 | plugins.RegisterOnMessagePlugin(Plugin{})
124 | }
125 |
126 | func getImage(url string) ([]byte, error) {
127 | r, err := http.DefaultClient.Get(url)
128 | if err != nil {
129 | return nil, err
130 | }
131 | robots, err := ioutil.ReadAll(r.Body)
132 | if err != nil {
133 | return nil, err
134 | }
135 | r.Body.Close()
136 | return robots, nil
137 | }
138 |
139 | func getAdminUid() []int64 {
140 | str := os.Getenv("BOT_FORWARD_ADMIN")
141 | if str == "" {
142 | return nil
143 | }
144 | accouts := strings.Split(str, ",")
145 | if len(accouts) == 0 {
146 | return nil
147 | }
148 | var result []int64
149 | for _, a := range accouts {
150 | e, err := strconv.ParseInt(a, 10, 64)
151 | if err != nil {
152 | continue
153 | }
154 | result = append(result, e)
155 | }
156 | return result
157 | }
158 |
--------------------------------------------------------------------------------
/pkg/plugins/haimage/haimage.go:
--------------------------------------------------------------------------------
1 | package haimage
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "net/http"
7 |
8 | "github.com/Mrs4s/MiraiGo/message"
9 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
10 | )
11 |
12 | // Plugin 古风小姐姐图片
13 | type Plugin struct {
14 | plugins.NoSortPlugin
15 | plugins.NoInitPlugin
16 | plugins.AlwaysNotFireNextEventPlugin
17 | }
18 |
19 | func init() {
20 | plugins.RegisterOnMessagePlugin(Plugin{})
21 | }
22 |
23 | // PluginInfo PluginInfo
24 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
25 | return &plugins.PluginInfo{
26 | ID: ".hapic",
27 | Name: "古风小姐姐图片",
28 | }
29 | }
30 |
31 | // IsFireEvent 是否触发
32 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
33 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
34 | v := msg.Elements[0]
35 | field, ok := v.(*message.TextElement)
36 | return ok && field.Content == ".hapic"
37 | }
38 | return false
39 | }
40 |
41 | // OnMessageEvent OnMessageEvent
42 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
43 | result := &plugins.MessageResponse{
44 | Elements: make([]message.IMessageElement, 1),
45 | }
46 | b, err := getHaImage()
47 | if err != nil {
48 | return nil, err
49 | }
50 | var image message.IMessageElement
51 | if plugins.GroupMessage == request.MessageType {
52 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(b))
53 | } else {
54 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(b))
55 | }
56 | if err != nil {
57 | return nil, err
58 | }
59 | result.Elements[0] = image
60 | return result, nil
61 | }
62 |
63 | var url = "https://cdn.seovx.com/ha/?mom=302"
64 |
65 | func getHaImage() ([]byte, error) {
66 |
67 | r, err := http.DefaultClient.Get(url)
68 | if err != nil {
69 | return nil, err
70 | }
71 | robots, err := ioutil.ReadAll(r.Body)
72 | if err != nil {
73 | return nil, err
74 | }
75 | r.Body.Close()
76 | return robots, nil
77 | }
78 |
--------------------------------------------------------------------------------
/pkg/plugins/haimage/haimage_test.go:
--------------------------------------------------------------------------------
1 | package haimage
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestGetHaImage(t *testing.T) {
9 | robots, _ := getHaImage()
10 | fmt.Print(robots)
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/plugins/hitokoto/hitokoto.go:
--------------------------------------------------------------------------------
1 | package hitokoto
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 |
9 | "github.com/Mrs4s/MiraiGo/message"
10 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
11 | )
12 |
13 | // Plugin 一言插件
14 | type Plugin struct {
15 | plugins.NoSortPlugin
16 | plugins.NoInitPlugin
17 | plugins.AlwaysNotFireNextEventPlugin
18 | }
19 |
20 | // PluginInfo PluginInfo
21 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
22 | return &plugins.PluginInfo{
23 | ID: "hitokoto",
24 | Name: "一言插件",
25 | }
26 | }
27 |
28 | // IsFireEvent 是否触发
29 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
30 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
31 | v := msg.Elements[0]
32 | field, ok := v.(*message.TextElement)
33 | return ok && field.Content == ".hitokoto"
34 | }
35 | return false
36 | }
37 |
38 | // OnMessageEvent OnMessageEvent
39 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
40 | result := &plugins.MessageResponse{
41 | Elements: make([]message.IMessageElement, 1),
42 | }
43 | r, err := http.DefaultClient.Get("https://v1.hitokoto.cn/")
44 | if err != nil {
45 | return nil, err
46 | }
47 | robots, err := ioutil.ReadAll(r.Body)
48 | r.Body.Close()
49 | if err != nil {
50 | return nil, err
51 | }
52 | resp := string(robots)
53 | if resp == "" {
54 | return nil, nil
55 | }
56 | var mapResult map[string]interface{}
57 | err = json.Unmarshal([]byte(resp), &mapResult)
58 | if err != nil {
59 | return nil, err
60 | }
61 | name := request.Sender.CardName
62 | if name == "" {
63 | name = request.Sender.Nickname
64 | }
65 | txt := fmt.Sprintf(
66 | "For %v : %v",
67 | name,
68 | mapResult["hitokoto"],
69 | )
70 | result.Elements[0] = message.NewText(txt)
71 | return result, nil
72 | }
73 |
74 | func init() {
75 | plugins.RegisterOnMessagePlugin(Plugin{})
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/plugins/jrrp/jrrp.go:
--------------------------------------------------------------------------------
1 | package jrrp
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math/rand"
7 | "strconv"
8 | "strings"
9 | "time"
10 |
11 | "github.com/Mrs4s/MiraiGo/message"
12 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
14 | )
15 |
16 | // Plugin Random插件
17 | type Plugin struct {
18 | plugins.NoSortPlugin
19 | plugins.NoInitPlugin
20 | plugins.AlwaysNotFireNextEventPlugin
21 | }
22 |
23 | // PluginInfo PluginInfo
24 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
25 | return &plugins.PluginInfo{
26 | ID: "Jrrp",
27 | Name: "今日人品插件",
28 | }
29 | }
30 |
31 | // IsFireEvent 是否触发
32 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
33 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
34 | v := msg.Elements[0]
35 | field, ok := v.(*message.TextElement)
36 | return ok && strings.HasPrefix(field.Content, ".jrrp")
37 | }
38 | return false
39 | }
40 |
41 | // OnMessageEvent OnMessageEvent
42 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
43 | result := &plugins.MessageResponse{}
44 | timeNow := time.Now().Local()
45 | score, err := getScore(timeNow, p.PluginInfo().ID, request.Sender.Uin, true)
46 | if err != nil {
47 | return nil, err
48 | }
49 | var elements []message.IMessageElement
50 | elements = append(elements, message.NewText(fmt.Sprintf("[%v]今日人品: %v", request.GetNickName(), score)))
51 |
52 | v := request.Elements[0]
53 | field, _ := v.(*message.TextElement)
54 | context := field.Content
55 | params := strings.Split(context, " ")
56 |
57 | if len(params) > 1 {
58 | preDays, err := strconv.Atoi(params[1])
59 | if err != nil {
60 | return nil, errors.New("请输入一个7以内的正整数")
61 | }
62 | if preDays > 7 {
63 | preDays = 7
64 | }
65 | for i := 1; i <= preDays; i++ {
66 | timeNow = timeNow.Add(-1 * 24 * time.Hour)
67 | score, err := getScore(timeNow, p.PluginInfo().ID, request.Sender.Uin, true)
68 | if err != nil {
69 | return nil, err
70 | }
71 | if score == 0 {
72 | break
73 | }
74 | elements = append(elements, message.NewText(fmt.Sprintf("\n[%v]历史人品: %v", request.GetNickName(), score)))
75 | }
76 | }
77 | result.Elements = elements
78 | return result, nil
79 | }
80 |
81 | func getScore(t time.Time, pid string, uid int64, genIfNil bool) (int, error) {
82 | timestr := timeToStr(t)
83 | key := []byte(fmt.Sprintf("jrrp.%v.%v", uid, timestr))
84 | var score int
85 | err := storage.Get([]byte(pid), key, func(b []byte) error {
86 | if b != nil {
87 | score = storage.BytesToInt(b)
88 | }
89 | return nil
90 | })
91 | if err != nil {
92 | return 0, err
93 | }
94 | if genIfNil && score == 0 {
95 | rand.Seed(time.Now().UnixNano())
96 | score = rand.Intn(100) + 1
97 | storage.Put([]byte(pid), key, storage.IntToBytes(score))
98 | theTime := t.Add(-7 * 24 * time.Hour)
99 | theTimestr := timeToStr(theTime)
100 | keyLast7Day := fmt.Sprintf("jrrp.%v.%v", uid, theTimestr)
101 | storage.Delete([]byte(pid), []byte(keyLast7Day))
102 | }
103 | return score, nil
104 | }
105 |
106 | func timeToStr(t time.Time) string {
107 | return fmt.Sprintf("%v-%v-%v", t.Year(), int(t.Month()), t.Day())
108 | }
109 |
110 | func init() {
111 | plugins.RegisterOnMessagePlugin(Plugin{})
112 | }
113 |
--------------------------------------------------------------------------------
/pkg/plugins/lpl/lpl.go:
--------------------------------------------------------------------------------
1 | package lpl
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "strings"
9 | "time"
10 | "unsafe"
11 |
12 | "github.com/Mrs4s/MiraiGo/message"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
14 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
15 | )
16 |
17 | // Plugin lpl今日明日赛事
18 | type Plugin struct {
19 | plugins.NoSortPlugin
20 | plugins.NoInitPlugin
21 | plugins.AlwaysNotFireNextEventPlugin
22 | }
23 |
24 | // PluginInfo PluginInfo
25 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
26 | return &plugins.PluginInfo{
27 | ID: "lpl",
28 | Name: "lpl今日明日赛事查询插件",
29 | }
30 | }
31 |
32 | // IsFireEvent 是否触发
33 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
34 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
35 | v := msg.Elements[0]
36 | field, ok := v.(*message.TextElement)
37 | return ok && field.Content == ".lpl"
38 | }
39 | return false
40 | }
41 |
42 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
43 | var elements []message.IMessageElement
44 |
45 | post := handleMatchMessage(w.PluginInfo().ID)
46 | if unsafe.Sizeof(post) == 0 {
47 | elements = append(elements, message.NewText("无可用信息"))
48 | } else {
49 | elements = append(elements, message.NewText(fmt.Sprintf("%s", post.TodayPost)))
50 | elements = append(elements, message.NewText(fmt.Sprintf("%s", post.TommorrowPost)))
51 | }
52 | result := &plugins.MessageResponse{
53 | Elements: elements,
54 | }
55 | return result, nil
56 | }
57 |
58 | // Run 回调
59 | // func (t Plugin) Run(bot *bot.Bot) error {
60 |
61 | // m := message.SendingMessage{}
62 | // post := handleMatchMessage(t.PluginInfo().ID)
63 | // if unsafe.Sizeof(post) == 0 {
64 | // m.Elements = append(m.Elements, message.NewText("无可用信息"))
65 | // return nil
66 | // }
67 |
68 | // m.Elements = append(m.Elements, message.NewText(fmt.Sprintf("%s", post.TodayPost)))
69 | // m.Elements = append(m.Elements, message.NewText(fmt.Sprintf("%s", post.TommorrowPost)))
70 |
71 | // bot.QQClient.SendGroupMessage(sender.Reciver, &m)
72 |
73 | // // bot.QQClient.Send
74 | // return nil
75 | // }
76 |
77 | func (t Plugin) Cron() string {
78 | return "0 /30 * * * ? *"
79 | }
80 |
81 | func init() {
82 | plugins.RegisterOnMessagePlugin(Plugin{})
83 | }
84 |
85 | func handleMatchMessage(pluginId string) MatchPost {
86 |
87 | prefix := []byte("lpl.recent_match")
88 | var post MatchPost
89 | storage.GetByPrefix([]byte(pluginId), prefix, func(key, v []byte) error {
90 | err := json.Unmarshal(v, &post)
91 | if err != nil {
92 | return err
93 | }
94 | return nil
95 | })
96 |
97 | if unsafe.Sizeof(post) == 0 || isTimeExpired(post) {
98 | //查询并填充
99 | post = createMatchPost()
100 | jsonBytes, _ := json.Marshal(post)
101 |
102 | err := storage.Delete([]byte(pluginId), prefix)
103 | if err != nil {
104 | return post
105 | }
106 |
107 | err = storage.Put([]byte(pluginId), prefix, jsonBytes)
108 | if err != nil {
109 | return post
110 | }
111 | }
112 | return post
113 | }
114 |
115 | func isTimeExpired(post MatchPost) bool {
116 |
117 | nowLocal := time.Now().Local()
118 |
119 | lastUpTime, err := time.Parse("2006-01-02 15:04:05", post.LastUpdate)
120 | if err != nil {
121 | return true
122 | }
123 | lutUnix := time.Unix(lastUpTime.Unix(), 0)
124 | subHours := int(nowLocal.Sub(lutUnix))
125 |
126 | if subHours > 8 {
127 | return true
128 | }
129 | return false
130 | }
131 |
132 | func createMatchInfo(game Game, isToday bool) string {
133 | var matchDay string
134 | if isToday {
135 | matchDay = "今日"
136 | } else {
137 | matchDay = "明日"
138 | }
139 |
140 | teamBox := strings.Split(game.MatchNameB, "vs")
141 | teamA := strings.TrimSpace(teamBox[0])
142 | teamB := strings.TrimSpace(teamBox[1])
143 |
144 | scoreA := game.ScoreA
145 | scoreB := game.ScoreB
146 |
147 | matchStatus := game.MatchStatus
148 | gameTypeName := game.GameTypeName //e.g 常规赛
149 |
150 | var status string
151 | if matchStatus == "1" {
152 | status = "未开始"
153 | } else if matchStatus == "2" {
154 | status = "进行中"
155 | } else {
156 | status = "已结束"
157 | }
158 |
159 | return fmt.Sprintf("%s比赛(%s,%s) %s %s:%s %s", matchDay, gameTypeName, status, teamA, scoreA, scoreB, teamB)
160 | }
161 |
162 | func createMatchPost() MatchPost {
163 | var post MatchPost
164 | post.TodayPost = ""
165 | post.TommorrowPost = ""
166 |
167 | recentGamesUrl := "https://lpl.qq.com/web201612/data/LOL_MATCH2_MATCH_HOMEPAGE_BMATCH_LIST_148.js"
168 | resp, err := http.DefaultClient.Get(recentGamesUrl)
169 |
170 | if err != nil {
171 | return post
172 | }
173 | robots, err := ioutil.ReadAll(resp.Body)
174 | resp.Body.Close()
175 | if err != nil {
176 | return post
177 | }
178 | respBodyStr := string(robots)
179 | if respBodyStr == "" {
180 | return post
181 | }
182 | var mBox MatchBox
183 | err = json.Unmarshal(robots, &mBox)
184 | if err != nil {
185 | return post
186 | }
187 |
188 | nowLocal := time.Now().Local()
189 | for _, game := range mBox.GameInfo {
190 |
191 | matchDate, err := time.Parse("2006-01-02 15:04:05", game.MatchDate)
192 | if err != nil {
193 | continue
194 | }
195 | if matchDate.Before(nowLocal) {
196 | continue
197 | }
198 | if matchDate.Day() == nowLocal.Day() {
199 | todayInfo := createMatchInfo(game, true)
200 | post.TodayPost += fmt.Sprintf("%s\n", todayInfo)
201 | }
202 | if matchDate.Day() == nowLocal.Day()+1 {
203 | tomorrowInfo := createMatchInfo(game, false)
204 | post.TommorrowPost += fmt.Sprintf("%s\n", tomorrowInfo)
205 | }
206 |
207 | }
208 |
209 | post.LastUpdate = nowLocal.String()
210 | if post.TodayPost == "" {
211 | post.TodayPost = "今日无赛事"
212 | }
213 | if post.TommorrowPost == "" {
214 | post.TommorrowPost = "明日无赛事"
215 | }
216 |
217 | return post
218 | }
219 |
220 | type MatchPost struct {
221 | TodayPost string `json:"todayPost"`
222 | TommorrowPost string `json:"tomorrowPost"`
223 | LastUpdate string `json:"lastUpTime"`
224 | }
225 |
226 | type MatchBox struct {
227 | Status string `json:"status"`
228 | LastUpdate string `json:"lastUpTime"`
229 | GameInfo []Game `json:"msg"`
230 | }
231 |
232 | type Game struct {
233 | IsTft string `json:"isTft"`
234 | TftInfos string `json:"tftInfos"`
235 | GameIdB string `json:"bGameId"`
236 | MatchIdB string `json:"bMatchId"`
237 | MatchNameB string `json:"bMatchName"` //xx vs xx
238 | GameId string `json:"GameId"`
239 | GameName string `json:"GameName"`
240 | GameMode string `json:"GameMode"`
241 | GameModeName string `json:"GameModeName"`
242 | GameTypeId string `json:"GameTypeId"`
243 | GameTypeName string `json:"GameTypeName"` //常规赛
244 | GameProcId string `json:"GameProcId"`
245 | GameProcName string `json:"GameProcName"` //第几周
246 | TeamA string `json:"TeamA"`
247 | ScoreA string `json:"ScoreA"`
248 | TeamB string `json:"TeamB"`
249 | ScoreB string `json:"ScoreB"`
250 | MatchDate string `json:"MatchDate"` //比赛时间
251 | MatchStatus string `json:"MatchStatus"` // 1 未开始 2 进行中
252 | MatchWin string `json:"MatchWin"`
253 | IQTMatchId string `json:"iQTMatchId"`
254 | AppTopicId string `json:"AppTopicId"`
255 | AppShowFlag_m string `json:"AppShowFlag_m"`
256 | AppShowFlag_n string `json:"AppShowFlag_n"`
257 | NewsId string `json:"NewsId"`
258 | ExtMsg string `json:"sExt1"`
259 | Video1 string `json:"Video1"`
260 | Video2 string `json:"Video2"`
261 | Video3 string `json:"Video3"`
262 | Chat1 string `json:"Chat1"`
263 | Chat2 string `json:"Chat2"`
264 | Chat3 string `json:"Chat3"`
265 | News1 string `json:"News1"`
266 | News2 string `json:"News2"`
267 | News3 string `json:"News3"`
268 | IsFocus string `json:"isFocus"`
269 | }
270 |
--------------------------------------------------------------------------------
/pkg/plugins/lpl/lpl_test.go:
--------------------------------------------------------------------------------
1 | package lpl
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestLpl(t *testing.T) {
13 | recentGamesUrl := "https://lpl.qq.com/web201612/data/LOL_MATCH2_MATCH_HOMEPAGE_BMATCH_LIST_148.js"
14 | resp, err := http.DefaultClient.Get(recentGamesUrl)
15 |
16 | if err != nil {
17 | return
18 | }
19 | robots, err := ioutil.ReadAll(resp.Body)
20 | resp.Body.Close()
21 | if err != nil {
22 | return
23 | }
24 | respBodyStr := string(robots)
25 | if respBodyStr == "" {
26 | return
27 | }
28 | var mBox MatchBox
29 | err = json.Unmarshal(robots, &mBox)
30 | if err != nil {
31 | return
32 | }
33 |
34 | nowLocal := time.Now().Local()
35 |
36 | for _, game := range mBox.GameInfo {
37 |
38 | matchDate, err := time.Parse("2006-01-02 15:04:05", game.MatchDate)
39 | if err != nil {
40 | continue
41 | }
42 | if matchDate.Before(nowLocal) {
43 | continue
44 | }
45 | if matchDate.Day() == nowLocal.Day() {
46 | todayInfo := createMatchInfo(game, true)
47 | fmt.Printf("%s", todayInfo)
48 | }
49 | if matchDate.Day() == nowLocal.Day()+1 {
50 | tomorrowInfo := createMatchInfo(game, false)
51 | fmt.Printf("%s", tomorrowInfo)
52 | }
53 |
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/plugins/mc/mc.go:
--------------------------------------------------------------------------------
1 | package mc
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io/ioutil"
10 | "net/http"
11 | "os"
12 | "strings"
13 |
14 | "github.com/Mrs4s/MiraiGo/message"
15 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
16 | )
17 |
18 | // Plugin menhear
19 | type Plugin struct {
20 | plugins.NoSortPlugin
21 | plugins.NoInitPlugin
22 | plugins.AlwaysNotFireNextEventPlugin
23 | }
24 |
25 | // PluginInfo PluginInfo
26 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
27 | return &plugins.PluginInfo{
28 | ID: ".mc",
29 | Name: "menhear",
30 | }
31 | }
32 |
33 | // IsFireEvent 是否触发
34 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
35 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
36 | v := msg.Elements[0]
37 | field, ok := v.(*message.TextElement)
38 | return ok && field.Content == ".mc"
39 | }
40 | return false
41 | }
42 |
43 | // OnMessageEvent OnMessageEvent
44 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
45 | result := &plugins.MessageResponse{
46 | Elements: make([]message.IMessageElement, 1),
47 | }
48 | b, err := randomImage()
49 | if err != nil {
50 | return nil, err
51 | }
52 | var image message.IMessageElement
53 | if plugins.GroupMessage == request.MessageType {
54 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(*b))
55 | } else {
56 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(*b))
57 | }
58 | if err != nil {
59 | return nil, err
60 | }
61 | result.Elements[0] = image
62 | return result, nil
63 | }
64 |
65 | func init() {
66 | exists, _ := pathExists("./mc")
67 | if !exists {
68 | os.Mkdir("./mc", 0777)
69 | }
70 | plugins.RegisterOnMessagePlugin(Plugin{})
71 | }
72 |
73 | type Resp struct {
74 | ImgUrl string `json:"imgurl"`
75 | Code string `json:"code"`
76 | }
77 |
78 | var randomUrl = "https://api.ixiaowai.cn/mcapi/mcapi.php?return=json"
79 |
80 | var client = http.Client{
81 | Transport: &http.Transport{
82 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
83 | },
84 | }
85 |
86 | func randomImage() (*[]byte, error) {
87 | r, err := client.Get(randomUrl)
88 | if err != nil {
89 | return nil, err
90 | }
91 | robots, err := ioutil.ReadAll(r.Body)
92 | if err != nil {
93 | return nil, err
94 | }
95 | var resp Resp
96 | err = json.Unmarshal(robots, &resp)
97 | if err != nil {
98 | return nil, err
99 | }
100 | defer r.Body.Close()
101 | if resp.Code != "200" {
102 | return nil, errors.New("请求接口失败")
103 | }
104 | b, e := getFile(resp.ImgUrl)
105 | return &b, e
106 | }
107 |
108 | func getFile(url string) ([]byte, error) {
109 | // url = strings.Replace(url, "large", "original", -1)
110 | path := getFileName(url)
111 | exists, _ := pathExists(path)
112 | if exists {
113 | file, err := os.Open(path)
114 | if err != nil {
115 | panic(err)
116 | }
117 | defer file.Close()
118 | content, err := ioutil.ReadAll(file)
119 | return content, err
120 | }
121 | r, err := http.DefaultClient.Get(url)
122 | if err != nil {
123 | return nil, err
124 | }
125 | content, err := ioutil.ReadAll(r.Body)
126 | if err != nil {
127 | return nil, err
128 | }
129 | _ = ioutil.WriteFile(path, content, 0644)
130 | return content, err
131 | }
132 |
133 | func getFileName(url string) string {
134 | i := strings.LastIndex(url, "/")
135 | return fmt.Sprintf("./mc/%v", url[i+1:])
136 | }
137 |
138 | func pathExists(path string) (bool, error) {
139 | _, err := os.Stat(path)
140 | if err == nil {
141 | return true, nil
142 | }
143 | if os.IsNotExist(err) {
144 | return false, nil
145 | }
146 | return false, err
147 | }
148 |
--------------------------------------------------------------------------------
/pkg/plugins/pixiv/http.go:
--------------------------------------------------------------------------------
1 | package pixiv
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "os"
10 | "strings"
11 | )
12 |
13 | func GetImage() (*[]byte, error) {
14 | return nil, nil
15 | }
16 |
17 | var setTuUrl = "https://api.acgmx.com/public/setu?type=json&ranking_type=illust"
18 |
19 | type Resp struct {
20 | Code int `json:"code"`
21 | Msg string `json:"msg"`
22 | Data *data `json:"data"`
23 | }
24 |
25 | type data struct {
26 | Illust string `json:"illust"`
27 | Title string `json:"title"`
28 | Large string `json:"large"`
29 | User *user `json:"user"`
30 | Originals []*original `json:"originals"`
31 | }
32 |
33 | type user struct {
34 | ID string `json:"id"`
35 | Name string `json:"name"`
36 | }
37 |
38 | type original struct {
39 | Url string `json:"url"`
40 | }
41 | type ImageForSend struct {
42 | Title string
43 | SourceFrom string
44 | Images *[]*[]byte
45 | UserName string
46 | IllustID string
47 | }
48 |
49 | func getSetu() (*ImageForSend, error) {
50 | req, err := http.NewRequest("GET", setTuUrl, nil)
51 | if err != nil {
52 | return nil, err
53 | }
54 | token := os.Getenv("BOT_PIXIV_TOKEN")
55 | req.Header.Add("token", token)
56 | r, err := http.DefaultClient.Do(req)
57 | if err != nil {
58 | return nil, err
59 | }
60 | defer r.Body.Close()
61 | robots, err := ioutil.ReadAll(r.Body)
62 | if err != nil {
63 | return nil, err
64 | }
65 | var resp Resp
66 | err = json.Unmarshal(robots, &resp)
67 | if err != nil {
68 | return nil, err
69 | }
70 | if !(resp.Code == 200 || resp.Code == 201) {
71 | return nil, errors.New(resp.Msg)
72 | }
73 |
74 | result := &ImageForSend{}
75 | result.IllustID = resp.Data.Illust
76 | result.UserName = resp.Data.User.Name
77 | result.Images = getImages(&resp)
78 | return result, nil
79 | }
80 |
81 | func getImages(resp *Resp) *[]*[]byte {
82 | var images []*[]byte
83 | for index, v := range resp.Data.Originals {
84 | image, err := getImage(resp.Data.Illust, index, v.Url)
85 | if err != nil {
86 | continue
87 | }
88 | images = append(images, image)
89 | }
90 | return &images
91 | }
92 |
93 | func getImage(id string, index int, url string) (*[]byte, error) {
94 | path := "./pixiv"
95 | // fmt.Sprintf("/%v", id)
96 | // fileName := getFileName(url)
97 | i := strings.LastIndex(url, ".")
98 | fileName := fmt.Sprintf("%v_%v.%v", id, index, url[i+1:])
99 | filePath := fmt.Sprintf("%v/%v", path, fileName)
100 | ok, _ := pathExists(filePath)
101 | if ok {
102 | file, err := os.Open(filePath)
103 | if err != nil {
104 | panic(err)
105 | }
106 | defer file.Close()
107 | content, err := ioutil.ReadAll(file)
108 | return &content, err
109 | }
110 | exists, _ := pathExists(path)
111 | if !exists {
112 | os.Mkdir(path, 0777)
113 | }
114 | r, err := http.DefaultClient.Get(url)
115 | if err != nil {
116 | return nil, err
117 | }
118 | defer r.Body.Close()
119 | robots, err := ioutil.ReadAll(r.Body)
120 | if err != nil {
121 | return nil, err
122 | }
123 | ioutil.WriteFile(filePath, robots, 0777)
124 | return &robots, nil
125 | }
126 |
127 | func getFileName(url string) string {
128 | i := strings.LastIndex(url, "/")
129 | return url[i+1:]
130 | }
131 |
132 | func pathExists(path string) (bool, error) {
133 | _, err := os.Stat(path)
134 | if err == nil {
135 | return true, nil
136 | }
137 | if os.IsNotExist(err) {
138 | return false, nil
139 | }
140 | return false, err
141 | }
142 |
143 | func init() {
144 | exists, _ := pathExists("./pixiv")
145 | if !exists {
146 | os.Mkdir("./pixiv", 0777)
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/pkg/plugins/pixiv/pixiv.go:
--------------------------------------------------------------------------------
1 | package pixiv
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/Mrs4s/MiraiGo/message"
9 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
10 | )
11 |
12 | // Plugin Pixiv助手插件
13 | type Plugin struct {
14 | plugins.NoSortPlugin
15 | plugins.NoInitPlugin
16 | plugins.AlwaysNotFireNextEventPlugin
17 | }
18 |
19 | func init() {
20 | p := Plugin{}
21 | plugins.RegisterOnMessagePlugin(p)
22 | // plugins.RegisterSchedulerPlugin(p)
23 | }
24 |
25 | // PluginInfo PluginInfo
26 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
27 | return &plugins.PluginInfo{
28 | ID: ".pixiv",
29 | Name: "Pixiv助手",
30 | }
31 | }
32 |
33 | // IsFireEvent 是否触发
34 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
35 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
36 | v := msg.Elements[0]
37 | field, ok := v.(*message.TextElement)
38 | return ok && strings.HasPrefix(field.Content, ".pixiv")
39 | }
40 | return false
41 | }
42 |
43 | // OnMessageEvent OnMessageEvent
44 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
45 | result := &plugins.MessageResponse{}
46 | var elements []message.IMessageElement
47 | res, err := getSetu()
48 | if err != nil {
49 | return nil, err
50 | }
51 | elements = append(elements, message.NewText(fmt.Sprintf("标题:%v\n作者:%v\n原地址:https://www.pixiv.net/artworks/%v\n", res.Title, res.UserName, res.IllustID)))
52 | if plugins.GroupMessage == request.MessageType {
53 | for _, image := range *res.Images {
54 | imageElement, err := request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(*image))
55 | if err != nil {
56 | return nil, err
57 | }
58 | elements = append(elements, imageElement)
59 | }
60 |
61 | } else {
62 | for _, image := range *res.Images {
63 | imageElement, err := request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(*image))
64 | if err != nil {
65 | return nil, err
66 | }
67 | elements = append(elements, imageElement)
68 | }
69 | }
70 | result.Elements = elements
71 | return result, nil
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/plugins/pixiv/pixiv.go.bak:
--------------------------------------------------------------------------------
1 | // package pixiv
2 |
3 | // import (
4 | // "bytes"
5 | // "fmt"
6 | // "io/ioutil"
7 | // "net/http"
8 | // "strings"
9 |
10 | // "github.com/Logiase/MiraiGo-Template/bot"
11 | // "github.com/Mrs4s/MiraiGo/message"
12 | // "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
13 | // "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
14 | // )
15 |
16 | // // Plugin Pixiv助手插件
17 | // type Plugin struct {
18 | // plugins.NoSortPlugin
19 | // plugins.NoInitPlugin
20 | // plugins.AlwaysNotFireNextEventPlugin
21 | // }
22 |
23 | // func init() {
24 | // p := Plugin{}
25 | // plugins.RegisterOnMessagePlugin(p)
26 | // plugins.RegisterSchedulerPlugin(p)
27 | // }
28 |
29 | // // PluginInfo PluginInfo
30 | // func (w Plugin) PluginInfo() *plugins.PluginInfo {
31 | // return &plugins.PluginInfo{
32 | // ID: ".pixiv",
33 | // Name: "Pixiv助手",
34 | // }
35 | // }
36 |
37 | // // IsFireEvent 是否触发
38 | // func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
39 | // if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
40 | // v := msg.Elements[0]
41 | // field, ok := v.(*message.TextElement)
42 | // return ok && strings.HasPrefix(field.Content, ".pixiv")
43 | // }
44 | // return false
45 | // }
46 |
47 | // // OnMessageEvent OnMessageEvent
48 | // func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
49 | // result := &plugins.MessageResponse{}
50 | // var elements []message.IMessageElement
51 |
52 | // v := request.Elements[0]
53 | // field, _ := v.(*message.TextElement)
54 | // context := field.Content
55 | // params := strings.Split(context, " ")
56 | // if len(params) > 1 {
57 | // command := strings.TrimSpace(params[1])
58 | // switch command {
59 | // case "r":
60 | // var cacheKey string
61 | // if plugins.GroupMessage == request.MessageType {
62 | // cacheKey = fmt.Sprintf("%v%v", request.MessageType, request.GroupCode)
63 | // } else {
64 | // cacheKey = fmt.Sprintf("%v%v", request.MessageType, request.Sender.Uin)
65 | // }
66 | // res, err := random(cacheKey)
67 | // if err == nil && res != nil {
68 | // elements = append(elements, message.NewText(fmt.Sprintf("标题:%v\n作者:%v\n原地址:https://www.pixiv.net/artworks/%v\n", res.Title, res.UserName, res.IllustID)))
69 | // for _, url := range res.Urls {
70 | // r, _ := http.DefaultClient.Get(url)
71 | // robots, _ := ioutil.ReadAll(r.Body)
72 | // r.Body.Close()
73 | // var image message.IMessageElement
74 | // if plugins.GroupMessage == request.MessageType {
75 | // image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(robots))
76 | // if err != nil {
77 | // elements = append(elements, message.NewText("\n"+url+"\n"))
78 | // } else {
79 | // elements = append(elements, image)
80 | // }
81 | // } else {
82 | // image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(robots))
83 | // if err != nil {
84 | // elements = append(elements, message.NewText("\n"+url+"\n"))
85 | // } else {
86 | // elements = append(elements, image)
87 | // }
88 | // }
89 | // }
90 | // }
91 | // if plugins.GroupMessage == request.MessageType && len(params) > 2 {
92 | // bucket := []byte(w.PluginInfo().ID)
93 | // key := []byte(fmt.Sprintf("pixiv.enable.%v", request.GroupCode))
94 | // if params[2] == "Y" {
95 | // storage.Put(bucket, key, storage.IntToBytes(1))
96 | // elements = append(elements, message.NewText("\n已开启定时发送"))
97 | // } else if params[2] == "N" {
98 | // storage.Delete(bucket, key)
99 | // elements = append(elements, message.NewText("\n已关闭定时发送"))
100 | // }
101 | // }
102 | // case "r18":
103 | // var cacheKey string
104 | // if plugins.GroupMessage == request.MessageType {
105 | // cacheKey = fmt.Sprintf("%v%v", request.MessageType, request.GroupCode)
106 | // } else {
107 | // cacheKey = fmt.Sprintf("%v%v", request.MessageType, request.Sender.Uin)
108 | // }
109 | // res, err := randomR18(string(cacheKey))
110 | // if err == nil && res != nil {
111 | // elements = append(elements, message.NewText(fmt.Sprintf("标题:%v\n作者:%v\n原地址:https://www.pixiv.net/artworks/%v\n", res.Title, res.UserName, res.IllustID)))
112 | // for _, url := range res.Urls {
113 | // r, _ := http.DefaultClient.Get(url)
114 | // robots, _ := ioutil.ReadAll(r.Body)
115 | // r.Body.Close()
116 | // var image message.IMessageElement
117 | // if plugins.GroupMessage == request.MessageType {
118 | // elements = append(elements, message.NewText("\n"+url+"\n"))
119 | // } else {
120 | // image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(robots))
121 | // if err != nil {
122 | // elements = append(elements, message.NewText("\n"+url+"\n"))
123 | // } else {
124 | // elements = append(elements, image)
125 | // }
126 | // }
127 | // }
128 | // }
129 | // default:
130 | // elements = append(elements, message.NewText("错误的命令"))
131 | // }
132 | // }
133 | // result.Elements = elements
134 | // return result, nil
135 | // }
136 |
137 | // // Cron cron表达式
138 | // func (p Plugin) Cron() string {
139 | // return "0 */10 * * * ?"
140 | // }
141 |
142 | // // Run 回调
143 | // func (p Plugin) Run(bot *bot.Bot) error {
144 | // bucket := []byte(p.PluginInfo().ID)
145 | // bot.ReloadGroupList()
146 | // groups, err := bot.GetGroupList()
147 | // if err != nil {
148 | // fmt.Printf("pixiv r send msg err %v", err)
149 | // }
150 | // for _, g := range groups {
151 | // key := []byte(fmt.Sprintf("pixiv.enable.%v", g.Code))
152 | // value, _ := storage.GetValue(bucket, key)
153 | // if value != nil && storage.BytesToInt(value) == 1 {
154 | // cacheKey := fmt.Sprintf("%v%v", plugins.GroupMessage, g.Code)
155 | // sendingMessage := &message.SendingMessage{}
156 | // var elements []message.IMessageElement
157 | // res, err := random(cacheKey)
158 | // if err != nil {
159 | // continue
160 | // }
161 | // elements = append(elements, message.NewText(fmt.Sprintf("标题:%v\n作者:%v\n原地址:https://www.pixiv.net/artworks/%v\n", res.Title, res.UserName, res.IllustID)))
162 | // for _, url := range res.Urls {
163 | // r, _ := http.DefaultClient.Get(url)
164 | // robots, _ := ioutil.ReadAll(r.Body)
165 | // defer r.Body.Close()
166 | // var image message.IMessageElement
167 | // image, err = bot.UploadGroupImage(g.Code, bytes.NewReader(robots))
168 | // if err != nil {
169 | // continue
170 | // }
171 | // elements = append(elements, image)
172 | // }
173 | // elements = append(elements, message.NewText("\n来自定时发送,可以发送[.pixiv r N]关闭"))
174 | // sendingMessage.Elements = elements
175 | // go bot.QQClient.SendGroupMessage(g.Code, sendingMessage)
176 | // }
177 | // }
178 | // return nil
179 | // }
180 |
--------------------------------------------------------------------------------
/pkg/plugins/pixiv/random.go.bak:
--------------------------------------------------------------------------------
1 | package pixiv
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "net/url"
10 | "time"
11 |
12 | "github.com/dezhiShen/MiraiGo-Bot/pkg/cache"
13 | )
14 |
15 | // var randomUrl = "https://open.pixivic.net/wallpaper/%v/random?size=%v&domain=https://i.pixiv.cat&webp=0&detail=1"
16 |
17 | // func randomImage(platform, size, msgType string) (*[]byte, error) {
18 | // url := fmt.Sprintf(randomUrl, platform, size)
19 | // if msgType == "image" {
20 | // if platform == "" {
21 | // platform = "mobile"
22 | // }
23 | // r, err := http.DefaultClient.Get(url)
24 | // if err != nil {
25 | // return nil, err
26 | // }
27 | // if r.StatusCode == 404 {
28 | // log.Print("发生404错误")
29 | // return nil, nil
30 | // }
31 | // robots, err := ioutil.ReadAll(r.Body)
32 | // if err != nil {
33 | // return nil, err
34 | // }
35 | // r.Body.Close()
36 | // return &robots, nil
37 | // }
38 | // c := http.Client{}
39 | // var imageSrc []byte
40 | // c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
41 | // imageSrc = []byte(req.URL.String())
42 | // fmt.Print(req.URL.String())
43 | // return errors.New("stop by sendType")
44 | // }
45 | // _, err := c.Get(url)
46 | // if imageSrc == nil {
47 | // fmt.Println(err)
48 | // return nil, errors.New("发生意外,请重试")
49 | // }
50 | // return &imageSrc, nil
51 | // }
52 |
53 | type RankingData struct {
54 | Title string `json:"title"`
55 | UserId string `json:"user_id"`
56 | UserName string `json:"user_name"`
57 | IllustID string `json:"illust_id"`
58 | Url string `json:"url"`
59 | }
60 |
61 | type PictureData struct {
62 | Error string `json:"error"`
63 | Success bool `json:"success"`
64 | OriginalUrlsProxy []string `json:"original_urls_proxy"`
65 | OriginalUrlProxy string `json:"original_url_proxy"`
66 | }
67 |
68 | type Picture struct {
69 | Title string `json:"title"`
70 | UserId string `json:"userId"`
71 | UserName string `json:"userName"`
72 | IllustID string `json:"illustId"`
73 | Urls []string `json:"urls"`
74 | }
75 |
76 | func randomAImage() (*RankingData, error) {
77 | randomUrl := "https://api.loli.st/pixiv/random.php?type=json&r18=true"
78 | r, err := http.DefaultClient.Get(randomUrl)
79 | if err != nil {
80 | return nil, err
81 | }
82 |
83 | robots, err := ioutil.ReadAll(r.Body)
84 | r.Body.Close()
85 | if err != nil {
86 | return nil, err
87 | }
88 | var data RankingData
89 | err = json.Unmarshal(robots, &data)
90 | return &data, err
91 | }
92 |
93 | func random(key string) (*Picture, error) {
94 | var data *RankingData
95 | var err error
96 | for i := 0; i < 10; i++ {
97 | data, err = randomAImage()
98 | if err != nil {
99 | return nil, err
100 | }
101 | theKey := fmt.Sprintf("pixiv_exists.%v.%v", key, data.IllustID)
102 | _, ok := cache.Get(theKey)
103 | if !ok {
104 | cache.Set(theKey, "Y", 24*time.Hour)
105 | break
106 | }
107 | }
108 | urlData := url.Values{
109 | "p": []string{data.IllustID},
110 | }
111 | r, err := http.DefaultClient.PostForm("https://api.pixiv.cat/v1/generate", urlData)
112 | if err != nil {
113 | return nil, err
114 | }
115 | robots, err := ioutil.ReadAll(r.Body)
116 | r.Body.Close()
117 | if err != nil {
118 | return nil, err
119 | }
120 | var pictureData PictureData
121 | err = json.Unmarshal(robots, &pictureData)
122 | if err != nil {
123 | return nil, err
124 | }
125 | if !pictureData.Success {
126 | return nil, errors.New(pictureData.Error)
127 | }
128 | result := &Picture{
129 | Title: data.Title,
130 | UserId: data.UserId,
131 | UserName: data.UserName,
132 | IllustID: data.IllustID,
133 | }
134 | if len(pictureData.OriginalUrlsProxy) > 0 {
135 | result.Urls = append(result.Urls, pictureData.OriginalUrlsProxy...)
136 | } else {
137 | result.Urls = append(result.Urls, pictureData.OriginalUrlProxy)
138 | }
139 | return result, nil
140 | }
141 |
142 | func getRank() (*RankingData, error) {
143 | r18Url := "https://api.loli.st/pixiv/?mode=daily_r18"
144 | r, err := http.DefaultClient.Get(r18Url)
145 | if err != nil {
146 | return nil, err
147 | }
148 |
149 | robots, err := ioutil.ReadAll(r.Body)
150 | r.Body.Close()
151 | if err != nil {
152 | return nil, err
153 | }
154 | var data RankingData
155 | err = json.Unmarshal(robots, &data)
156 | return &data, err
157 | }
158 |
159 | func randomR18(key string) (*Picture, error) {
160 | var data *RankingData
161 | var err error
162 | for i := 0; i < 10; i++ {
163 | data, err = getRank()
164 | if err != nil {
165 | return nil, err
166 | }
167 | theKey := fmt.Sprintf("pixiv_r18_exists.%v.%v", key, data.IllustID)
168 | _, ok := cache.Get(theKey)
169 | if !ok {
170 | cache.Set(theKey, "Y", 24*time.Hour)
171 | break
172 | }
173 | }
174 | urlData := url.Values{
175 | "p": []string{data.IllustID},
176 | }
177 | r, err := http.DefaultClient.PostForm("https://api.pixiv.cat/v1/generate", urlData)
178 | if err != nil {
179 | return nil, err
180 | }
181 | robots, err := ioutil.ReadAll(r.Body)
182 | r.Body.Close()
183 | if err != nil {
184 | return nil, err
185 | }
186 | var pictureData PictureData
187 | err = json.Unmarshal(robots, &pictureData)
188 | if err != nil {
189 | return nil, err
190 | }
191 | if !pictureData.Success {
192 | return nil, errors.New(pictureData.Error)
193 | }
194 | result := &Picture{
195 | Title: data.Title,
196 | UserId: data.UserId,
197 | UserName: data.UserName,
198 | IllustID: data.IllustID,
199 | }
200 | if len(pictureData.OriginalUrlsProxy) > 0 {
201 | result.Urls = append(result.Urls, pictureData.OriginalUrlsProxy...)
202 | } else {
203 | result.Urls = append(result.Urls, pictureData.OriginalUrlProxy)
204 | }
205 | return result, nil
206 | }
207 |
--------------------------------------------------------------------------------
/pkg/plugins/random/random.go:
--------------------------------------------------------------------------------
1 | package random
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "time"
7 |
8 | "github.com/Mrs4s/MiraiGo/message"
9 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
10 | )
11 |
12 | // Plugin Random插件
13 | type Plugin struct {
14 | plugins.NoSortPlugin
15 | plugins.NoInitPlugin
16 | plugins.AlwaysNotFireNextEventPlugin
17 | }
18 |
19 | // PluginInfo PluginInfo
20 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
21 | return &plugins.PluginInfo{
22 | ID: "Random",
23 | Name: "Random插件",
24 | }
25 | }
26 |
27 | // IsFireEvent 是否触发
28 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
29 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
30 | v := msg.Elements[0]
31 | field, ok := v.(*message.TextElement)
32 | return ok && field.Content == ".r"
33 | }
34 | return false
35 | }
36 |
37 | // OnMessageEvent OnMessageEvent
38 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
39 | result := &plugins.MessageResponse{
40 | Elements: make([]message.IMessageElement, 1),
41 | }
42 | name := request.Sender.CardName
43 | if name == "" {
44 | name = request.Sender.Nickname
45 | }
46 | rand.Seed(time.Now().UnixNano())
47 | v := rand.Intn(100)
48 | result.Elements[0] = message.NewText(fmt.Sprintf("[%v]掷出了: %v", name, v))
49 | return result, nil
50 | }
51 |
52 | func init() {
53 | plugins.RegisterOnMessagePlugin(Plugin{})
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/plugins/rss/manage.go:
--------------------------------------------------------------------------------
1 | package rss
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
8 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
9 | "github.com/mmcdole/gofeed"
10 | )
11 |
12 | var rss_prefix string = "rss.url:"
13 | var rss_url_distributor string = "rss-url.distributor:"
14 |
15 | func listenFeed(url string, req *plugins.MessageRequest) error {
16 | rss_url_distributor_key :=
17 | rss_url_distributor + url + string(req.MessageType)
18 | distributorInfo := &info{
19 | Type: string(req.MessageType),
20 | }
21 | if req.MessageType == "group" {
22 | rss_url_distributor_key += fmt.Sprintf(":%v", req.GroupCode)
23 | distributorInfo.Code = req.GroupCode
24 | } else {
25 | rss_url_distributor_key += fmt.Sprintf(":%v", req.Sender.Uin)
26 | distributorInfo.Code = req.Sender.Uin
27 | }
28 | storage.Put([]byte(pluginId), []byte(rss_prefix+url), []byte(url))
29 | jsonBytes, _ := json.Marshal(distributorInfo)
30 | storage.Put([]byte(pluginId), []byte(rss_url_distributor_key), jsonBytes)
31 | return nil
32 | }
33 | func unListenFeed(url string, req *plugins.MessageRequest) error {
34 | rss_url_distributor_key :=
35 | rss_url_distributor + url + string(req.MessageType)
36 | if req.MessageType == "group" {
37 | rss_url_distributor_key += fmt.Sprintf(":%v", req.GroupCode)
38 | } else {
39 | rss_url_distributor_key += fmt.Sprintf(":%v", req.Sender.Uin)
40 | }
41 | storage.Delete([]byte(pluginId), []byte(rss_url_distributor_key))
42 | var hasRss = false
43 | storage.GetByPrefix([]byte(pluginId), []byte(rss_url_distributor+url), func(b1, b2 []byte) error {
44 | if hasRss {
45 | return nil
46 | }
47 | hasRss = true
48 | return nil
49 | })
50 | if !hasRss {
51 | storage.Delete([]byte(pluginId), []byte(rss_prefix+url))
52 | }
53 | return nil
54 | }
55 |
56 | func getFeed(url string) (*gofeed.Feed, error) {
57 | fp := gofeed.NewParser()
58 | feed, err := fp.ParseURL(url)
59 | if err != nil {
60 | logger.Infof("抓取Feed时发生异常:%v", url)
61 | }
62 | return feed, err
63 | }
64 |
65 | func updateFeed(url string, d int64) ([]*gofeed.Item, error) {
66 | logger.Infof("开始抓取更新:%s", url)
67 | feed, err := getFeed(url)
68 | if err != nil {
69 | return nil, err
70 | }
71 | var results []*gofeed.Item
72 | for i, e := range feed.Items {
73 | itemLastUpdatedDate := e.PublishedParsed
74 | if itemLastUpdatedDate == nil {
75 | itemLastUpdatedDate = e.UpdatedParsed
76 | }
77 | logger.Infof("推文时间:%v", itemLastUpdatedDate.Local().Format("2006-01-02 15:04:05"))
78 | if itemLastUpdatedDate.Local().Unix() <= d {
79 | //>0避免置顶的影响
80 | if i > 0 {
81 | break
82 | } else {
83 | continue
84 | }
85 | }
86 | results = append(results, e)
87 | }
88 | logger.Infof("数量:%v", len(results))
89 | logger.Infof("结束更新:%s", url)
90 | return results, nil
91 | }
92 |
93 | func getAllFeed(req *plugins.MessageRequest) []string {
94 | var urls []string
95 | storage.GetByPrefix([]byte(pluginId), []byte(rss_prefix), func(b1, b2 []byte) error {
96 | urls = append(urls, string(b2))
97 | return nil
98 | })
99 | var result []string
100 | for _, url := range urls {
101 | rss_url_distributor_key :=
102 | rss_url_distributor + url + string(req.MessageType)
103 | if req.MessageType == "group" {
104 | rss_url_distributor_key += fmt.Sprintf(":%v", req.GroupCode)
105 | } else {
106 | rss_url_distributor_key += fmt.Sprintf(":%v", req.Sender.Uin)
107 | }
108 | v, _ := storage.GetValue([]byte(pluginId), []byte(rss_url_distributor_key))
109 | if v != nil {
110 | result = append(result, url)
111 | }
112 | }
113 | return result
114 | }
115 |
--------------------------------------------------------------------------------
/pkg/plugins/rss/manage_test.go:
--------------------------------------------------------------------------------
1 | package rss
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func Test_getFeed(t *testing.T) {
9 | // feed1, err := getFeed("http://192.168.31.104/weibo/user/7505824632")
10 | // if err != nil {
11 | // panic(err)
12 | // }
13 | // println(feed1)
14 | feed2, err := updateFeed("https://nitter.namazso.eu/212moving/rss", time.Now().Unix())
15 | if err != nil {
16 | panic(err)
17 | }
18 | print(feed2)
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/plugins/rss/rss.go:
--------------------------------------------------------------------------------
1 | package rss
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "io/ioutil"
8 | "net/http"
9 | "strings"
10 | "time"
11 |
12 | "github.com/Logiase/MiraiGo-Template/bot"
13 | "github.com/Mrs4s/MiraiGo/client"
14 | "github.com/Mrs4s/MiraiGo/message"
15 | "github.com/PuerkitoBio/goquery"
16 | "github.com/dezhiShen/MiraiGo-Bot/pkg/command"
17 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
18 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
19 | "github.com/mmcdole/gofeed"
20 | "github.com/sirupsen/logrus"
21 | )
22 |
23 | // Plugin menhear
24 | type Plugin struct {
25 | plugins.NoSortPlugin
26 | plugins.NoInitPlugin
27 | plugins.AlwaysNotFireNextEventPlugin
28 | }
29 |
30 | var logger = logrus.WithField("bot-plugin", "rss")
31 | var cron = "0 */15 * * * ?"
32 | var pluginId = ".rss"
33 |
34 | // PluginInfo PluginInfo
35 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
36 | return &plugins.PluginInfo{
37 | ID: pluginId,
38 | Name: "rss",
39 | }
40 | }
41 |
42 | // IsFireEvent 是否触发
43 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
44 | v := msg.Elements[0]
45 | field, ok := v.(*message.TextElement)
46 | return ok && strings.HasPrefix(field.Content, ".feed")
47 | }
48 |
49 | type rssReq struct {
50 | Event string `short:"e" long:"event" description:"动作" default:"add"`
51 | }
52 |
53 | // OnMessageEvent OnMessageEvent
54 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
55 | var elements []message.IMessageElement
56 | context := ""
57 | for _, v := range request.Elements {
58 | if v.Type() == message.Text {
59 | field, _ := v.(*message.TextElement)
60 | context += (field.Content)
61 | }
62 | }
63 | req := rssReq{}
64 | commands, err := command.Parse(".feed", &req, strings.Split(context, " "))
65 | if err != nil {
66 | return nil, err
67 | }
68 | if req.Event == "add" {
69 | for i := 1; i < len(commands); i++ {
70 | url := commands[i]
71 | // prefix := []byte(rss_prefix + url)
72 | // storage.Put([]byte(w.PluginInfo().ID), prefix, []byte(url))
73 | if strings.TrimSpace(url) == "" {
74 | continue
75 | }
76 | err := listenFeed(url, request)
77 | if err != nil {
78 | return nil, err
79 | }
80 | elements = append(elements, message.NewText("订阅成功:"+url))
81 | }
82 | } else if req.Event == "del" {
83 | for i := 1; i < len(commands); i++ {
84 | url := commands[i]
85 | // prefix := []byte(rss_prefix + url)
86 | // storage.Put([]byte(w.PluginInfo().ID), prefix, []byte(url))
87 | err := unListenFeed(url, request)
88 | if err != nil {
89 | return nil, err
90 | }
91 | elements = append(elements, message.NewText("移除成功:"+url))
92 | }
93 | } else if req.Event == "list" {
94 | urls := getAllFeed(request)
95 | if len(urls) > 0 {
96 | for _, e := range urls {
97 | elements = append(elements, message.NewText(e+"\n"))
98 | }
99 | } else {
100 | elements = append(elements, message.NewText("当前无订阅"))
101 | }
102 | } else if req.Event == "update" {
103 | now := int64(time.Now().Unix()/900) * 900
104 | for i := 1; i < len(commands); i++ {
105 | url := commands[i]
106 | items, err := updateFeed(url, now)
107 | if err != nil {
108 | return nil, errors.New("更新失败" + err.Error())
109 | }
110 | feeds := items2Feeds(items)
111 | flag := false
112 | for _, f := range feeds {
113 | if request.MessageType == plugins.GroupMessage {
114 | telements, _ := feed2MessageElements(f, request.QQClient, "group", request.GroupCode)
115 | if len(telements) > 0 {
116 | flag = true
117 | request.QQClient.SendGroupMessage(request.GroupCode, &message.SendingMessage{
118 | Elements: telements,
119 | })
120 | }
121 | } else {
122 | telements, _ := feed2MessageElements(f, request.QQClient, "private", request.Sender.Uin)
123 | if len(telements) > 0 {
124 | flag = true
125 | request.QQClient.SendPrivateMessage(request.Sender.Uin, &message.SendingMessage{
126 | Elements: telements,
127 | })
128 | }
129 | }
130 | if err != nil {
131 | return nil, err
132 | }
133 | }
134 | if !flag {
135 | elements = append(elements, message.NewText("暂无更新"))
136 | }
137 | }
138 | }
139 | return &plugins.MessageResponse{
140 | Elements: elements,
141 | }, nil
142 | }
143 |
144 | func init() {
145 | plugin := Plugin{}
146 | plugins.RegisterOnMessagePlugin(plugin)
147 | plugins.RegisterSchedulerPlugin(plugin)
148 | }
149 |
150 | type info struct {
151 | Type string `json:"type"`
152 | Code int64 `json:"code"`
153 | }
154 | type oneOfFeed struct {
155 | Title string
156 | Link string
157 | Images []string
158 | ImagesBytes [][]byte
159 | }
160 |
161 | // Run 回调
162 | func (t Plugin) Run(bot *bot.Bot) error {
163 | prefix := []byte(rss_prefix)
164 | var urls []string
165 |
166 | storage.GetByPrefix([]byte(t.PluginInfo().ID), prefix, func(key, v []byte) error {
167 | url := string(v)
168 | urls = append(urls, url)
169 | return nil
170 | })
171 | now := time.Now().Local()
172 | logger.Infof("开始更新,更新时间:%v", now.Format("2006-01-02 15:04:05"))
173 | theDate := now.Unix() - 15*60
174 | for _, url := range urls {
175 | items, _ := updateFeed(url, theDate)
176 | feeds := items2Feeds(items)
177 | var infos []info
178 | storage.GetByPrefix([]byte(t.PluginInfo().ID), []byte(rss_url_distributor+url), func(key, v []byte) error {
179 | var info info
180 | json.Unmarshal(v, &info)
181 | infos = append(infos, info)
182 | return nil
183 | })
184 |
185 | for _, info := range infos {
186 | for _, oneFeed := range feeds {
187 | elemens, err := feed2MessageElements(oneFeed, bot.QQClient, info.Type, info.Code)
188 | if err != nil {
189 | return err
190 | }
191 | if info.Type == "group" {
192 | bot.SendGroupMessage(info.Code, &message.SendingMessage{
193 | Elements: elemens,
194 | })
195 | } else {
196 | bot.SendPrivateMessage(info.Code, &message.SendingMessage{
197 | Elements: elemens,
198 | })
199 | }
200 | }
201 |
202 | }
203 | }
204 | // bot.QQClient.Send
205 | return nil
206 | }
207 |
208 | // Cron cron表达式
209 | func (t Plugin) Cron() string {
210 | return cron
211 | }
212 |
213 | func items2Feeds(items []*gofeed.Item) []oneOfFeed {
214 | var feeds []oneOfFeed
215 | for _, feedItem := range items {
216 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(feedItem.Description))
217 | if err != nil {
218 | continue
219 | }
220 | e := oneOfFeed{
221 | Title: feedItem.Title,
222 | Link: feedItem.Link,
223 | }
224 | images := doc.Find("img")
225 | if images != nil && len(images.Nodes) > 0 {
226 | for _, node := range images.Nodes {
227 | src, exists := goquery.NewDocumentFromNode(node).Attr("src")
228 | if exists {
229 | e.Images = append(e.Images, src)
230 | r, _ := http.DefaultClient.Get(src)
231 | content, _ := ioutil.ReadAll(r.Body)
232 | e.ImagesBytes = append(e.ImagesBytes, content)
233 | }
234 | }
235 |
236 | }
237 | feeds = append(feeds, e)
238 | }
239 | return feeds
240 | }
241 | func feed2MessageElements(oneOfFeed oneOfFeed, client *client.QQClient, messageType string, code int64) ([]message.IMessageElement, error) {
242 | var messageElement []message.IMessageElement
243 | messageElement = append(messageElement, message.NewText(oneOfFeed.Title+"\n"))
244 | messageElement = append(messageElement, message.NewText(oneOfFeed.Link))
245 | for _, b := range oneOfFeed.ImagesBytes {
246 | if messageType == "group" {
247 | // sendingMessage := &message.SendingMessage{}
248 | image, err := client.UploadGroupImage(code, bytes.NewReader(b))
249 | if err != nil {
250 | logger.Error("上传图片发生异常")
251 | continue
252 | }
253 | messageElement = append(messageElement, image)
254 | } else {
255 | image, err := client.UploadPrivateImage(code, bytes.NewReader(b))
256 | if err != nil {
257 | logger.Error("上传图片发生异常")
258 | continue
259 | }
260 | messageElement = append(messageElement, image)
261 | }
262 | }
263 | return messageElement, nil
264 | }
265 |
--------------------------------------------------------------------------------
/pkg/plugins/segmentation/plugin.go:
--------------------------------------------------------------------------------
1 | package segmentation
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/Mrs4s/MiraiGo/message"
9 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
10 | "github.com/go-ego/gse"
11 | )
12 |
13 | // Plugin segmentation
14 | type Plugin struct {
15 | plugins.NoSortPlugin
16 | plugins.NoInitPlugin
17 | plugins.AlwaysNotFireNextEventPlugin
18 | }
19 |
20 | var (
21 | seg gse.Segmenter
22 | )
23 |
24 | // PluginInit 简单插件初始化
25 | func (p Plugin) PluginInit() {
26 | // 加载默认字典
27 | seg.LoadDict()
28 | }
29 |
30 | // PluginInfo PluginInfo
31 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
32 | return &plugins.PluginInfo{
33 | ID: "segmentation",
34 | Name: "分词插件",
35 | }
36 | }
37 |
38 | // IsFireEvent 是否触发
39 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
40 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
41 | v := msg.Elements[0]
42 | field, ok := v.(*message.TextElement)
43 | return ok && strings.HasPrefix(field.Content, ".segment")
44 | }
45 | return false
46 | }
47 |
48 | // OnMessageEvent OnMessageEvent
49 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
50 | result := &plugins.MessageResponse{}
51 | v := request.Elements[0]
52 | field, _ := v.(*message.TextElement)
53 | context := field.Content
54 | params := strings.TrimSpace(strings.TrimPrefix(context, ".segment"))
55 | if params == "" {
56 | return nil, errors.New("请输入要分析的文本")
57 | }
58 | var elements []message.IMessageElement
59 | text := strings.TrimSpace(params)
60 | sentiment := seg.Slice(text)
61 | elements = append(elements, message.NewText(text))
62 | elements = append(elements, message.NewText(fmt.Sprintf("\n结果: %v", sentiment)))
63 | result.Elements = elements
64 | return result, nil
65 | }
66 |
67 | func init() {
68 | plugins.RegisterOnMessagePlugin(Plugin{})
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/plugins/sovietjokes/sj_test.go:
--------------------------------------------------------------------------------
1 | package sovietjokes
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestSj(t *testing.T) {
11 |
12 | jokes := getJokes()
13 | rand.Seed(time.Now().UnixNano())
14 | v := rand.Intn(len(jokes) - 1)
15 | randJoke := jokes[v]
16 | fmt.Printf("%s\n", randJoke.Title)
17 | fmt.Printf("%s", randJoke.Content)
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/plugins/sovietjokes/sovietjokes.go:
--------------------------------------------------------------------------------
1 | package sovietjokes
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "math/rand"
8 | "net/http"
9 | "os"
10 | "strings"
11 | "time"
12 | "unsafe"
13 |
14 | "github.com/Mrs4s/MiraiGo/message"
15 | "github.com/antchfx/htmlquery"
16 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
17 | )
18 |
19 | type Plugin struct {
20 | plugins.NoSortPlugin
21 | plugins.NoInitPlugin
22 | plugins.AlwaysNotFireNextEventPlugin
23 | }
24 |
25 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
26 | return &plugins.PluginInfo{
27 | ID: "sovietjokes",
28 | Name: "苏联笑话插件",
29 | }
30 | }
31 |
32 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
33 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
34 | v := msg.Elements[0]
35 | field, ok := v.(*message.TextElement)
36 | return ok && field.Content == ".sj"
37 | }
38 | return false
39 | }
40 |
41 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
42 | var elements []message.IMessageElement
43 | //todo:
44 | randJoke := getRandomJoke()
45 | if unsafe.Sizeof(randJoke) == 0 {
46 | elements = append(elements, message.NewText("没有笑话库存了,你有兴趣加入吗?"))
47 | } else {
48 | elements = append(elements, message.NewText(fmt.Sprintf("%s\n", randJoke.Title)))
49 | elements = append(elements, message.NewText(fmt.Sprintf("%s", randJoke.Content)))
50 | }
51 |
52 | result := &plugins.MessageResponse{
53 | Elements: elements,
54 | }
55 | return result, nil
56 | }
57 |
58 | func init() {
59 | plugins.RegisterOnMessagePlugin(Plugin{})
60 | }
61 |
62 | func getRandomJoke() Joke {
63 |
64 | jokes := getJokes()
65 | rand.Seed(time.Now().UnixNano())
66 | v := rand.Intn(len(jokes) - 1)
67 | randJoke := jokes[v]
68 |
69 | return randJoke
70 | }
71 |
72 | func getJokes() []Joke {
73 |
74 | var jokes []Joke
75 | var emptyJk []Joke
76 | //检测根目录是否存在 sovietjokes.json ,不存在进行一次爬取
77 | if !sjExists() {
78 |
79 | jokes = analyzeJokes()
80 |
81 | jbytes, err := json.Marshal(&jokes)
82 |
83 | if err != nil {
84 | return emptyJk
85 | }
86 |
87 | err = ioutil.WriteFile(_joke_path_, jbytes, 0777)
88 | if err != nil {
89 | return emptyJk
90 | }
91 | } else {
92 | //解析文件
93 | jData, err := ioutil.ReadFile(_joke_path_)
94 |
95 | if err != nil {
96 | return emptyJk
97 | }
98 |
99 | datajson := []byte(jData)
100 |
101 | err = json.Unmarshal(datajson, &jokes)
102 |
103 | if err != nil {
104 | return emptyJk
105 | }
106 | }
107 | return jokes
108 | }
109 |
110 | func analyzeJokes() []Joke {
111 |
112 | var jokes []Joke
113 |
114 | sjUrl := "https://library.moegirl.org.cn/%E8%8B%8F%E8%81%94%E7%AC%91%E8%AF%9D"
115 | html := getHtml(sjUrl)
116 | if html == "" {
117 | return jokes
118 | }
119 |
120 | root, _ := htmlquery.Parse(strings.NewReader(html))
121 | heads := htmlquery.Find(root, "//span[@class='mw-headline']")
122 |
123 | for _, row := range heads {
124 |
125 | var j Joke
126 | //ttstr := strings.Split(fmt.Sprintf("%s", htmlquery.InnerText(row)), " ")[1]
127 |
128 | j.Title = fmt.Sprintf("%s", htmlquery.InnerText(row))
129 | jokes = append(jokes, j)
130 | }
131 |
132 | contents := htmlquery.Find(root, "//div[@class='poem']/p")
133 | for index, row := range contents {
134 |
135 | jokes[index].Content = fmt.Sprintf("%s", htmlquery.InnerText(row))
136 | }
137 |
138 | return jokes
139 | }
140 |
141 | func sjExists() bool {
142 |
143 | _, err := os.Stat(_joke_path_)
144 |
145 | if err != nil {
146 | return false
147 | }
148 |
149 | if os.IsNotExist(err) {
150 | return false
151 | }
152 |
153 | return true
154 | }
155 |
156 | func getHtml(url_ string) string {
157 | req, _ := http.NewRequest("GET", url_, nil)
158 | req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3776.0 Safari/537.36")
159 | client := &http.Client{Timeout: time.Second * 5}
160 | resp, err := client.Do(req)
161 | if err != nil {
162 | return ""
163 | }
164 | defer resp.Body.Close()
165 | data, err := ioutil.ReadAll(resp.Body)
166 | if err != nil && data == nil {
167 | return ""
168 | }
169 | return fmt.Sprintf("%s", data)
170 | }
171 |
172 | type Joke struct {
173 | Title string `json:"title"`
174 | Content string `json:"content"`
175 | }
176 |
177 | const _joke_path_ = "./sovietJokes.json"
178 |
--------------------------------------------------------------------------------
/pkg/plugins/thecat/thecat.go:
--------------------------------------------------------------------------------
1 | package thecat
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "io/ioutil"
8 | "net/http"
9 | "strings"
10 |
11 | "github.com/Mrs4s/MiraiGo/message"
12 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
13 | )
14 |
15 | var url = "https://api.thecatapi.com/v1/images/search"
16 |
17 | //?limit=1&size=med"
18 |
19 | // Plugin 猫猫图
20 | type Plugin struct {
21 | plugins.NoSortPlugin
22 | plugins.NoInitPlugin
23 | plugins.AlwaysNotFireNextEventPlugin
24 | }
25 |
26 | // PluginInfo PluginInfo
27 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
28 | return &plugins.PluginInfo{
29 | ID: "thecat",
30 | Name: "猫猫图",
31 | }
32 | }
33 |
34 | // IsFireEvent 是否触发
35 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
36 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
37 | v := msg.Elements[0]
38 | field, ok := v.(*message.TextElement)
39 | return ok && (field.Content == ".thecat" || field.Content == ".cat" || strings.Contains(field.Content, "来点猫猫图"))
40 | }
41 | return false
42 | }
43 |
44 | // OnMessageEvent OnMessageEvent
45 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
46 | result := &plugins.MessageResponse{
47 | Elements: make([]message.IMessageElement, 1),
48 | }
49 | b, err := getCatPic()
50 | if err != nil {
51 | return nil, err
52 | }
53 | var image message.IMessageElement
54 | if plugins.GroupMessage == request.MessageType {
55 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(b))
56 | } else {
57 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(b))
58 | }
59 | if err != nil {
60 | return nil, err
61 | }
62 | result.Elements[0] = image
63 | return result, nil
64 | }
65 |
66 | func init() {
67 | plugins.RegisterOnMessagePlugin(Plugin{})
68 | }
69 |
70 | type Data struct {
71 | ID string `json:"id"`
72 | Url string `json:"url"`
73 | }
74 |
75 | func getCatPic() ([]byte, error) {
76 | r, err := http.DefaultClient.Get(url)
77 | if err != nil {
78 | return nil, err
79 | }
80 | robots, err := ioutil.ReadAll(r.Body)
81 | if err != nil {
82 | return nil, err
83 | }
84 | r.Body.Close()
85 | resp := string(robots)
86 | if resp == "" {
87 | return nil, errors.New("请稍后重试")
88 | }
89 |
90 | var datas []Data
91 | err = json.Unmarshal(robots, &datas)
92 | if err != nil {
93 | return nil, err
94 | }
95 | if datas == nil || len(datas) == 0 {
96 | return nil, errors.New("请稍后重试")
97 | }
98 | imageResp, err := http.DefaultClient.Get(datas[0].Url)
99 | if err != nil {
100 | return nil, err
101 | }
102 | imageBytes, err := ioutil.ReadAll(imageResp.Body)
103 | if err != nil {
104 | return nil, err
105 | }
106 | imageResp.Body.Close()
107 | return imageBytes, nil
108 | }
109 |
--------------------------------------------------------------------------------
/pkg/plugins/thedog/thedog.go:
--------------------------------------------------------------------------------
1 | package thedog
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "io/ioutil"
8 | "net/http"
9 | "strings"
10 |
11 | "github.com/Mrs4s/MiraiGo/message"
12 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
13 | )
14 |
15 | var url = "https://api.thedogapi.com/v1/images/search"
16 |
17 | // Plugin 狗狗图
18 | type Plugin struct {
19 | plugins.NoSortPlugin
20 | plugins.NoInitPlugin
21 | plugins.AlwaysNotFireNextEventPlugin
22 | }
23 |
24 | // PluginInfo PluginInfo
25 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
26 | return &plugins.PluginInfo{
27 | ID: "thedog",
28 | Name: "狗狗图",
29 | }
30 | }
31 |
32 | // IsFireEvent 是否触发
33 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
34 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
35 | v := msg.Elements[0]
36 | field, ok := v.(*message.TextElement)
37 | return ok && (field.Content == ".thedog" || field.Content == ".dog" || strings.Contains(field.Content, "来点狗狗图"))
38 | }
39 | return false
40 | }
41 |
42 | // OnMessageEvent OnMessageEvent
43 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
44 | result := &plugins.MessageResponse{
45 | Elements: make([]message.IMessageElement, 1),
46 | }
47 | b, err := getPic()
48 | if err != nil {
49 | return nil, err
50 | }
51 | var image message.IMessageElement
52 | if plugins.GroupMessage == request.MessageType {
53 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(b))
54 | } else {
55 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(b))
56 | }
57 | if err != nil {
58 | return nil, err
59 | }
60 | result.Elements[0] = image
61 | return result, nil
62 | }
63 |
64 | func init() {
65 | plugins.RegisterOnMessagePlugin(Plugin{})
66 | }
67 |
68 | type Data struct {
69 | ID string `json:"id"`
70 | Url string `json:"url"`
71 | }
72 |
73 | func getPic() ([]byte, error) {
74 | r, err := http.DefaultClient.Get(url)
75 | if err != nil {
76 | return nil, err
77 | }
78 | robots, err := ioutil.ReadAll(r.Body)
79 | if err != nil {
80 | return nil, err
81 | }
82 | r.Body.Close()
83 | resp := string(robots)
84 | if resp == "" {
85 | return nil, errors.New("请稍后重试")
86 | }
87 |
88 | var datas []Data
89 | err = json.Unmarshal(robots, &datas)
90 | if err != nil {
91 | return nil, err
92 | }
93 | if len(datas) == 0 {
94 | return nil, errors.New("请稍后重试")
95 | }
96 | imageResp, err := http.DefaultClient.Get(datas[0].Url)
97 | if err != nil {
98 | return nil, err
99 | }
100 | imageBytes, err := ioutil.ReadAll(imageResp.Body)
101 | if err != nil {
102 | return nil, err
103 | }
104 | imageResp.Body.Close()
105 | return imageBytes, nil
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/plugins/tiangou/tiangou.go:
--------------------------------------------------------------------------------
1 | package tiangou
2 |
3 | import (
4 | "io/ioutil"
5 | "net/http"
6 |
7 | "github.com/Mrs4s/MiraiGo/message"
8 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
9 | )
10 |
11 | // Plugin 舔狗语录
12 | type Plugin struct {
13 | plugins.NoSortPlugin
14 | plugins.NoInitPlugin
15 | plugins.AlwaysNotFireNextEventPlugin
16 | }
17 |
18 | // PluginInfo PluginInfo
19 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
20 | return &plugins.PluginInfo{
21 | ID: ".tg",
22 | Name: "舔狗语录",
23 | }
24 | }
25 |
26 | // IsFireEvent 是否触发
27 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
28 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
29 | v := msg.Elements[0]
30 | field, ok := v.(*message.TextElement)
31 | return ok && field.Content == ".tg"
32 | }
33 | return false
34 | }
35 |
36 | // OnMessageEvent OnMessageEvent
37 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
38 | result := &plugins.MessageResponse{
39 | Elements: make([]message.IMessageElement, 1),
40 | }
41 | r, err := http.DefaultClient.Get("https://api.ixiaowai.cn/tgrj/index.php")
42 | if err != nil {
43 | return nil, err
44 | }
45 | robots, err := ioutil.ReadAll(r.Body)
46 | r.Body.Close()
47 | if err != nil {
48 | return nil, err
49 | }
50 | if robots == nil {
51 | return nil, nil
52 | }
53 | resp := string(robots)
54 | result.Elements[0] = message.NewText(resp)
55 | return result, nil
56 | }
57 |
58 | func init() {
59 | plugins.RegisterOnMessagePlugin(Plugin{})
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/plugins/tips/tips.go:
--------------------------------------------------------------------------------
1 | package tips
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "strconv"
8 | "strings"
9 | "time"
10 |
11 | "github.com/Logiase/MiraiGo-Template/bot"
12 | "github.com/Mrs4s/MiraiGo/message"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
14 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
15 | "github.com/go-basic/uuid"
16 | )
17 |
18 | // Info 消息详情
19 | type Info struct {
20 | ID string `json:"ID"`
21 | Content string `json:"content"`
22 | // 发送类型 1 私聊 2群聊
23 | SendType int `json:"sendType"`
24 | GroupCode int64 `json:"groupCode"`
25 | SenderUID int64 `json:"senderUID"`
26 | Hour int `json:"hour"`
27 | Minute int `json:"minute"`
28 | EveryDay bool `json:"everyDay"`
29 | }
30 |
31 | // Tips 提示
32 | type Tips struct {
33 | plugins.NoSortPlugin
34 | plugins.NoInitPlugin
35 | plugins.AlwaysNotFireNextEventPlugin
36 | }
37 |
38 | // PluginInfo PluginInfo
39 | func (t Tips) PluginInfo() *plugins.PluginInfo {
40 | return &plugins.PluginInfo{
41 | ID: "tips",
42 | Name: "提醒插件",
43 | }
44 | }
45 |
46 | // IsFireEvent 是否触发
47 | func (t Tips) IsFireEvent(msg *plugins.MessageRequest) bool {
48 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
49 | v := msg.Elements[0]
50 | field, ok := v.(*message.TextElement)
51 | return ok && strings.HasPrefix(field.Content, ".tips ")
52 | }
53 | return false
54 | }
55 |
56 | // OnMessageEvent OnMessageEvent
57 | func (t Tips) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
58 | result := &plugins.MessageResponse{
59 | Elements: make([]message.IMessageElement, 1),
60 | }
61 | v := request.Elements[0]
62 | field, _ := v.(*message.TextElement)
63 | context := field.Content
64 | params := strings.Split(context, " ")
65 | if params[1] == "help" {
66 | result.Elements[0] = message.NewText(fmt.Sprintf("请输入 .tips 小时:分钟 提示内容 每天重复(Y/N[默认])\n .tips remove 小时:分钟 将会删除由你创建的该时间点的提醒"))
67 | return result, nil
68 | }
69 | if params[1] == "remove" {
70 | timestr := params[2]
71 | hAndM := strings.Split(timestr, ":")
72 | if len(hAndM) != 2 {
73 | return nil, errors.New("错误的时间格式")
74 | }
75 | hour, err := strconv.Atoi(hAndM[0])
76 | if err != nil {
77 | return nil, errors.New("错误的时间格式")
78 | }
79 | min, err := strconv.Atoi(hAndM[1])
80 | if err != nil {
81 | return nil, errors.New("错误的时间格式")
82 | }
83 | prefix := []byte(fmt.Sprintf("tips.%v.%v", hour, min))
84 | var keys []([]byte)
85 | storage.GetByPrefix([]byte(t.PluginInfo().ID), prefix, func(k, v []byte) error {
86 | var info Info
87 | err := json.Unmarshal(v, &info)
88 | if err != nil {
89 | return err
90 | }
91 | if info.SenderUID == request.Sender.Uin {
92 | keys = append(keys, k)
93 | }
94 | return nil
95 | })
96 | for _, k := range keys {
97 | storage.Delete([]byte(t.PluginInfo().ID), k)
98 | }
99 | result.Elements[0] = message.NewText(fmt.Sprintf("已经移除由你创建的%v的提醒", timestr))
100 | return result, nil
101 |
102 | }
103 | if len(params) < 3 {
104 | return nil, errors.New("请输入 .tips 小时:分钟 提示内容 每天重复(Y/N[默认])")
105 | }
106 | timestr := params[1]
107 | content := params[2]
108 | sendType := 2
109 | if len(params) > 3 {
110 | if params[3] == "1" {
111 | sendType = 1
112 | } else {
113 | sendType = 2
114 | }
115 | }
116 | everyDay := len(params) > 4 && params[4] == "Y"
117 | hAndM := strings.Split(timestr, ":")
118 | if len(hAndM) != 2 {
119 | return nil, errors.New("错误的时间格式")
120 | }
121 | hour, err := strconv.Atoi(hAndM[0])
122 | if err != nil {
123 | return nil, errors.New("错误的时间格式")
124 | }
125 | min, err := strconv.Atoi(hAndM[1])
126 | if err != nil {
127 | return nil, errors.New("错误的时间格式")
128 | }
129 | if err != nil {
130 | return nil, err
131 | }
132 | info := Info{
133 | ID: uuid.New(),
134 | Content: content,
135 | SendType: sendType,
136 | SenderUID: request.Sender.Uin,
137 | Hour: hour,
138 | Minute: min,
139 | EveryDay: everyDay,
140 | GroupCode: request.GroupCode,
141 | }
142 | jsonBytes, _ := json.Marshal(info)
143 | err = storage.Put([]byte(t.PluginInfo().ID), []byte(fmt.Sprintf("tips.%v.%v.%v", info.Hour, info.Minute, info.ID)), jsonBytes)
144 | if err != nil {
145 | return nil, err
146 | }
147 | result.Elements[0] = message.NewText(fmt.Sprintf("For %v:提醒创建成功!", request.GetNickName()))
148 | return result, nil
149 | }
150 |
151 | // Run 回调
152 | func (t Tips) Run(bot *bot.Bot) error {
153 | nowLocal := time.Now().Local()
154 | prefix := []byte(fmt.Sprintf("tips.%v.%v.", nowLocal.Hour(), nowLocal.Minute()))
155 | storage.GetByPrefix([]byte(t.PluginInfo().ID), prefix, func(key, v []byte) error {
156 | var info Info
157 | err := json.Unmarshal(v, &info)
158 | if err != nil {
159 | return err
160 | }
161 | sendingMessage := &message.SendingMessage{}
162 | sendingMessage.Append(message.NewAt(info.SenderUID))
163 | sendingMessage.Append(message.NewText(info.Content))
164 | go bot.QQClient.SendGroupMessage(info.GroupCode, sendingMessage)
165 | if !info.EveryDay {
166 | go storage.Delete([]byte(t.PluginInfo().ID), key)
167 | }
168 | return nil
169 | })
170 | // bot.QQClient.Send
171 | return nil
172 | }
173 |
174 | // Cron cron表达式
175 | func (t Tips) Cron() string {
176 | return "0 */1 * * * ?"
177 | }
178 | func init() {
179 | tips := Tips{}
180 | plugins.RegisterOnMessagePlugin(tips)
181 | plugins.RegisterSchedulerPlugin(tips)
182 | }
183 |
--------------------------------------------------------------------------------
/pkg/plugins/todayFortune/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dezhishen/MiraiGo-Bot-Plugins/24519dd79fed87fbd54d6d9828cf8954afa6a5c7/pkg/plugins/todayFortune/test.jpg
--------------------------------------------------------------------------------
/pkg/plugins/todayFortune/todayFortune.go:
--------------------------------------------------------------------------------
1 | package todayFortune
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "strings"
7 |
8 | "github.com/Mrs4s/MiraiGo/message"
9 | "github.com/dezhiShen/MiraiGo-Bot-Plugins/pkg/fortune"
10 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
11 | )
12 |
13 | // Plugin Random插件
14 | type Plugin struct {
15 | plugins.NoSortPlugin
16 | plugins.NoInitPlugin
17 | plugins.AlwaysNotFireNextEventPlugin
18 | }
19 |
20 | // PluginInfo PluginInfo
21 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
22 | return &plugins.PluginInfo{
23 | ID: "todayFortune",
24 | Name: "今日运势",
25 | }
26 | }
27 |
28 | // IsFireEvent 是否触发
29 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
30 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
31 | v := msg.Elements[0]
32 | field, ok := v.(*message.TextElement)
33 | if !ok {
34 | return false
35 | }
36 | return strings.HasPrefix(field.Content, ".tf") || field.Content == "运势" || field.Content == "签到"
37 | }
38 | return false
39 | }
40 |
41 | // OnMessageEvent OnMessageEvent
42 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
43 | result, err := fortune.Randtext()
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | retItem := &plugins.MessageResponse{
49 | Elements: make([]message.IMessageElement, 1),
50 | }
51 |
52 | b, err := fortune.Draw(fortune.RandTheme(), result)
53 | if err != nil {
54 | return nil, err
55 | }
56 | dec := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(b))
57 | buf := &bytes.Buffer{}
58 | buf.ReadFrom(dec)
59 | retItem.Elements = append(retItem.Elements, message.NewText(request.GetNickName()+":"))
60 | var image message.IMessageElement
61 | if plugins.GroupMessage == request.MessageType {
62 | image, err = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(buf.Bytes()))
63 | } else {
64 | image, err = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(buf.Bytes()))
65 | }
66 | if err != nil {
67 | print(err.Error())
68 | return nil, err
69 | }
70 | retItem.Elements = append(retItem.Elements, image)
71 | return retItem, nil
72 | }
73 |
74 | func init() {
75 | plugins.RegisterOnMessagePlugin(Plugin{})
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/plugins/translate/tr_test.go:
--------------------------------------------------------------------------------
1 | package translate
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestTr(t *testing.T) {
8 | tr, err := callHttp("test", "en", "zh")
9 | print(tr)
10 | print(err)
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/plugins/translate/translate.go:
--------------------------------------------------------------------------------
1 | package translate
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io/ioutil"
10 | "math/rand"
11 | "net/http"
12 | "net/url"
13 | "os"
14 | "strconv"
15 | "strings"
16 |
17 | "github.com/Mrs4s/MiraiGo/message"
18 | "github.com/dezhiShen/MiraiGo-Bot/pkg/command"
19 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
20 | )
21 |
22 | type Plugin struct {
23 | plugins.NoSortPlugin
24 | plugins.NoInitPlugin
25 | plugins.AlwaysNotFireNextEventPlugin
26 | }
27 |
28 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
29 | return &plugins.PluginInfo{
30 | ID: "translate(baidu)",
31 | Name: "百度文本翻译插件",
32 | }
33 | }
34 |
35 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
36 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
37 | v := msg.Elements[0]
38 | field, ok := v.(*message.TextElement)
39 | return ok && strings.HasPrefix(field.Content, ".tr") || field.Content == ".tr--help"
40 | }
41 | return false
42 | }
43 |
44 | type TranslateReq struct {
45 | From string `short:"f" long:"from" description:"来源语言" default:"auto"`
46 | To string `short:"t" long:"to" description:"目标语言" default:"auto"`
47 | }
48 |
49 | var dictI18 = map[string]string{
50 | "zh": "中文",
51 | "en": "英语",
52 | "yue": "粤语",
53 | "wyw": "文言文",
54 | "jp": "日语",
55 | "kor": "韩语",
56 | "fra": "法语",
57 | "spa": "西班牙语",
58 | "th": "泰语",
59 | "ara": "阿拉伯语",
60 | "ru": "俄语",
61 | "pt": "葡萄牙语",
62 | "de": "德语",
63 | "it": "意大利语",
64 | "el": "希腊语",
65 | "nl": "荷兰语",
66 | "pl": "波兰语",
67 | "bul": "保加利亚语",
68 | "est": "爱沙尼亚语",
69 | "dan": "丹麦语",
70 | "fin": "芬兰语",
71 | "cs": "捷克语",
72 | "rom": "罗马尼亚语",
73 | "slo": "斯洛文尼亚语",
74 | "swe": "瑞典语",
75 | "hu": "匈牙利语",
76 | "cht": "繁体中文",
77 | "vie": "越南语",
78 | }
79 |
80 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
81 |
82 | var elements []message.IMessageElement
83 |
84 | v := request.Elements[0]
85 | field, _ := v.(*message.TextElement)
86 | context := field.Content
87 | trReq := TranslateReq{}
88 | commands, err := command.Parse(".tr", &trReq, strings.Split(context, " "))
89 | if err != nil {
90 | return nil, err
91 | }
92 | // 测试 根命令 .dict|.tr
93 | // rootCommand := commands[0]
94 | // 文本
95 | var q string
96 | for i := 1; i < len(commands); i++ {
97 | q = q + " " + commands[i]
98 | }
99 | q = strings.TrimSpace(q)
100 | from := trReq.From
101 | to := trReq.To
102 | transInfo, err := callHttp(q, from, to)
103 | if err != nil {
104 | return nil, err
105 | }
106 | re := transInfo.TransRe
107 | elements = append(elements, message.NewText(fmt.Sprintf("%v=>%v\n", dictI18[transInfo.FromLan], dictI18[transInfo.ToLan])))
108 | elements = append(elements, message.NewText(fmt.Sprintf("源文本\n%v\n", re[0].Source)))
109 | elements = append(elements, message.NewText(fmt.Sprintf("翻译文本\n%v\n", re[0].Destination)))
110 |
111 | result := &plugins.MessageResponse{
112 | Elements: elements,
113 | }
114 |
115 | return result, nil
116 | }
117 |
118 | func callHttp(q, from, to string) (*TransStruct, error) {
119 | salt := strconv.Itoa(rand.Intn(100000))
120 | uri := "http://api.fanyi.baidu.com/api/trans/vip/translate?"
121 | data := appid + q + salt + key
122 | signMd5 := md5.New()
123 | signMd5.Write([]byte(data))
124 | sign := hex.EncodeToString(signMd5.Sum(nil))
125 | uri += fmt.Sprintf("q=%v", url.QueryEscape(q))
126 | uri += fmt.Sprintf("&from=%v", from)
127 | uri += fmt.Sprintf("&to=%v", to)
128 | uri += fmt.Sprintf("&appid=%v", appid)
129 | uri += fmt.Sprintf("&salt=%v", salt)
130 | uri += fmt.Sprintf("&sign=%v", sign)
131 | fmt.Printf("%v\n", uri)
132 | resp, err := http.DefaultClient.Get(uri)
133 | if err != nil {
134 | return nil, err
135 | }
136 | robots, err := ioutil.ReadAll(resp.Body)
137 | resp.Body.Close()
138 | if err != nil {
139 | return nil, err
140 | }
141 | respBodyStr := string(robots)
142 | fmt.Printf("%v\n", respBodyStr)
143 | var transInfo TransStruct
144 | err = json.Unmarshal(robots, &transInfo)
145 | if err != nil {
146 | return nil, err
147 | }
148 | return &transInfo, nil
149 | }
150 |
151 | var key string
152 |
153 | var appid string
154 |
155 | func init() {
156 | plugins.RegisterOnMessagePlugin(Plugin{})
157 | var e error
158 | key, e = getKey()
159 | if e != nil {
160 | fmt.Printf("读取百度翻译的key发生错误:[%v]", e.Error())
161 | }
162 | appid, e = getID()
163 | if e != nil {
164 | fmt.Printf("读取百度翻译的ID发生错误:[%v]", e.Error())
165 | }
166 | }
167 |
168 | type TransResult struct {
169 | Source string `json:"src"`
170 | Destination string `json:"dst"`
171 | }
172 |
173 | type TransStruct struct {
174 | FromLan string `json:"from"`
175 | ToLan string `json:"to"`
176 | TransRe []TransResult `json:"trans_result"`
177 | }
178 |
179 | func getID() (string, error) {
180 | str := os.Getenv("BOT_BAIDU_FANYI_ID")
181 | if str == "" {
182 | return "", errors.New("未配置百度翻译的appID")
183 | }
184 | return str, nil
185 | }
186 |
187 | func getKey() (string, error) {
188 | str := os.Getenv("BOT_BAIDU_FANYI_KEY")
189 | if str == "" {
190 | return "", errors.New("未配置百度翻译的key")
191 | }
192 | return str, nil
193 | }
194 |
--------------------------------------------------------------------------------
/pkg/plugins/vader/vader.go:
--------------------------------------------------------------------------------
1 | package vader
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/Mrs4s/MiraiGo/message"
9 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
10 | "github.com/jonreiter/govader"
11 | )
12 |
13 | var analyzer = govader.NewSentimentIntensityAnalyzer()
14 |
15 | // Plugin vader
16 | type Plugin struct {
17 | plugins.NoSortPlugin
18 | plugins.NoInitPlugin
19 | plugins.AlwaysNotFireNextEventPlugin
20 | }
21 |
22 | // PluginInfo PluginInfo
23 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
24 | return &plugins.PluginInfo{
25 | ID: "vader",
26 | Name: "情感分析插件",
27 | }
28 | }
29 |
30 | // IsFireEvent 是否触发
31 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
32 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
33 | v := msg.Elements[0]
34 | field, ok := v.(*message.TextElement)
35 | return ok && strings.HasPrefix(field.Content, ".vader")
36 | }
37 | return false
38 | }
39 |
40 | // OnMessageEvent OnMessageEvent
41 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
42 | result := &plugins.MessageResponse{}
43 | v := request.Elements[0]
44 | field, _ := v.(*message.TextElement)
45 | context := field.Content
46 | params := strings.TrimSpace(strings.TrimPrefix(context, ".vader"))
47 | if params == "" {
48 | return nil, errors.New("请输入要分析的话")
49 | }
50 | var elements []message.IMessageElement
51 | text := strings.TrimSpace(params)
52 | sentiment := analyzer.PolarityScores(text)
53 | elements = append(elements, message.NewText(text))
54 | elements = append(elements, message.NewText(fmt.Sprintf("\n综合分数: %v", sentiment.Compound)))
55 | elements = append(elements, message.NewText(fmt.Sprintf("\n积极得分: %v", sentiment.Positive)))
56 | elements = append(elements, message.NewText(fmt.Sprintf("\n中立得分: %v", sentiment.Neutral)))
57 | elements = append(elements, message.NewText(fmt.Sprintf("\n消极得分: %v", sentiment.Negative)))
58 | result.Elements = elements
59 | return result, nil
60 | }
61 |
62 | func init() {
63 | plugins.RegisterOnMessagePlugin(Plugin{})
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/plugins/weather/weather.go:
--------------------------------------------------------------------------------
1 | package weather
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "strings"
9 |
10 | "github.com/Mrs4s/MiraiGo/message"
11 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
12 | )
13 |
14 | // Plugin 天气插件
15 | type Plugin struct {
16 | plugins.NoSortPlugin
17 | plugins.NoInitPlugin
18 | plugins.AlwaysNotFireNextEventPlugin
19 | }
20 |
21 | // PluginInfo PluginInfo
22 | func (w Plugin) PluginInfo() *plugins.PluginInfo {
23 | return &plugins.PluginInfo{
24 | ID: "weather",
25 | Name: "天气插件",
26 | }
27 | }
28 |
29 | // IsFireEvent 是否触发
30 | func (w Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
31 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
32 | v := msg.Elements[0]
33 | field, ok := v.(*message.TextElement)
34 | return ok && strings.HasPrefix(field.Content, ".weather ")
35 | }
36 | return false
37 | }
38 |
39 | // OnMessageEvent OnMessageEvent
40 | func (w Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
41 | result := &plugins.MessageResponse{
42 | Elements: make([]message.IMessageElement, 1),
43 | }
44 | v := request.Elements[0]
45 | field, _ := v.(*message.TextElement)
46 | context := field.Content
47 | localtion := strings.TrimSpace(strings.ReplaceAll(context, ".weather", ""))
48 | resp, err := getWeather(localtion)
49 | if err != nil {
50 | return nil, err
51 | }
52 | var imageErr error
53 | var image message.IMessageElement
54 | if request.MessageType == "group" {
55 | image, imageErr = request.QQClient.UploadGroupImage(request.GroupCode, bytes.NewReader(resp))
56 | } else {
57 | image, imageErr = request.QQClient.UploadPrivateImage(request.Sender.Uin, bytes.NewReader(resp))
58 | }
59 | if imageErr != nil {
60 | return nil, imageErr
61 | }
62 | result.Elements[0] = image
63 | return result, nil
64 | }
65 |
66 | func init() {
67 | plugins.RegisterOnMessagePlugin(Plugin{})
68 | }
69 |
70 | func getWeather(localtion string) ([]byte, error) {
71 | req, err := http.NewRequest("GET", fmt.Sprintf("https://wttr.in/~%v.png?1&background=968136&p&lang=zh", localtion), nil)
72 | if err != nil {
73 | return nil, err
74 | }
75 | r, err := http.DefaultClient.Do(req)
76 | if err != nil {
77 | return nil, err
78 | }
79 | defer r.Body.Close()
80 | robots, err := ioutil.ReadAll(r.Body)
81 | if err != nil {
82 | return nil, err
83 | }
84 | return robots, nil
85 | }
86 |
--------------------------------------------------------------------------------
/pkg/plugins/weather/weather_test.go:
--------------------------------------------------------------------------------
1 | package weather
2 |
3 | import (
4 | "io/ioutil"
5 | "testing"
6 | )
7 |
8 | func Test_getWeather(t *testing.T) {
9 | got, _ := getWeather("岳麓区")
10 | _ = ioutil.WriteFile("weather.png", got, 0644)
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/plugins/weibolisten/weibo.go:
--------------------------------------------------------------------------------
1 | package weibolisten
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "strings"
10 |
11 | "github.com/Logiase/MiraiGo-Template/bot"
12 | "github.com/Mrs4s/MiraiGo/message"
13 | "github.com/dezhiShen/MiraiGo-Bot/pkg/plugins"
14 | "github.com/dezhiShen/MiraiGo-Bot/pkg/storage"
15 | )
16 |
17 | // ListenUser 监听的用户
18 | type ListenUser struct {
19 | //用户UID
20 | UID string `json:"uid"`
21 | //用户ContainerID
22 | ContainerID string `json:"containerId"`
23 | //用户最后微博ID
24 | LastWeiboID string `json:"lastWeiboId"`
25 | }
26 | type messageType string
27 |
28 | const (
29 | //GroupMessage 群消息
30 | GroupMessage = messageType("group")
31 | //PrivateMessage 私聊消息
32 | PrivateMessage = messageType("private")
33 | )
34 |
35 | // ListenUserMessage 监听用户需要发送的群或者其他
36 | type ListenUserMessage struct {
37 | // 微博用户ID
38 | UID string `json:"id"`
39 | // 1 私聊/2 群聊
40 | ReciveType messageType `json:"type"`
41 | // QQ的 私聊 用户ID / 群聊用户ID
42 | Reciver int64 `json:"senderId"`
43 | }
44 |
45 | // Plugin 微博监听
46 | type Plugin struct {
47 | plugins.NoSortPlugin
48 | plugins.NoInitPlugin
49 | plugins.AlwaysNotFireNextEventPlugin
50 | }
51 |
52 | // PluginInfo PluginInfo
53 | func (p Plugin) PluginInfo() *plugins.PluginInfo {
54 | return &plugins.PluginInfo{
55 | ID: "weibo-listen",
56 | Name: "微博监听插件",
57 | Description: "涩图感应器(√)",
58 | }
59 | }
60 |
61 | // IsFireEvent 是否触发
62 | func (p Plugin) IsFireEvent(msg *plugins.MessageRequest) bool {
63 | if len(msg.Elements) == 1 && msg.Elements[0].Type() == message.Text {
64 | v := msg.Elements[0]
65 | field, ok := v.(*message.TextElement)
66 | return ok && strings.HasPrefix(field.Content, ".weibo-l ")
67 | }
68 | return false
69 | }
70 |
71 | // OnMessageEvent OnMessageEvent
72 | func (p Plugin) OnMessageEvent(request *plugins.MessageRequest) (*plugins.MessageResponse, error) {
73 | var elements []message.IMessageElement
74 |
75 | v := request.Elements[0]
76 | field, _ := v.(*message.TextElement)
77 | context := field.Content
78 | params := strings.Split(context, " ")
79 | command := params[1]
80 | switch command {
81 | case "add":
82 | if len(params) < 3 {
83 | return nil, errors.New("请输入要添加的微博账户的UID")
84 | }
85 | key := []byte(fmt.Sprintf("weibo-listen.user.%v", params[2]))
86 | var listenUser ListenUser
87 | exists := false
88 | err := storage.Get([]byte(p.PluginInfo().ID), key, func(s []byte) error {
89 | if s != nil {
90 | _ = json.Unmarshal([]byte(s), &listenUser)
91 | exists = true
92 | }
93 | return nil
94 | })
95 | if err != nil {
96 | return nil, err
97 | }
98 | if !exists {
99 | listenUser = ListenUser{
100 | UID: params[2],
101 | }
102 | err := setContainerID(&listenUser)
103 | if err != nil {
104 | return nil, err
105 | }
106 | jsonBytes, _ := json.Marshal(listenUser)
107 | storage.Put([]byte(p.PluginInfo().ID), key, jsonBytes)
108 | }
109 | listenUserMessage := ListenUserMessage{
110 | UID: params[2],
111 | ReciveType: messageType(request.MessageType),
112 | }
113 | if request.MessageType == plugins.GroupMessage {
114 | listenUserMessage.Reciver = request.GroupCode
115 | } else {
116 | listenUserMessage.Reciver = request.Sender.Uin
117 | }
118 | messageKey := []byte(
119 | fmt.Sprintf(
120 | "weibo-listen.user-sender.%v.%v.%v",
121 | listenUserMessage.UID,
122 | listenUserMessage.ReciveType,
123 | listenUserMessage.Reciver,
124 | ))
125 | notExists := false
126 | storage.Get([]byte(p.PluginInfo().ID), messageKey, func(s []byte) error {
127 | notExists = s == nil
128 | return nil
129 | })
130 | if notExists {
131 | jsonBytes, _ := json.Marshal(listenUserMessage)
132 | err = storage.Put([]byte(p.PluginInfo().ID), messageKey, jsonBytes)
133 | if err != nil {
134 | return nil, err
135 | }
136 | incrKey := []byte(fmt.Sprintf("weibo-listen.user-count.%v", params[2]))
137 | _, err = storage.Incr([]byte(p.PluginInfo().ID), incrKey, 1)
138 | }
139 | if err != nil {
140 | return nil, err
141 | }
142 | elements = append(elements, message.NewText("添加成功!"))
143 | case "remove":
144 | if len(params) < 3 {
145 | return nil, errors.New("请输入要移除的微博账户的UID")
146 | }
147 | listenUserMessage := ListenUserMessage{
148 | UID: params[2],
149 | ReciveType: messageType(request.MessageType),
150 | }
151 | if request.MessageType == plugins.GroupMessage {
152 | listenUserMessage.Reciver = request.GroupCode
153 | } else {
154 | listenUserMessage.Reciver = request.Sender.Uin
155 | }
156 | messageKey := []byte(fmt.Sprintf("weibo-listen.user-sender.%v.%v.%v", listenUserMessage.UID, listenUserMessage.ReciveType, listenUserMessage.Reciver))
157 | err := storage.Delete([]byte(p.PluginInfo().ID), messageKey)
158 | if err != nil {
159 | return nil, err
160 | }
161 | incrKey := []byte(fmt.Sprintf("weibo-listen.user-count.%v", params[2]))
162 | doIncr := false
163 | storage.Get([]byte(p.PluginInfo().ID), incrKey, func(s []byte) error {
164 | if s == nil {
165 | return nil
166 | }
167 | count := storage.BytesToInt(s)
168 | doIncr = count > 0
169 | return nil
170 | })
171 | if doIncr {
172 | _, err = storage.Incr([]byte(p.PluginInfo().ID), incrKey, -1)
173 | }
174 | if err != nil {
175 | return nil, err
176 | }
177 | elements = append(elements, message.NewText("移除成功!"))
178 | case "help":
179 | elements = append(elements, message.NewText("add uid 增加一个监听的微博用户\nremove uid 移除一个监听的微博用户"))
180 | default:
181 | elements = append(elements, message.NewText("只支持 add remove help命令"))
182 | }
183 | result := &plugins.MessageResponse{
184 | Elements: elements,
185 | }
186 | return result, nil
187 | }
188 |
189 | // Run 回调
190 | func (p Plugin) Run(bot *bot.Bot) error {
191 | prefix := []byte("weibo-listen.user.")
192 | var listenUsers []ListenUser
193 | storage.GetByPrefix([]byte(p.PluginInfo().ID), prefix, func(key, v []byte) error {
194 | var info ListenUser
195 | err := json.Unmarshal(v, &info)
196 | if err != nil {
197 | return err
198 | }
199 | countKey := []byte(fmt.Sprintf("weibo-listen.user-count.%v", info.UID))
200 | var count int
201 | err = storage.Get([]byte(p.PluginInfo().ID), countKey, func(s []byte) error {
202 | if s == nil {
203 | count = 0
204 | } else {
205 | count = storage.BytesToInt(s)
206 | return nil
207 | }
208 | return nil
209 | })
210 | if err != nil {
211 | return err
212 | }
213 | if count == 0 {
214 | return nil
215 | }
216 | listenUsers = append(listenUsers, info)
217 | return nil
218 | })
219 | for _, info := range listenUsers {
220 | key := []byte(fmt.Sprintf("weibo-listen.user.%v", info.UID))
221 | if info.ContainerID == "" {
222 | setContainerID(&info)
223 | jsonBytes, _ := json.Marshal(info)
224 | storage.Put([]byte(p.PluginInfo().ID), key, jsonBytes)
225 | }
226 | card, err := getLastItemsByUIDAndContainerID(info.UID, info.ContainerID)
227 | if err != nil {
228 | fmt.Printf("%v", err)
229 | return err
230 | }
231 | if card.Mblog.BID == info.LastWeiboID {
232 | return nil
233 | }
234 | info.LastWeiboID = card.Mblog.BID
235 | messageKey := []byte(fmt.Sprintf("weibo-listen.user-sender.%v.", info.UID))
236 | m := message.SendingMessage{}
237 | m.Elements = append(m.Elements, message.NewText(fmt.Sprintf("%v 发了微博 %v\n", card.Mblog.User.ScreenName, card.Mblog.RawText)))
238 | m.Elements = append(m.Elements, message.NewText(card.Scheme))
239 | err = storage.GetByPrefix([]byte(p.PluginInfo().ID), messageKey, func(k, senderValue []byte) error {
240 | var sender ListenUserMessage
241 | json.Unmarshal([]byte(senderValue), &sender)
242 | if &sender == nil {
243 | return nil
244 | }
245 | if sender.ReciveType == GroupMessage {
246 | bot.QQClient.SendGroupMessage(sender.Reciver, &m)
247 | } else if sender.ReciveType == PrivateMessage {
248 | bot.QQClient.SendPrivateMessage(sender.Reciver, &m)
249 | }
250 | return nil
251 | })
252 | if err != nil {
253 | return err
254 | }
255 | jsonBytes, _ := json.Marshal(info)
256 | storage.Put([]byte(p.PluginInfo().ID), key, jsonBytes)
257 | }
258 | // bot.QQClient.Send
259 | return nil
260 | }
261 |
262 | // Cron cron表达式
263 | func (p Plugin) Cron() string {
264 | return "0 0/15 * * * ?"
265 | }
266 |
267 | func init() {
268 | p := Plugin{}
269 | plugins.RegisterOnMessagePlugin(p)
270 | plugins.RegisterSchedulerPlugin(p)
271 | }
272 |
273 | func getContainerIDByUID(UID string) (string, error) {
274 | url := fmt.Sprintf("https://m.weibo.cn/api/container/getIndex?type=uid&value=%v", UID)
275 | resp, err := http.DefaultClient.Get(url)
276 | if err != nil {
277 | return "", err
278 | }
279 | robots, err := ioutil.ReadAll(resp.Body)
280 | resp.Body.Close()
281 | if err != nil {
282 | return "", err
283 | }
284 | respBodyStr := string(robots)
285 | if respBodyStr == "" {
286 | return "", err
287 | }
288 | var containerResp ContainerResp
289 | err = json.Unmarshal(robots, &containerResp)
290 | if err != nil {
291 | return "", err
292 | }
293 | for _, v := range containerResp.Data.TabsInfo.Tabs {
294 | if v.TabKey == "weibo" {
295 | return v.ContainerID, nil
296 |
297 | }
298 | }
299 | return "", nil
300 | }
301 |
302 | func setContainerID(info *ListenUser) error {
303 | if info.ContainerID == "" {
304 | id, err := getContainerIDByUID(info.UID)
305 | if err != nil {
306 | return err
307 | }
308 | if id == "" {
309 | return errors.New("UID错误,或者该微博不可见")
310 | }
311 | info.ContainerID = id
312 | }
313 | return nil
314 | }
315 |
316 | func getLastItemsByUIDAndContainerID(UID, ContainerID string) (*WeiboContentCard, error) {
317 | var contentResp WeiboContentResp
318 | url := fmt.Sprintf(
319 | "https://m.weibo.cn/api/container/getIndex?uid=%v&t=0&type=uid&containerid=%v",
320 | UID,
321 | ContainerID,
322 | )
323 | resp, err := http.DefaultClient.Get(url)
324 | if err != nil {
325 | return nil, err
326 | }
327 | robots, err := ioutil.ReadAll(resp.Body)
328 | resp.Body.Close()
329 | if err != nil {
330 | return nil, err
331 | }
332 | respBodyStr := string(robots)
333 | if respBodyStr == "" {
334 | return nil, err
335 | }
336 | err = json.Unmarshal(robots, &contentResp)
337 | if err != nil {
338 | return nil, err
339 | }
340 | if &contentResp == nil {
341 | return nil, nil
342 | }
343 | if contentResp.Ok != 1 {
344 | return nil, errors.New(fmt.Sprintf("微博请求失败%v", contentResp.Msg))
345 | }
346 | for _, c := range contentResp.Data.Cards {
347 | if c.Mblog.IsTop == 1 {
348 | continue
349 | }
350 | return &c, nil
351 | }
352 | return nil, nil
353 | }
354 |
355 | type WeiboContentResp struct {
356 | Ok int `json:"ok"`
357 | Msg string `json:"msg"`
358 | Data *WeiboContentData `json:"data"`
359 | }
360 |
361 | type WeiboContentData struct {
362 | Cards []WeiboContentCard `json:"cards"`
363 | }
364 |
365 | type WeiboContentCard struct {
366 | CardType int `json:"card_type"`
367 | ItemID string `json:"itemid"`
368 | Scheme string `json:"scheme"`
369 | Mblog *WeiboContentMblog `json:"mblog"`
370 | }
371 |
372 | type WeiboContentMblog struct {
373 | IsTop int `json:"isTop"`
374 | Text string `json:"text"`
375 | RawText string `json:"raw_text"`
376 | BID string `json:"bid"`
377 | Pics []WeiboContentPic `json:"pics"`
378 | User *WeiboUser `json:"user"`
379 | }
380 |
381 | type WeiboUser struct {
382 | ScreenName string `json:"screen_name"`
383 | }
384 | type WeiboContentPic struct {
385 | PID string `json:"pid"`
386 | Url string `json:"url"`
387 | }
388 |
389 | type ContainerResp struct {
390 | Data ContainerData `json:"data"`
391 | }
392 |
393 | type ContainerData struct {
394 | TabsInfo TabsInfo `json:"tabsInfo"`
395 | }
396 |
397 | type Tab struct {
398 | ID int `json:"id"`
399 | TabKey string `json:"tabKey"`
400 | TabType string `json:"tab_type"`
401 | ContainerID string `json:"containerid"`
402 | }
403 |
404 | type TabsInfo struct {
405 | Tabs []Tab `json:"tabs"`
406 | }
407 |
408 | type User struct {
409 | }
410 |
--------------------------------------------------------------------------------
/pkg/rss/rss.go:
--------------------------------------------------------------------------------
1 | package rss
2 |
3 | import (
4 | "github.com/SlyMarbo/rss"
5 | )
6 |
7 | func Feed(url string) (*rss.Feed, error) {
8 | return rss.Fetch(url)
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/rss/rss_test.go:
--------------------------------------------------------------------------------
1 | package rss
2 |
3 | import "testing"
4 |
5 | func TestFeed(t *testing.T) {
6 | url := "https://rssfeed.today/weibo/rss/7408951128"
7 | Feed(url)
8 | }
9 |
--------------------------------------------------------------------------------
/pkg/tools/image/image.go:
--------------------------------------------------------------------------------
1 | package image
2 |
3 | import (
4 | "image/color"
5 |
6 | "github.com/fogleman/gg"
7 | )
8 |
9 | func CreatImage(text string, path string) error {
10 | //图片的宽度
11 | var srcWidth float64 = 200
12 | //图片的高度
13 | var srcHeight float64 = 200
14 | dc := gg.NewContext(int(srcWidth), int(srcHeight))
15 | //设置背景色
16 | dc.SetColor(color.White)
17 | dc.Clear()
18 | dc.SetRGB255(255, 0, 0)
19 | if err := dc.LoadFontFace("C:\\github\\plugins\\assert\\fonts\\Symbola.ttf", 25); err != nil {
20 | return err
21 | }
22 | sWidth, _ := dc.MeasureString(text)
23 | dc.DrawString(text, (srcWidth-sWidth)/2, 40)
24 | err := dc.SavePNG(path)
25 | if err != nil {
26 | return err
27 | }
28 | return nil
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/tools/image/image_test.go:
--------------------------------------------------------------------------------
1 | package image
2 |
3 | import (
4 | "log"
5 | "testing"
6 | )
7 |
8 | func TestCreatImage(t *testing.T) {
9 | err := CreatImage("☆★", "out.png")
10 | if err != nil {
11 | log.Fatal(err)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/scripts/deploy_docker_test.sh:
--------------------------------------------------------------------------------
1 | git pull
2 | docker build . -t dezhishen/miraigo:test
3 | docker stop miraigo
4 | docker rm miraigo
5 | docker run --user $(id -u) -d -e BOT_BAIDU_FANYI_KEY=`echo $BOT_BAIDU_FANYI_KEY` -e BOT_BAIDU_FANYI_ID=`echo $BOT_BAIDU_FANYI_ID` -e BOT_PIXIV_TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJTZG5pdSIsInV1aWQiOiJjNTQ3OGY3MGUxY2E0NmVjODJiOTNiZGVlOWNiYjU2MSIsImlhdCI6MTYxNzA5Mjk0MSwiYWNjb3VudCI6IntcImVtYWlsXCI6XCIxMTc5NTUxOTYwQHFxLmNvbVwiLFwiZ2VuZGVyXCI6LTEsXCJoYXNQcm9uXCI6MCxcImlkXCI6ODE2LFwicGFzc1dvcmRcIjpcIjFkZmViY2NjNmJjNzI4ZTc1MGExZjQ1MjhlY2Q2NjcxXCIsXCJzdGF0dXNcIjowLFwidXNlck5hbWVcIjpcIlNkbml1XCJ9IiwianRpIjoiODE2In0.VgJxTsHwXLMRApBXEW0WOuicsoc3WLLT6BrcN4lmeDk" -e BOT_DJT_KEY=9bc86e70f1c8f0d9 -e BOT_CHP_KEY=ce3552aa350641f0 --restart=always --name=miraigo -e TZ=Asia/Shanghai -v /docker_data/miraigo/:/data dezhishen/miraigo:test
6 |
--------------------------------------------------------------------------------