├── .gitignore ├── LICENSE ├── README.md ├── pom.xml ├── readme └── img │ └── Context.png └── src └── main ├── kotlin └── com │ └── icecreamqaq │ └── yuq │ └── mirai │ ├── MiraiBot.kt │ ├── YuQMiraiModule.kt │ ├── YuQMiraiStart.kt │ ├── entity │ └── mirai.kt │ ├── fun.kt │ ├── logger │ └── log.kt │ ├── message │ ├── MiraiMessage.kt │ ├── MiraiMessageItemFactory.kt │ ├── UploadResource.kt │ └── items.kt │ └── util │ └── YuQInternalFunMiraiImpl.kt └── resources ├── conf └── module │ └── com.IceCreamQAQ.YuQ.Mirai.properties └── ehcache-YuQ-Mirai.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /src/test/** 2 | /tmp/** 3 | /log/** 4 | device.json1 -------------------------------------------------------------------------------- /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 | YuQ-Mirai 633 | Copyright (C) 2020 YuQ 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 | YuQ-Mirai 是一个以 [mirai](https://github.com/mamoe/mirai) 为基础,并实现了 [YuQ API](https://github.com/YuQWorks/YuQ) 的 QQ 机器人快速开发框架。 2 | 3 | - 路由映射 4 | - 依赖注入 5 | - 定时任务 6 | - ORM支持 7 | - 上下文消息 8 | 9 | YuQ-Mirai 使用 Kotlin 开发,并且完美支持 Java 与 Kotlin。 10 | 11 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.IceCreamQAQ.YuQ/YuQ-Mirai/badge.svg)](https://search.maven.org/#search|ga|1|com.IceCreamQAQ.YuQ.YuQ-Mirai) 12 | 13 | 14 | 在 YuQ 我们仅需很简单的代码,就可以完成很复杂的功能。 15 | 比如,我们要针对一个指令"菜单",进行一个标准的菜单消息回复。 16 | ```Java 17 | @GroupController 18 | public class GroupMenu{ 19 | @Action("菜单") 20 | public String menu(){ 21 | return "这是具体的菜单内容。"; 22 | } 23 | } 24 | ``` 25 | YuQ 会在指令式机器人的开发中,提供非常好的帮助,让开发者能有更好的开发体验。 26 | 在 Controller 中,我们的 Action 方法,返回的内容,会直接构建成消息,并发送当当前消息源。 27 | 通过路由映射,我们可以很方便的编写指令,只需要将 Class 声明为一个 Controller,并且编写 Action 方法。 28 | 其余的,YuQ 会帮您完成。 29 | 30 | 比如我们想禁言一个人,禁言的指令为"ban @xxx或QQ号码 time" 31 | 我们只需要编写: 32 | ```Java 33 | @GroupController 34 | public class GroupMenu{ 35 | @Action("ban {ban} {time}") 36 | public String ban(Member ban, int time){ 37 | ban.ban(time); 38 | return "好的!"; 39 | } 40 | } 41 | ``` 42 | 这样,我们就可以很轻易的完成 ban 这个指令了。 43 | 44 | 对于需要连续对话的指令式机器人,基于 YuQ 也可以轻松满足。 45 | Context 46 | 47 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.IceCreamQAQ.YuQ 8 | YuQ-Mirai 9 | 0.1.0.0-DEV25 10 | 11 | YuQ-Mirai 12 | YuQ-Mirai is a simple qq robot Framework. 13 | https://github.com/YuQWorks/YuQ-Mirai 14 | 15 | 16 | https://github.com/YuQWorks/YuQ-Mirai 17 | scm:git:https://github.com/YuQWorks/YuQ-Mirai.git 18 | scm:git:ssh://github.com:YuQWorks/YuQ-Mirai.git 19 | 20 | 21 | 22 | 23 | IceCream 24 | www@withdata.net 25 | 26 | 27 | 28 | 29 | 30 | IceCream-releases 31 | internal releases 32 | https://maven.IceCreamQAQ.com/repository/maven-releases/ 33 | 34 | 35 | IceCream-snapshots 36 | internal snapshot 37 | https://maven.IceCreamQAQ.com/repository/maven-snapshots/ 38 | false 39 | 40 | 41 | 42 | 43 | 1.7.10 44 | 45 | 2.14.0 46 | 0.1.0.0-DEV32 47 | 48 | 1.8 49 | 50 | 51 | 52 | 53 | 54 | org.jetbrains.kotlin 55 | kotlin-stdlib 56 | ${kotlin.version} 57 | 58 | 59 | 60 | com.IceCreamQAQ 61 | YuQ 62 | ${YuQ.version} 63 | 64 | 65 | 66 | net.mamoe 67 | mirai-core-jvm 68 | ${mirai.version} 69 | 70 | 71 | 72 | org.jetbrains.kotlin 73 | kotlin-reflect 74 | ${kotlin.version} 75 | 76 | 77 | 78 | 79 | 80 | ${project.basedir}/src/main/kotlin 81 | ${project.basedir}/src/test/kotlin 82 | 83 | 84 | org.jetbrains.kotlin 85 | kotlin-maven-plugin 86 | ${kotlin.version} 87 | 88 | 89 | 90 | compile 91 | 92 | compile 93 | 94 | 95 | 96 | 97 | test-compile 98 | 99 | test-compile 100 | 101 | 102 | 103 | 104 | 105 | -Xjvm-default=compatibility 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-source-plugin 112 | 2.4 113 | 114 | true 115 | 116 | 117 | 118 | compile 119 | 120 | jar 121 | 122 | 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-compiler-plugin 128 | 129 | 8 130 | 8 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | IceCream 144 | https://maven.icecreamqaq.com/repository/maven-public/ 145 | 146 | 147 | -------------------------------------------------------------------------------- /readme/img/Context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuQWorks/YuQ-Mirai/a37f5ed5be47530c328168770cbf872aeed830e2/readme/img/Context.png -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/MiraiBot.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai 2 | 3 | import com.IceCreamQAQ.Yu.`as`.ApplicationService 4 | import com.IceCreamQAQ.Yu.annotation.Config 5 | import com.IceCreamQAQ.Yu.annotation.Default 6 | import com.IceCreamQAQ.Yu.cache.EhcacheHelp 7 | import com.IceCreamQAQ.Yu.controller.Router 8 | import com.IceCreamQAQ.Yu.di.YuContext 9 | import com.IceCreamQAQ.Yu.event.EventBus 10 | import com.IceCreamQAQ.Yu.toJSONObject 11 | import com.IceCreamQAQ.Yu.toJSONString 12 | import com.IceCreamQAQ.Yu.util.Web 13 | import com.alibaba.fastjson.JSON 14 | import com.icecreamqaq.yuq.* 15 | import com.icecreamqaq.yuq.controller.ContextRouter 16 | import com.icecreamqaq.yuq.controller.ContextSession 17 | import com.icecreamqaq.yuq.entity.* 18 | import com.icecreamqaq.yuq.event.* 19 | import com.icecreamqaq.yuq.message.Message 20 | import com.icecreamqaq.yuq.message.MessageItem 21 | import com.icecreamqaq.yuq.mirai.entity.FriendImpl 22 | import com.icecreamqaq.yuq.mirai.entity.GroupImpl 23 | import com.icecreamqaq.yuq.mirai.entity.GroupMemberImpl 24 | import com.icecreamqaq.yuq.mirai.logger.Network 25 | import com.icecreamqaq.yuq.mirai.message.* 26 | import kotlinx.coroutines.* 27 | import net.mamoe.mirai.Bot 28 | import net.mamoe.mirai.BotFactory 29 | import net.mamoe.mirai.alsoLogin 30 | import net.mamoe.mirai.contact.Friend as MiraiFriend 31 | import net.mamoe.mirai.contact.Group as MiraiGroup 32 | import net.mamoe.mirai.contact.NormalMember as MiraiMember 33 | import net.mamoe.mirai.event.events.* 34 | import net.mamoe.mirai.event.events.GroupMemberEvent 35 | import net.mamoe.mirai.event.events.MessageRecallEvent 36 | import net.mamoe.mirai.event.events.FriendMessageEvent 37 | import net.mamoe.mirai.message.data.* 38 | import net.mamoe.mirai.message.data.Image.Key.queryUrl 39 | import net.mamoe.mirai.utils.BotConfiguration 40 | import net.mamoe.mirai.utils.LoginSolver 41 | import net.mamoe.mirai.utils.StandardCharImageLoginSolver 42 | import net.mamoe.mirai.utils.info 43 | import okhttp3.FormBody 44 | import okhttp3.OkHttpClient 45 | import okhttp3.Request 46 | import org.slf4j.LoggerFactory 47 | import java.io.File 48 | import java.util.* 49 | import javax.inject.Inject 50 | import javax.inject.Named 51 | import kotlin.collections.ArrayList 52 | import kotlin.collections.HashMap 53 | import kotlin.collections.set 54 | import net.mamoe.mirai.event.events.BotJoinGroupEvent as MiraiBotJoinGroupEvent 55 | import net.mamoe.mirai.event.events.FriendAddEvent as MiraiFriendAddEvent 56 | import net.mamoe.mirai.event.events.FriendDeleteEvent as MiraiFriendDeleteEvent 57 | import net.mamoe.mirai.event.events.NewFriendRequestEvent as MiraiNewFriendRequestEvent 58 | import net.mamoe.mirai.event.events.GroupMessageEvent as MiraiGroupMessageEvent 59 | import net.mamoe.mirai.message.data.MessageSource as MiraiSource 60 | 61 | open class MiraiBot : YuQ, ApplicationService, User, YuQVersion { 62 | 63 | private val log = LoggerFactory.getLogger(MiraiBot::class.java) 64 | 65 | @Config("YuQ.Mirai.user.qq") 66 | lateinit var qq: String 67 | 68 | @Config("YuQ.Mirai.user.pwd") 69 | lateinit var pwd: String 70 | 71 | @Config("YuQ.bot.name") 72 | private var botName: String? = null 73 | 74 | @Config("YuQ.Mirai.protocol") 75 | @Default("HD") 76 | lateinit var protocol: String 77 | 78 | @Inject 79 | @field:Named("group") 80 | lateinit var group: Router 81 | 82 | @Inject 83 | @field:Named("priv") 84 | lateinit var priv: Router 85 | 86 | @Inject 87 | lateinit var contextRouter: ContextRouter 88 | 89 | @Inject 90 | override lateinit var web: Web 91 | override fun id2platformId(id: Long) = id.toString() 92 | 93 | override fun platformId2id(platformId: String) = platformId.toLong() 94 | 95 | @Inject 96 | lateinit var eventBus: EventBus 97 | 98 | @Inject 99 | override lateinit var messageItemFactory: MiraiMessageItemFactory 100 | // override val web: Web 101 | 102 | @Inject 103 | lateinit var rainBot: YuQInternalBotImpl 104 | 105 | @Inject 106 | @field:Named("ContextSession") 107 | lateinit var sessionCache: EhcacheHelp 108 | 109 | @Inject 110 | lateinit var context: YuContext 111 | 112 | lateinit var bot: Bot 113 | override var botId: Long = 0 114 | override val botInfo: User = this 115 | override val cookieEx = Cookie("", 0, HashMap()) 116 | 117 | data class Cookie( 118 | override var skey: String, 119 | override var gtk: Long = 0, 120 | override var pskeyMap: Map 121 | ) : YuQ.QQCookie 122 | 123 | override lateinit var friends: UserListImpl 124 | override lateinit var groups: UserListImpl 125 | override val guilds: GuildList 126 | get() = TODO("Not yet implemented") 127 | 128 | lateinit var sKey: String 129 | lateinit var superKey: String 130 | var gtk: Long = 0 131 | 132 | val pskeyMap = HashMap() 133 | 134 | 135 | // var DefaultLogger: (identity: String?) -> MiraiLogger = { YuQMiraiLogger } 136 | 137 | override fun init() { 138 | // FPMM.getTime = { System.currentTimeMillis() } 139 | // FPMM.clear() 140 | 141 | mif = messageItemFactory 142 | // mf = messageFactory 143 | yuq = this 144 | botId = qq.toLong() 145 | com.icecreamqaq.yuq.web = web 146 | localEventBus = eventBus 147 | com.icecreamqaq.yuq.eventBus = eventBus 148 | miraiBot = this 149 | 150 | bot = makeBot(botId, pwd) 151 | runBlocking { 152 | bot.alsoLogin() 153 | } 154 | context.putBean(Bot::class.java, "", bot) 155 | 156 | registerCookie() 157 | 158 | refreshFriends() 159 | refreshGroups() 160 | } 161 | 162 | fun makeBot(botId: Long, pwd: String): Bot = BotFactory.newBot(botId, pwd) { 163 | if (File("device_$qq.json").exists()) fileBasedDeviceInfo("device_$qq.json") 164 | else if (File("device.json").exists()) fileBasedDeviceInfo() 165 | else fileBasedDeviceInfo("device_$qq.json") 166 | loginSolver = StandardCharImageLoginSolver() 167 | networkLoggerSupplier = { Network("Net ${it.id}") } 168 | botLoggerSupplier = { com.icecreamqaq.yuq.mirai.logger.Bot(("Bot ${it.id}")) } 169 | if (this@MiraiBot.protocol == "Android") protocol = BotConfiguration.MiraiProtocol.ANDROID_PHONE 170 | if (this@MiraiBot.protocol == "Watch") protocol = BotConfiguration.MiraiProtocol.ANDROID_WATCH 171 | if (this@MiraiBot.protocol == "HD") protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD 172 | } 173 | 174 | open fun registerCookie() { 175 | val f = fun(sKey: String): Long { 176 | var hash = 5381L 177 | for (element in sKey) { 178 | hash += (hash shl 5 and 2147483647) + element.toInt() and 2147483647 179 | hash = hash and 2147483647 180 | } 181 | return hash and 2147483647 182 | } 183 | 184 | for (method in bot::class.java.declaredMethods) { 185 | if (method.name == "getClient") { 186 | 187 | method.isAccessible = true 188 | val client = method.invoke(bot) 189 | for (cm in client::class.java.declaredMethods) { 190 | if (cm.name == "getWLoginSigInfo") { 191 | cm.isAccessible = true 192 | val lsi = cm.invoke(client) 193 | val lsiJS = lsi.toJSONString() 194 | val lsiJO = JSON.parseObject(lsiJS) 195 | val sKey = String(Base64.getDecoder().decode(lsiJO.getJSONObject("sKey").getString("data"))) 196 | 197 | this.sKey = sKey 198 | this.cookieEx.skey = sKey 199 | this.gtk = f(sKey) 200 | this.cookieEx.gtk = this.gtk 201 | this.superKey = String(Base64.getDecoder().decode(lsiJO.getString("superKey"))) 202 | 203 | val psKeys = lsiJO.getJSONObject("psKeyMap") 204 | 205 | for (k in psKeys.keys) { 206 | val value = String( 207 | Base64.getDecoder().decode(psKeys.getJSONObject(k).getString("data")) 208 | ?: continue 209 | ) 210 | val pskey = YuQ.QQCookie.Pskey(value, f(value)) 211 | pskeyMap[k] = pskey 212 | web.saveCookie(k, "/", "p_skey", value) 213 | web.saveCookie(k, "/", "p_uin", "o$qq") 214 | } 215 | 216 | this.cookieEx.pskeyMap = pskeyMap 217 | 218 | web.saveCookie("qq.com", "/", "uin", "o$qq") 219 | web.saveCookie("qq.com", "/", "skey", sKey) 220 | } 221 | } 222 | } 223 | } 224 | } 225 | 226 | override fun refreshFriends(): FriendList { 227 | val friends = UserListImpl() 228 | for (friend in bot.friends) { 229 | friends[friend.id] = FriendImpl(friend) 230 | } 231 | this.friends = friends 232 | return friends 233 | } 234 | 235 | private fun getOrNew(f: MiraiFriend) = friends[f.id] ?: { 236 | val a = FriendImpl(f) 237 | friends[a.id] = a 238 | a 239 | }() 240 | 241 | private fun getOrNew(g: MiraiGroup) = groups[g.id] ?: { 242 | val a = GroupImpl(g) 243 | groups[a.id] = a 244 | a 245 | }() 246 | 247 | private fun getOrNew(m: MiraiMember) = getOrNew(m.group).run { 248 | getOrNull(m.id) ?: { 249 | val member = GroupMemberImpl(m, this) 250 | members[member.id] = member 251 | if (member.permission == 1) admins.add(member) 252 | member 253 | }() 254 | } 255 | 256 | override fun refreshGroups(): GroupList { 257 | val groups = UserListImpl() 258 | for (group in bot.groups) { 259 | try { 260 | groups[group.id] = GroupImpl(group) 261 | } catch (e: Exception) { 262 | log.error("Load Group ${group.id} Error!", e) 263 | } 264 | 265 | } 266 | this.groups = groups 267 | return groups 268 | } 269 | 270 | override fun refreshGuilds(): GuildList { 271 | TODO("Not yet implemented") 272 | } 273 | 274 | override fun start() { 275 | context.injectBean(rainBot) 276 | startBot() 277 | } 278 | 279 | override fun stop() { 280 | bot.close() 281 | } 282 | 283 | suspend fun MessageChain.toMessage(): Message? { 284 | val message = Message() 285 | 286 | val miraiSource = this[MiraiSource] ?: return null 287 | message.id = miraiSource.ids[0] 288 | val source = MiraiMessageSource(miraiSource) 289 | message.source = source 290 | message.sourceMessage = this 291 | 292 | val pathBody = ArrayList() 293 | val messageBody = message.body 294 | 295 | var itemNum = 0 296 | loop@ for (m in this) { 297 | when (m) { 298 | is MiraiSource -> continue@loop 299 | is QuoteReply -> message.reply = MiraiMessageSource(m.source) 300 | is PlainText -> { 301 | messageBody.append(TextImpl(m.content)) 302 | val sm = m.content.trim() 303 | if (sm.isEmpty()) continue@loop 304 | val sms = sm.replace("\n", " ").split(" ") 305 | var loopStart = 0 306 | if (itemNum == 0 && botName != null && sms[0] == botName) loopStart = 1 307 | for (i in loopStart until sms.size) { 308 | pathBody.add(TextImpl(sms[i])) 309 | itemNum++ 310 | } 311 | } 312 | is At -> { 313 | val item = AtImpl(m.target) 314 | messageBody.append(item) 315 | if (itemNum == 0 && m.target == botId) continue@loop 316 | pathBody.add(item) 317 | itemNum++ 318 | } 319 | is AtAll -> { 320 | val item = (AtImpl(-1L)) 321 | messageBody.append(item) 322 | pathBody.add(item) 323 | itemNum++ 324 | } 325 | is Face -> { 326 | val item = (FaceImpl(m.id)) 327 | messageBody.append(item) 328 | pathBody.add(item) 329 | itemNum++ 330 | } 331 | is Image -> { 332 | val item = (ImageReceive(m.imageId, m.queryUrl())) 333 | messageBody.append(item) 334 | pathBody.add(item) 335 | itemNum++ 336 | } 337 | is FlashImage -> { 338 | val item = (FlashImageImpl(ImageReceive(m.image.imageId, m.image.queryUrl()))) 339 | messageBody.append(item) 340 | pathBody.add(item) 341 | itemNum++ 342 | } 343 | is OnlineAudio -> { 344 | val item = VoiceRecv(m) 345 | messageBody.append(item) 346 | pathBody.add(item) 347 | itemNum++ 348 | } 349 | is LightApp -> { 350 | val item = JsonImpl(m.content) 351 | messageBody.append(item) 352 | pathBody.add(item) 353 | itemNum++ 354 | } 355 | is ServiceMessage -> { 356 | val item = XmlImpl(m.serviceId, m.content) 357 | messageBody.append(item) 358 | pathBody.add(item) 359 | itemNum++ 360 | } 361 | else -> { 362 | val item = NoImplItemImpl(m) 363 | messageBody.append(item) 364 | pathBody.add(item) 365 | itemNum++ 366 | } 367 | } 368 | } 369 | 370 | message.path = pathBody 371 | 372 | return message 373 | } 374 | 375 | fun startBot() { 376 | 377 | // BotEvent 378 | val eventChannel = bot.eventChannel 379 | 380 | eventChannel.subscribeAlways { 381 | registerCookie() 382 | } 383 | 384 | // 好友消息事件 385 | eventChannel.subscribeAlways { 386 | val message = message.toMessage() ?: return@subscribeAlways 387 | // message.temp = false 388 | // message.qq = this.sender.id 389 | 390 | val friend = getOrNew(this.sender) 391 | rainBot.receiveFriendMessage(friend, message) 392 | } 393 | 394 | // 群消息事件 395 | eventChannel.subscribeAlways { 396 | val message = message.toMessage() ?: return@subscribeAlways 397 | // message.temp = false 398 | // message.group = this.subject.id 399 | // message.qq = this.sender.id 400 | 401 | val group = getOrNew(this.sender.group) 402 | // val member = when (this.sender.id) { 403 | //// 80000000L -> AnonymousMemberImpl(this.sender, group) 404 | // else -> getOrNew(this.sender) 405 | // } 406 | val member = when (this.sender) { 407 | is MiraiMember -> getOrNew(this.sender as MiraiMember) 408 | else -> return@subscribeAlways 409 | } 410 | 411 | rainBot.receiveGroupMessage(member, message) 412 | } 413 | 414 | // 临时会话事件 415 | eventChannel.subscribeAlways { 416 | val message = message.toMessage() ?: return@subscribeAlways 417 | // message.temp = true 418 | // message.group = this.group.id 419 | // message.qq = this.sender.id 420 | 421 | val member = getOrNew(this.sender as MiraiMember) 422 | rainBot.receiveTempMessage(member, message) 423 | } 424 | 425 | // 新好友申请事件 426 | eventChannel.subscribeAlways { 427 | val ui = UserInfo( 428 | id = this.fromId, 429 | avatar = "", 430 | name = this.fromNick, 431 | sex = UserSex.none, 432 | age = 0, 433 | qqAge = 0, 434 | level = 0, 435 | loginDays = 0, 436 | vips = listOf() 437 | ) 438 | val g = this.fromGroup?.let { getOrNew(it) } 439 | val e = NewFriendRequestEvent(ui, g, this.message) 440 | if (eventBus.post(e)) { 441 | when (e.accept) { 442 | true -> { 443 | it.accept() 444 | val mf = this.bot.friends[this.fromId]!! 445 | val f = FriendImpl(mf) 446 | this@MiraiBot.friends[f.id] = f 447 | } 448 | else -> it.reject() 449 | } 450 | } 451 | } 452 | // 机器人被邀请入群事件 453 | eventChannel.subscribeAlways { 454 | val ui = UserInfo( 455 | id = this.invitorId, 456 | avatar = "", 457 | name = this.invitorNick, 458 | sex = UserSex.none, 459 | age = 0, 460 | qqAge = 0, 461 | level = 0, 462 | loginDays = 0, 463 | vips = listOf() 464 | ) 465 | val gi = GroupInfo( 466 | id = this.groupId, 467 | name = this.groupName, 468 | maxCount = 0, 469 | owner = ui, 470 | admin = listOf() 471 | ) 472 | val e = GroupInviteEvent(gi, ui, "") 473 | if (eventBus.post(e)) { 474 | when (e.accept) { 475 | true -> { 476 | it.accept() 477 | // val mg = bot.groups[this.groupId] 478 | // val g = GroupImpl(mg) 479 | // this@MiraiBot.groups[g.id] = g 480 | } 481 | 482 | else -> {} 483 | } 484 | } 485 | } 486 | // 有新成员申请入群事件 487 | eventChannel.subscribeAlways { 488 | val ui = UserInfo( 489 | id = this.fromId, 490 | avatar = "", 491 | name = this.fromNick, 492 | sex = UserSex.none, 493 | age = 0, 494 | qqAge = 0, 495 | level = 0, 496 | loginDays = 0, 497 | vips = listOf() 498 | ) 499 | val e = GroupMemberRequestEvent(getOrNew(this.group!!), ui, this.message) 500 | if (eventBus.post(e) && e.accept != null) 501 | if (e.accept!!) { 502 | it.accept() 503 | val m = this.group!![this.fromId]!! 504 | val group = getOrNew(this.group!!) 505 | val member = GroupMemberImpl(m, group) 506 | group.members[member.id] = member 507 | eventBus.post(GroupMemberJoinEvent(group, member)) 508 | } else it.reject(e.blackList) 509 | } 510 | 511 | // 好友部分变动监听 512 | eventChannel.subscribeAlways { 513 | val friend = FriendImpl(friend) 514 | this@MiraiBot.friends[friend.id] = friend 515 | eventBus.post(FriendAddEvent(friend)) 516 | } 517 | eventChannel.subscribeAlways { 518 | val friend = this@MiraiBot.friends[friend.id] ?: return@subscribeAlways 519 | this@MiraiBot.friends.remove(friend.id) 520 | eventBus.post(FriendDeleteEvent(friend)) 521 | } 522 | eventChannel.subscribeAlways { 523 | // this@MiraiBot.friends[friend.id]?.name = friend.nick 524 | } 525 | 526 | // 群部分变动监听 527 | eventChannel.subscribeAlways { 528 | val group = GroupImpl(group) 529 | this@MiraiBot.groups[group.id] = group 530 | eventBus.post(BotJoinGroupEvent(group)) 531 | } 532 | eventChannel.subscribeAlways { 533 | val group = this@MiraiBot.groups[group.id] ?: return@subscribeAlways 534 | this@MiraiBot.groups.remove(group.id) 535 | eventBus.post( 536 | if (this is BotLeaveEvent.Kick) BotLeaveGroupEvent.Kick(group[operator.id]) 537 | else BotLeaveGroupEvent.Leave(group) 538 | ) 539 | } 540 | eventChannel.subscribeAlways { 541 | // this@MiraiBot.groups[group.id]?.name = group.name 542 | } 543 | eventChannel.subscribeAlways { 544 | // this@MiraiBot.groups[group.id]!!.members[member.id]!!.permission = new.level 545 | // when (new.level){ 546 | // 547 | // } 548 | groups[group.id]?.refreshAdmin() 549 | } 550 | 551 | // 群成员部分变动监听 552 | eventChannel.subscribeAlways { 553 | val group = getOrNew(member.group) 554 | val member = GroupMemberImpl(member, group) 555 | group.members[member.id] = member 556 | eventBus.post( 557 | if (this is MemberJoinEvent.Invite) GroupMemberInviteEvent( 558 | group, 559 | member, 560 | member 561 | ) else GroupMemberJoinEvent.Join(group, member) 562 | ) 563 | } 564 | eventChannel.subscribeAlways { 565 | val group = getOrNew(member.group) 566 | val member = group[member.id] 567 | group.members.remove(member.id) 568 | eventBus.post( 569 | if (this is MemberLeaveEvent.Kick) GroupMemberKickEvent( 570 | group, member, group.members[operator?.id] 571 | ?: group.bot 572 | ) 573 | else GroupMemberLeaveEvent.Leave(group, member) 574 | ) 575 | } 576 | eventChannel.subscribeAlways { 577 | // this@MiraiBot.groups[member.group.id]?.members?.get(member.id)?.nameCard = member.nameCard 578 | } 579 | eventChannel.subscribeAlways { 580 | // this@MiraiBot.groups[member.group.id]?.members?.get(member.id)?.title = member.specialTitle 581 | } 582 | 583 | fun GroupMemberEvent.getMember() = getOrNew(member as MiraiMember) 584 | 585 | eventChannel.subscribeAlways { 586 | val member = this.getMember() 587 | val op = this@MiraiBot.groups[this.group.id]?.members?.get(this.operator?.id ?: -1) 588 | ?: this@MiraiBot.groups[this.group.id]?.bot ?: return@subscribeAlways 589 | eventBus.post(GroupBanMemberEvent(member.group, member, op, this.durationSeconds)) 590 | } 591 | eventChannel.subscribeAlways { 592 | val member = this.getMember() 593 | val op = this@MiraiBot.groups[this.group.id]?.members?.get(this.operator?.id ?: -1) 594 | ?: this@MiraiBot.groups[this.group.id]?.bot ?: return@subscribeAlways 595 | eventBus.post(GroupUnBanMemberEvent(member.group, member, op)) 596 | } 597 | eventChannel.subscribeAlways { 598 | val member = this@MiraiBot.groups[this.group.id]?.bot ?: return@subscribeAlways 599 | val op = this@MiraiBot.groups[this.group.id]?.get(this.operator.id) ?: return@subscribeAlways 600 | eventBus.post(GroupBanBotEvent(member.group, member, op, this.durationSeconds)) 601 | } 602 | eventChannel.subscribeAlways { 603 | val member = this@MiraiBot.groups[this.group.id]?.bot ?: return@subscribeAlways 604 | val op = this@MiraiBot.groups[this.group.id]?.get(this.operator.id) ?: return@subscribeAlways 605 | eventBus.post(GroupUnBanBotEvent(member.group, member, op)) 606 | } 607 | 608 | // NudgeEvent 609 | 610 | 611 | eventChannel.subscribeAlways { 612 | when (subject) { 613 | is MiraiGroup -> { 614 | if (from.id == botId) return@subscribeAlways 615 | val group = groups[subject.id] ?: return@subscribeAlways 616 | if (target.id == botId) ClickBotEvent.Group(group[from.id], action, suffix) 617 | else ClickSomeBodyEvent.Group(group[from.id], group[target.id], action, suffix) 618 | } 619 | is MiraiFriend -> { 620 | if (from.id == botId) return@subscribeAlways 621 | if (target.id == botId) ClickBotEvent.Private.FriendClick(friends[from.id]!!, action, suffix) 622 | else ClickSomeBodyEvent.Private(friends[from.id]!!, friends[target.id]!!, action, suffix) 623 | } 624 | else -> error("暂不支持当前类型的戳一戳事件响应。(type: $subject)") 625 | }() 626 | } 627 | // eventChannel.subscribeAlways { 628 | // if (from.id == botId) return@subscribeAlways 629 | // if (from is MiraiMember) { 630 | // val group = groups[(from as MiraiMember).group.id] ?: return@subscribeAlways 631 | // ClickBotEvent.Group(group[from.id], action, suffix) 632 | // } else { 633 | // ClickBotEvent.Private.FriendClick(friends[from.id] ?: return@subscribeAlways, action, suffix) 634 | // }() 635 | // } 636 | 637 | 638 | eventChannel.subscribeAlways { 639 | eventBus.post( 640 | when (this) { 641 | is MessageRecallEvent.GroupRecall -> { 642 | val g = getOrNew(group) 643 | GroupRecallEvent( 644 | g, g.members[this.authorId] ?: g.bot, g.members[this.operator?.id] 645 | ?: g.bot, this.messageIds[0] 646 | ) 647 | } 648 | is MessageRecallEvent.FriendRecall -> PrivateRecallEvent( 649 | friends[this.authorId] ?: return@subscribeAlways, 650 | friends[this.operator.id] ?: return@subscribeAlways, 651 | this.messageIds[0] 652 | ) 653 | } 654 | ) 655 | } 656 | } 657 | 658 | // override fun sendMessage(message: Message) = 659 | // when { 660 | // message.temp -> { 661 | // groups[message.group!!]!![message.qq!!] 662 | // } 663 | // message.group != null -> { 664 | // groups[message.group!!]!! 665 | // } 666 | // else -> { 667 | // friends[message.qq!!]!! 668 | // } 669 | // }.sendMessage(message) 670 | // 671 | // 672 | // override fun recallMessage(messageSource: MessageSource): Int { 673 | // return messageSource.recall() 674 | // } 675 | 676 | override val avatar: String 677 | get() = bot.avatarUrl 678 | override val id: Long 679 | get() = botId 680 | override val name: String 681 | get() = bot.nick 682 | override val platformId: String 683 | get() = botId.toString() 684 | 685 | override fun canSendMessage() = false 686 | 687 | override fun isFriend() = false 688 | override fun runtimeName() = "YuQ-Mirai" 689 | 690 | override fun runtimeVersion() = "0.1.0.0-DEV24" 691 | 692 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/YuQMiraiModule.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai 2 | 3 | import com.IceCreamQAQ.Yu.di.YuContext 4 | import com.IceCreamQAQ.Yu.hook.HookItem 5 | import com.IceCreamQAQ.Yu.hook.YuHook 6 | import com.IceCreamQAQ.Yu.loader.AppClassloader 7 | import com.IceCreamQAQ.Yu.module.Module 8 | import com.icecreamqaq.yuq.mirai.util.YuQInternalFunMiraiImpl 9 | import com.icecreamqaq.yuq.util.YuQInternalFun 10 | import javax.inject.Inject 11 | import javax.inject.Named 12 | 13 | class YuQMiraiModule : Module { 14 | 15 | @Inject 16 | private lateinit var context: YuContext 17 | 18 | override fun onLoad() { 19 | YuHook.put( 20 | HookItem( 21 | "net.mamoe.mirai.Mirai", 22 | "findMiraiInstance", 23 | "com.icecreamqaq.yuq.mirai.HookMiraiService" 24 | ) 25 | ) 26 | YuHook.put( 27 | HookItem( 28 | "net.mamoe.mirai.utils.StandardCharImageLoginSolver", 29 | "onSolveSliderCaptcha", 30 | "com.icecreamqaq.yuq.mirai.HookSliderCaptcha" 31 | ) 32 | ) 33 | context.putBean(YuQInternalFun::class.java, "", YuQInternalFunMiraiImpl()) 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/YuQMiraiStart.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai 2 | 3 | import com.IceCreamQAQ.Yu.DefaultStarter 4 | import com.IceCreamQAQ.Yu.hook.* 5 | import com.IceCreamQAQ.Yu.loader.AppClassloader 6 | import com.IceCreamQAQ.Yu.toJSONObject 7 | import com.icecreamqaq.yuq.YuQStarter 8 | import javafx.application.Application.launch 9 | import kotlinx.coroutines.delay 10 | import kotlinx.coroutines.runBlocking 11 | import net.mamoe.mirai.IMirai 12 | import okhttp3.FormBody 13 | import okhttp3.OkHttpClient 14 | import okhttp3.Request 15 | import org.slf4j.LoggerFactory 16 | import java.lang.reflect.Method 17 | import java.util.* 18 | import kotlin.reflect.full.companionObjectInstance 19 | 20 | //@Deprecated( 21 | // message = "建议使用 YuQ 提供的启动类,YuQStarter", 22 | // level = DeprecationLevel.WARNING, 23 | // replaceWith = ReplaceWith("YuQStarter", "com.icecreamqaq.yuq.YuQStarter") 24 | //) 25 | 26 | lateinit var classloader: AppClassloader 27 | 28 | class YuQMiraiStart { 29 | 30 | 31 | companion object { 32 | private val log = LoggerFactory.getLogger(YuQMiraiStart::class.java) 33 | 34 | @JvmStatic 35 | fun start() { 36 | // AppClassloader.registerBackList(arrayListOf("net.mamoe.", "javafx.")) 37 | 38 | 39 | val startTime = System.currentTimeMillis() 40 | val classloader = AppClassloader(YuQStarter::class.java.classLoader) 41 | Thread.currentThread().contextClassLoader = classloader 42 | 43 | val yuClass = classloader.loadClass("com.IceCreamQAQ.Yu.DefaultApp") 44 | val start: Method? = yuClass.getMethod("start") 45 | 46 | val yu = yuClass.newInstance() 47 | start!!.invoke(yu) 48 | 49 | val overTime = System.currentTimeMillis() 50 | 51 | log.info("Done! ${(overTime - startTime).toDouble() / 1000}s.") 52 | 53 | println( 54 | " __ __ ____ \n" + 55 | " \\ \\/ /_ __/ __ \\\n" + 56 | " \\ / // / /_/ /\n" + 57 | " /_/\\_,_/\\___\\_\\\n" 58 | ) 59 | println("感谢您使用 YuQ 进行开发,在您使用中如果遇到任何问题,可以到 Github,Gitee 提出 issue,您也可以添加 YuQ 的开发交流群(787049553)进行交流。") 60 | } 61 | 62 | @JvmStatic 63 | fun start(args: Array) { 64 | DefaultStarter.init(args) 65 | start() 66 | } 67 | 68 | } 69 | 70 | } 71 | 72 | class HookMiraiService : HookRunnable { 73 | override fun init(info: HookInfo) { 74 | 75 | } 76 | 77 | override fun preRun(method: HookMethod): Boolean { 78 | // method.result = Class.forName("net.mamoe.mirai.internal.MiraiImpl") 79 | // .run { kotlin.companionObjectInstance as? IMirai ?: newInstance() } 80 | return false 81 | } 82 | 83 | override fun postRun(method: HookMethod) { 84 | 85 | } 86 | 87 | override fun onError(method: HookMethod): Boolean { 88 | return false 89 | } 90 | } 91 | 92 | const val capHost = "https://tencap.icecreamapi.com" 93 | 94 | class HookSliderCaptcha : HookRunnable { 95 | override fun init(info: HookInfo) { 96 | 97 | } 98 | 99 | override fun preRun(method: HookMethod): Boolean { 100 | val client = OkHttpClient() 101 | 102 | fun post(url: String, paras: Map) = 103 | client.newCall( 104 | Request.Builder() 105 | .url(url) 106 | .post( 107 | FormBody.Builder() 108 | .apply { paras.forEach { add(it.key, it.value) } } 109 | .build() 110 | ).build() 111 | ).execute().body!!.string() 112 | 113 | val url = method.paras[2]!! as String 114 | val ticket = runBlocking { 115 | val ticket: String 116 | val pid = "${UUID.randomUUID()}.${UUID.randomUUID()}" 117 | 118 | post( 119 | "$capHost/createCap", 120 | mapOf("pid" to pid, "url" to url) 121 | ).toJSONObject() 122 | .getInteger("code") 123 | .let { 124 | if (it != 0) error("滑块会话创建失败!") 125 | } 126 | 127 | println("[SliderCaptcha] 需要滑动验证码, 请将下面提供的 Pid 输入进滑块助手,并点击提交按钮,然后手动完成滑块验证。") 128 | println("[SliderCaptcha] @see https://www.yuque.com/icecreamqaq/api/use-ten-cap") 129 | println("[SliderCaptcha] Pid: $pid") 130 | 131 | delay(6000) 132 | 133 | while (true) { 134 | val result = post( 135 | "$capHost/checkCap", 136 | mapOf("pid" to pid) 137 | ).toJSONObject() 138 | 139 | val cpc = result.getInteger("code") 140 | if (cpc != 0) { 141 | delay(2000) 142 | continue 143 | } 144 | 145 | ticket = result.getString("result") 146 | break 147 | } 148 | ticket 149 | } 150 | // RainUI.webListener(url) 151 | method.result = ticket 152 | return true 153 | } 154 | 155 | override fun postRun(method: HookMethod) { 156 | 157 | } 158 | 159 | override fun onError(method: HookMethod) = false 160 | 161 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/entity/mirai.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai.entity 2 | 3 | import com.IceCreamQAQ.Yu.toJSONObject 4 | import com.icecreamqaq.yuq.YuQ 5 | import com.icecreamqaq.yuq.entity.* 6 | import com.icecreamqaq.yuq.error.SendMessageFailedByCancel 7 | import com.icecreamqaq.yuq.event.SendMessageEvent 8 | import com.icecreamqaq.yuq.message.Image 9 | import com.icecreamqaq.yuq.message.Message 10 | import com.icecreamqaq.yuq.message.MessageSource 11 | import com.icecreamqaq.yuq.mirai.localEventBus 12 | import com.icecreamqaq.yuq.mirai.message.* 13 | import com.icecreamqaq.yuq.mirai.miraiBot 14 | import com.icecreamqaq.yuq.mirai.send 15 | import com.icecreamqaq.yuq.util.WebHelper.Companion.postWithQQKey 16 | import com.icecreamqaq.yuq.web 17 | import kotlinx.coroutines.runBlocking 18 | import net.mamoe.mirai.contact.Contact.Companion.uploadImage 19 | import net.mamoe.mirai.message.action.Nudge 20 | import net.mamoe.mirai.message.action.Nudge.Companion.sendNudge 21 | import net.mamoe.mirai.message.data.Image.Key.queryUrl 22 | import org.slf4j.LoggerFactory 23 | import java.io.File 24 | import net.mamoe.mirai.contact.Contact as MiraiContact 25 | import net.mamoe.mirai.contact.Friend as MiraiFriend 26 | import net.mamoe.mirai.contact.Group as MiraiGroup 27 | import net.mamoe.mirai.contact.NormalMember as MiraiMember 28 | 29 | abstract class ContactImpl(val miraiContact: MiraiContact) : Contact { 30 | 31 | override val yuq: YuQ 32 | get() = miraiBot 33 | 34 | private val log = LoggerFactory.getLogger(ContactImpl::class.java) 35 | 36 | override fun sendMessage(message: Message): MessageSource { 37 | // val ms = message.toLogString() 38 | // val ts = this.toLogString() 39 | // log.debug("Send Message To: $ts, $ms") 40 | // if (localEventBus.post(SendMessageEvent.Per(this, message))) throw SendMessageFailedByCancel() 41 | // val m = MiraiMessageSource( 42 | // runBlocking { 43 | // miraiContact.sendMessage(message.toLocal(this@ContactImpl)) 44 | // }.source 45 | // ) 46 | // localEventBus.post(SendMessageEvent.Post(this, message, m)) 47 | // log.info("$ts <- $ms") 48 | // return m 49 | return message.send(this, miraiContact) { 50 | kotlin.runCatching { 51 | MiraiMessageSource( 52 | runBlocking { 53 | miraiContact.sendMessage(message.toLocal(this@ContactImpl)) 54 | }.source 55 | ) 56 | }.getOrElse { 57 | miraiBot.rainBot.messageSendFailedByReadTimeout(this, message) 58 | } 59 | } 60 | } 61 | 62 | override fun sendFile(file: File) { 63 | TODO("Not yet implemented") 64 | } 65 | 66 | override fun uploadImage(imageFile: File): Image = 67 | runBlocking { miraiContact.uploadImage(imageFile).let { ImageReceive(it.imageId, it.queryUrl()) } } 68 | } 69 | 70 | class FriendImpl(internal val friend: MiraiFriend) : ContactImpl(friend), Friend { 71 | 72 | override val id = friend.id 73 | override val platformId = id.toString() 74 | override val guid = id.toString() 75 | 76 | override val avatar 77 | get() = friend.avatarUrl 78 | override val name 79 | get() = friend.nick 80 | 81 | override fun click() { 82 | runBlocking { 83 | friend.sendNudge(friend.nudge()) 84 | } 85 | } 86 | 87 | override fun delete() { 88 | TODO("Not yet implemented") 89 | } 90 | 91 | override fun toString() = "Friend($name($id))" 92 | 93 | } 94 | 95 | class GroupImpl(internal val group: MiraiGroup) : ContactImpl(group), Group { 96 | override val id = group.id 97 | override val platformId = id.toString() 98 | override val guid = "g$id" 99 | 100 | override var maxCount: Int = -1 101 | override val admins = arrayListOf() 102 | 103 | override val avatar: String 104 | get() = group.avatarUrl 105 | 106 | override val name: String = group.name 107 | override val notices: GroupNoticeList 108 | get() = TODO("Not yet implemented") 109 | override val owner: Member 110 | 111 | override operator fun get(qq: Long) = super.get(qq) as GroupMemberImpl 112 | 113 | override val members: UserListImpl 114 | override val bot: GroupMemberImpl 115 | 116 | init { 117 | members = UserListImpl() 118 | var owner: GroupMemberImpl? = null 119 | for (member in group.members) { 120 | val m = GroupMemberImpl(member, this) 121 | members[member.id] = m 122 | if (m.permission == 2) owner = m 123 | if (m.permission == 1) admins.add(m) 124 | } 125 | bot = GroupMemberImpl(group.botAsMember, this) 126 | this.owner = owner ?: if (bot.permission == 2) bot else error("Group $id Can't Find Owner!") 127 | 128 | } 129 | 130 | fun refreshAdmin() { 131 | admins.clear() 132 | for (member in group.members) { 133 | val m = GroupMemberImpl(member, this) 134 | members[member.id] = m 135 | if (m.permission == 1) admins.add(m) 136 | } 137 | } 138 | 139 | init { 140 | // maxCount = -1 141 | 142 | try { 143 | maxCount = web.postWithQQKey( 144 | "https://qun.qq.com/cgi-bin/qun_mgr/search_group_members", 145 | mapOf( 146 | "gc" to id.toString(), 147 | "st" to 0.toString(), 148 | "end" to 15.toString(), 149 | "sort" to "0", 150 | "bkn" to "{gtk}" 151 | ) as MutableMap 152 | ).toJSONObject().getIntValue("max_count") 153 | } catch (e: Exception) { 154 | } 155 | } 156 | 157 | override fun leave() { 158 | runBlocking { 159 | group.quit() 160 | } 161 | } 162 | 163 | override fun isFriend() = false 164 | 165 | override fun toString(): String { 166 | return "Group($name($id))" 167 | } 168 | 169 | override fun banAll() { 170 | group.settings.isMuteAll = true 171 | } 172 | 173 | override fun unBanAll() { 174 | group.settings.isMuteAll = false 175 | } 176 | 177 | 178 | } 179 | 180 | open class GroupMemberImpl( 181 | internal val member: MiraiMember, 182 | final override val group: GroupImpl 183 | ) : ContactImpl(member), 184 | Member { 185 | override val platformId = id.toString() 186 | override val guid = "${group.id}_$id" 187 | 188 | override val permission 189 | get() = member.permission.level 190 | override var nameCard 191 | get() = member.nameCard 192 | set(value) { 193 | member.nameCard = value 194 | } 195 | override var title 196 | get() = member.specialTitle 197 | set(value) { 198 | member.specialTitle = value 199 | } 200 | 201 | override fun at() = AtImpl(id) 202 | 203 | override val ban: Int 204 | get() { 205 | return member.muteTimeRemaining 206 | } 207 | 208 | override fun ban(time: Int) { 209 | runBlocking { 210 | member.mute(time) 211 | } 212 | } 213 | 214 | override fun click() { 215 | runBlocking { 216 | group.group.sendNudge(member.nudge()) 217 | } 218 | } 219 | 220 | override fun clickWithTemp() { 221 | TODO("Not yet implemented") 222 | } 223 | 224 | override fun unBan() { 225 | runBlocking { 226 | member.unmute() 227 | } 228 | } 229 | 230 | override fun kick(message: String) { 231 | runBlocking { 232 | member.kick(message) 233 | } 234 | } 235 | 236 | override fun toString(): String { 237 | return "Member($nameCard($id)[${group.name}(${group.id}])" 238 | } 239 | 240 | final override val id 241 | get() = member.id 242 | override val lastMessageTime: Long 243 | get() = member.lastSpeakTimestamp.toLong() * 1000 244 | override val avatar 245 | get() = member.avatarUrl 246 | override val name 247 | get() = member.nick 248 | 249 | 250 | } 251 | 252 | class AnonymousMemberImpl(member: MiraiMember, group: GroupImpl) : GroupMemberImpl(member, group), AnonymousMember { 253 | 254 | override val guid = "${id}_${group.id}" 255 | 256 | override fun canSendMessage() = false 257 | override fun isFriend() = false 258 | 259 | } 260 | -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/fun.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai 2 | 3 | import com.IceCreamQAQ.Yu.event.EventBus 4 | import com.icecreamqaq.yuq.entity.Contact 5 | import com.icecreamqaq.yuq.message.Message 6 | import com.icecreamqaq.yuq.message.MessageSource 7 | 8 | internal lateinit var localEventBus: EventBus 9 | internal lateinit var miraiBot: MiraiBot 10 | 11 | internal fun Message.send(contact: Contact, obj: T, send: (T) -> MessageSource) = miraiBot.rainBot.sendMessage(this, contact, obj, send) 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/logger/log.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai.logger 2 | 3 | import net.mamoe.mirai.utils.MiraiLogger 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | 7 | abstract class LogBase: MiraiLogger { 8 | abstract val log: Logger 9 | 10 | override val isEnabled = true 11 | override var follower: MiraiLogger? = null 12 | 13 | override fun verbose(message: String?) { 14 | log.trace(message) 15 | } 16 | 17 | override fun verbose(message: String?, e: Throwable?) { 18 | log.trace(message, e) 19 | } 20 | 21 | override fun debug(message: String?) { 22 | log.debug(message) 23 | } 24 | 25 | override fun debug(message: String?, e: Throwable?) { 26 | log.debug(message, e) 27 | } 28 | 29 | override fun info(message: String?) { 30 | log.info(message) 31 | } 32 | 33 | override fun info(message: String?, e: Throwable?) { 34 | log.info(message, e) 35 | } 36 | 37 | override fun warning(message: String?) { 38 | log.warn(message) 39 | } 40 | 41 | override fun warning(message: String?, e: Throwable?) { 42 | log.warn(message, e) 43 | } 44 | 45 | override fun error(message: String?) { 46 | log.error(message) 47 | } 48 | 49 | override fun error(message: String?, e: Throwable?) { 50 | log.error(message, e) 51 | } 52 | 53 | 54 | override fun plus(follower: T): T = follower 55 | } 56 | 57 | class Network(override val identity: String?) : LogBase() { 58 | override val log = LoggerFactory.getLogger(Network::class.java) 59 | } 60 | 61 | class Bot(override val identity: String?) : LogBase() { 62 | override val log = LoggerFactory.getLogger(Bot::class.java) 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/message/MiraiMessage.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai.message 2 | 3 | import com.icecreamqaq.yuq.entity.Contact 4 | import com.icecreamqaq.yuq.message.Message 5 | import com.icecreamqaq.yuq.message.MessageSource 6 | import kotlinx.coroutines.runBlocking 7 | import net.mamoe.mirai.contact.PermissionDeniedException 8 | import net.mamoe.mirai.message.data.MessageChain 9 | import net.mamoe.mirai.message.data.MessageSource.Key.recall 10 | import net.mamoe.mirai.message.data.QuoteReply 11 | import net.mamoe.mirai.message.data.bot 12 | import net.mamoe.mirai.message.data.buildMessageChain 13 | 14 | fun Message.toLocal(contact: Contact): MessageChain { 15 | var mm = buildMessageChain {} 16 | 17 | if (this.reply != null) mm += QuoteReply((this.reply as MiraiMessageSource).source) 18 | if (this.at != null) { 19 | mm += AtImpl(at!!.id).toLocal(contact) 20 | if (at!!.newLine) mm += "\n" 21 | } 22 | 23 | for (messageItem in this.body) { 24 | mm += messageItem.toLocal(contact) as net.mamoe.mirai.message.data.Message 25 | } 26 | 27 | return mm 28 | } 29 | 30 | class MiraiMessageSource(val source: net.mamoe.mirai.message.data.MessageSource) : MessageSource { 31 | override val id: Int 32 | get() = source.ids[0] 33 | override val liteMsg: String = source.contentToString() 34 | override val sendTime: Long 35 | get() = TODO("Not yet implemented") 36 | override val sendTo: Long 37 | get() = TODO("Not yet implemented") 38 | override val sender: Long 39 | get() = TODO("Not yet implemented") 40 | 41 | override fun recall(): Int { 42 | return runBlocking { 43 | return@runBlocking try { 44 | source.recall() 45 | 0 46 | } catch (e: PermissionDeniedException) { 47 | -1 48 | } catch (e: IllegalStateException) { 49 | -2 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/message/MiraiMessageItemFactory.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai.message 2 | 3 | import com.IceCreamQAQ.Yu.`as`.ApplicationService 4 | import com.IceCreamQAQ.Yu.util.IO 5 | import com.IceCreamQAQ.Yu.util.Web 6 | import com.icecreamqaq.yuq.entity.Member 7 | import com.icecreamqaq.yuq.message.* 8 | import com.icecreamqaq.yuq.web 9 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 10 | import java.awt.image.BufferedImage 11 | import java.io.File 12 | import java.io.InputStream 13 | import javax.inject.Inject 14 | 15 | class MiraiMessageItemFactory : MessageItemFactory { 16 | 17 | override fun text(text: String) = TextImpl(text) 18 | 19 | override fun at(member: Member) = AtMemberImpl(member) 20 | 21 | override fun at(qq: Long) = AtImpl(qq) 22 | 23 | override fun face(id: Int) = FaceImpl(id) 24 | 25 | 26 | override fun image(file: File) = imageByFile(file) 27 | 28 | override fun image(url: String) = imageByUrl(url) 29 | 30 | override fun imageByBufferedImage(bufferedImage: BufferedImage) = TODO()//ImageSend(bufferedImage.data.) 31 | 32 | override fun imageByFile(file: File) = ImageSend(file.toExternalResource()) 33 | 34 | override fun imageById(id: String) = ImageReceive(id, "") 35 | 36 | override fun imageByInputStream(inputStream: InputStream) = ImageSend(inputStream.toExternalResource().apply { inputStream.close() }) 37 | 38 | override fun imageByUrl(url: String) = imageByInputStream(web.download(url)) 39 | 40 | override fun imageToFlash(image: Image) = FlashImageImpl(image) 41 | 42 | override fun voiceByInputStream(inputStream: InputStream) = VoiceSend(inputStream) 43 | 44 | override fun xmlEx(serviceId: Int, value: String): XmlEx = XmlImpl(serviceId, value) 45 | 46 | override fun jsonEx(value: String) = JsonImpl(value) 47 | override fun messagePackage(flag: Int, body: MutableList): MessagePackage { 48 | TODO("Not yet implemented") 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/message/UploadResource.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai.message 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/message/items.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai.message 2 | 3 | import com.icecreamqaq.yuq.entity.Contact 4 | import com.icecreamqaq.yuq.entity.Member 5 | import com.icecreamqaq.yuq.message.* 6 | import com.icecreamqaq.yuq.message.At 7 | import com.icecreamqaq.yuq.message.Face 8 | import com.icecreamqaq.yuq.message.FlashImage 9 | import com.icecreamqaq.yuq.message.Image 10 | import com.icecreamqaq.yuq.message.Voice 11 | import com.icecreamqaq.yuq.mirai.entity.ContactImpl 12 | import com.icecreamqaq.yuq.mirai.entity.GroupImpl 13 | import com.icecreamqaq.yuq.mirai.entity.GroupMemberImpl 14 | import kotlinx.coroutines.runBlocking 15 | import net.mamoe.mirai.message.data.* 16 | import net.mamoe.mirai.utils.ExternalResource 17 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 18 | import java.io.InputStream 19 | import net.mamoe.mirai.contact.Member as MiraiMember 20 | import net.mamoe.mirai.message.data.At as MiraiAt 21 | import net.mamoe.mirai.message.data.Face as MiraiFace 22 | import net.mamoe.mirai.message.data.Image as MiraiImage 23 | import net.mamoe.mirai.message.data.Audio as MiraiVoice 24 | 25 | 26 | class TextImpl(override var text: String) : MessageItemBase(), Text { 27 | 28 | override fun toLocal(contact: Contact) = PlainText(text) 29 | 30 | } 31 | 32 | class AtImpl(override var user: Long) : MessageItemBase(), At { 33 | 34 | override fun toLocal(contact: Contact) = 35 | if (contact is GroupImpl) 36 | if (user == -1L) AtAll 37 | else MiraiAt(contact[user].miraiContact as MiraiMember) 38 | else PlainText("@$user") 39 | 40 | } 41 | 42 | class AtMemberImpl(override val member: Member) : MessageItemBase(), AtByMember { 43 | override fun toLocal(contact: Contact) = MiraiAt((member as GroupMemberImpl).miraiContact as MiraiMember) 44 | } 45 | 46 | class FaceImpl(override val faceId: Int) : MessageItemBase(), Face { 47 | 48 | override fun toLocal(contact: Contact) = MiraiFace(faceId) 49 | 50 | } 51 | 52 | class ImageSend(private val ei: ExternalResource) : MessageItemBase(), Image { 53 | 54 | override lateinit var id: String 55 | override lateinit var url: String 56 | 57 | lateinit var image: MiraiImage 58 | 59 | override fun toLocal(contact: Contact): Any { 60 | contact as ContactImpl 61 | if (::image.isInitialized) return image 62 | image = runBlocking { contact.miraiContact.uploadImage(ei) } 63 | ei.close() 64 | id = image.imageId 65 | return image 66 | } 67 | 68 | override fun toPath() = "图片" 69 | 70 | } 71 | 72 | open class ImageReceive(id: String, override val url: String) : MessageItemBase(), Image { 73 | 74 | override val id: String = if (id.startsWith("{")) id.replace("{", "").replace("}", "").replace("-", "") else id 75 | 76 | override fun toLocal(contact: Contact): Any { 77 | return MiraiImage(id.split(".").let { "{${it[0].toUUID()}}.${it[1]}" }) 78 | // val cType = contact is GroupImpl 79 | // val iType = image is GroupImage 80 | // return if (cType == iType) image else runBlocking { mif.imageByUrl(image.queryUrl()).toLocal(contact) } 81 | } 82 | 83 | private fun String.toUUID(): String = "${this[0..7]}-${this[8..11]}-${this[12..15]}-${this[16..19]}-${this[20..31]}" 84 | 85 | private operator fun String.get(intRange: IntRange): String { 86 | val sb = StringBuilder() 87 | 88 | for (i in intRange) { 89 | sb.append(this[i]) 90 | } 91 | return sb.toString() 92 | } 93 | 94 | } 95 | 96 | 97 | class FlashImageImpl(override val image: Image) : MessageItemBase(), FlashImage { 98 | 99 | override fun toLocal(contact: Contact): Any { 100 | return (image.toLocal(contact) as MiraiImage).flash() 101 | } 102 | 103 | override fun toPath() = "闪照" 104 | } 105 | 106 | class VoiceRecv( 107 | val miraiVoice: OnlineAudio 108 | ) : MessageItemBase(), Voice { 109 | 110 | override val id: String = miraiVoice.filename 111 | override val url: String = miraiVoice.urlForDownload ?: "" 112 | 113 | override fun toLocal(contact: Contact) = miraiVoice 114 | } 115 | 116 | class VoiceSend(val inputStream: InputStream) : MessageItemBase(), Voice { 117 | 118 | lateinit var miraiVoice: MiraiVoice 119 | 120 | override fun toPath() = if (::miraiVoice.isInitialized) miraiVoice.filename ?: "" else "语音" 121 | 122 | override val id: String 123 | get() = miraiVoice.filename 124 | override val url: String 125 | get() = "" 126 | 127 | override fun toLocal(contact: Contact): Any { 128 | return if (::miraiVoice.isInitialized) miraiVoice 129 | else if (contact is GroupImpl) 130 | runBlocking { 131 | contact.group.uploadAudio(inputStream.toExternalResource()) 132 | }.apply { miraiVoice = this } 133 | else error("mirai send voice only supposed group!") 134 | } 135 | 136 | } 137 | 138 | class XmlImpl(override val serviceId: Int, override val value: String) : MessageItemBase(), XmlEx { 139 | 140 | override fun toLocal(contact: Contact) = SimpleServiceMessage(serviceId, value) 141 | 142 | } 143 | 144 | class JsonImpl(override val value: String) : MessageItemBase(), JsonEx { 145 | 146 | override fun toLocal(contact: Contact) = LightApp(value) 147 | 148 | } 149 | 150 | class NoImplItemImpl(override var source: Any) : MessageItemBase(), NoImplItem { 151 | override fun toLocal(contact: Contact) = source 152 | } 153 | 154 | -------------------------------------------------------------------------------- /src/main/kotlin/com/icecreamqaq/yuq/mirai/util/YuQInternalFunMiraiImpl.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.mirai.util 2 | 3 | import com.icecreamqaq.yuq.entity.Member 4 | import com.icecreamqaq.yuq.util.YuQInternalFun 5 | 6 | class YuQInternalFunMiraiImpl: YuQInternalFun() { 7 | 8 | override fun setMemberLastMessageTime(member: Member, time: Long) { 9 | 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/main/resources/conf/module/com.IceCreamQAQ.YuQ.Mirai.properties: -------------------------------------------------------------------------------- 1 | #yu.config.runMode = devv 2 | 3 | yu.scanPackages=com.icecreamqaq.yuq 4 | yu.modules=com.icecreamqaq.yuq.mirai.YuQMiraiModule 5 | #yu.cache.ehcache.config = ehcache-YuQ-Mirai.xml 6 | 7 | YuQ.Controller.RainCode.prefix = ^ 8 | YuQ.Controller.RainCode.enable = false 9 | 10 | yu.cache.ehcache.caches.ContextSession.default=ehcache-YuQ-Mirai.xml -------------------------------------------------------------------------------- /src/main/resources/ehcache-YuQ-Mirai.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 24 | 25 | 26 | --------------------------------------------------------------------------------