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