├── LICENSE ├── README.md ├── Subscription ├── RecordLoader.py ├── __init__.py ├── account.json ├── compareData.py └── tri_account.json ├── __init__.py ├── cal_shanten ├── __init__.py ├── cal_mahjong.py ├── dfs.py └── utils.py ├── fonts └── msyh1.otf ├── gacha ├── __init__.py ├── gacha.json ├── gacha.py └── group_pool.json ├── mahjong_handle ├── __init__.py ├── assets │ ├── correct.png │ ├── exist.png │ ├── font │ │ ├── HYWenHei 65W.ttf │ │ └── HYWenHei 85W.ttf │ ├── no.png │ └── rule.png ├── db │ └── db.sqlite ├── handler.py ├── hands.txt ├── imghandler.py ├── mahjong_image.py ├── user.py └── utils.py ├── majsoul_Info ├── __init__.py ├── majsoul_Spider.py └── processData.py ├── requirements.txt ├── resources ├── decoration │ ├── 24K金棒.jpg │ ├── huiye │ │ ├── 和牌-恋之降临.jpg │ │ ├── 桌布-恋之见证.jpg │ │ ├── 牌背-恋之背影.jpg │ │ └── 立直-恋之箭矢.jpg │ ├── saki1 │ │ ├── 和牌-花天月地.jpg │ │ ├── 和牌-龙卷雷霆.jpg │ │ ├── 桌布-赛前小憩.jpg │ │ ├── 牌背-艾托企鹅.jpg │ │ └── 立直棒-墨西哥卷饼.jpg │ ├── saki2 │ │ ├── 和牌-未来视.jpg │ │ ├── 和牌-高岭之花.jpg │ │ ├── 桌布-清凉假日.jpg │ │ ├── 牌背-摇曳气球.jpg │ │ └── 立直棒-爱心便当.jpg │ ├── 一触即发.jpg │ ├── 出阵.jpg │ ├── 和牌-KO.jpg │ ├── 和牌-天罚.jpg │ ├── 和牌-安可.jpg │ ├── 和牌-幽灵嗷嗷.jpg │ ├── 和牌-方舟反应堆.jpg │ ├── 和牌-旋风.jpg │ ├── 和牌-核心裂变.jpg │ ├── 和牌-樱花.jpg │ ├── 和牌-烈焰.jpg │ ├── 和牌-爆炎龙卷.jpg │ ├── 和牌-红玫瑰.jpg │ ├── 和牌-逆鳞.jpg │ ├── 和牌-黑炎.jpg │ ├── 咸鱼立直棒.jpg │ ├── 大葱立直棒.jpg │ ├── 孔雀绿桌布.jpg │ ├── 果绿牌背.jpg │ ├── 桌布-吃瓜.jpg │ ├── 橘猫爪.jpg │ ├── 淡黄牌背.jpg │ ├── 激斗.jpg │ ├── 牌背-天然呆幽灵.jpg │ ├── 狗骨头立直棒.jpg │ ├── 猩红立直棒.jpg │ ├── 玫瑰红牌背.jpg │ ├── 真剑胜负.jpg │ ├── 立直-叮.jpg │ ├── 立直-幻影.jpg │ ├── 立直-开场曲.jpg │ ├── 立直-火焰.jpg │ ├── 立直-碎冰.jpg │ ├── 立直-苍火.jpg │ ├── 立直-虚拟导航.jpg │ ├── 立直-蝙蝠.jpg │ ├── 立直-雷电环锁.jpg │ ├── 立直-飞羽.jpg │ ├── 立直-龙腾.jpg │ ├── 立直棒-仿生喵.jpg │ ├── 立直棒-小恶魔蝙蝠.jpg │ ├── 立直棒-应援棒.jpg │ ├── 立直棒-恋之反省.jpg │ ├── 立直棒-断恶.jpg │ ├── 立直棒-陨石法杖.jpg │ ├── 立直棒-雪糕.jpg │ ├── 紫罗兰桌布.jpg │ └── 莲藕紫桌布.jpg ├── gift │ ├── 00-手工曲奇.jpg │ ├── 01-蓝罐曲奇.jpg │ ├── 02-香喷喷曲奇.jpg │ ├── 03-怀旧掌机.jpg │ ├── 04-Twitch掌机.jpg │ ├── 05-次世代游戏机.jpg │ ├── 06-简易美术品.jpg │ ├── 07-精美挂画.jpg │ ├── 08-经典名画.jpg │ ├── 09-美味果酒.jpg │ ├── 10-香醇红酒.jpg │ ├── 11-82年的拉菲.jpg │ ├── 12-普通的碎钻.jpg │ ├── 13-鸽子蛋宝石.jpg │ ├── 14-海洋之心.jpg │ ├── 15-熊公仔.jpg │ ├── 16-熊公仔L.jpg │ ├── 17-熊公仔XXL.jpg │ ├── 18-同人小册子.jpg │ ├── 19-简装同人志.jpg │ ├── 20-精美同人志.jpg │ ├── 21-朴素的小裙子.jpg │ ├── 22-普通的小裙子.jpg │ └── 23-华丽的小裙子.jpg ├── jades │ ├── 光明宝玉.jpg │ ├── 勇气宝玉.jpg │ ├── 希望宝玉.jpg │ ├── 意志宝玉.jpg │ ├── 慈爱宝玉.jpg │ ├── 智慧宝玉.jpg │ ├── 纯真宝玉.jpg │ └── 诚实宝玉.jpg └── person │ ├── 七海礼奈.png │ ├── 三上千织.png │ ├── 九条璃雨.png │ ├── 二之宫花.png │ ├── 五十岚阳菜.png │ ├── 八木唯.png │ ├── 凉宫杏树.png │ ├── 北见纱和子.png │ ├── 卡维.png │ ├── 原村和.png │ ├── 四宫辉夜.png │ ├── 园城寺怜.png │ ├── 天江衣.png │ ├── 姬川响.png │ ├── 宫永咲.png │ ├── 宫永照.png │ ├── 寺崎千穗理.png │ ├── 小野寺七羽.png │ ├── 小鸟游雏田.png │ ├── 抚子.png │ ├── 新子憧.png │ ├── 早乙女芽亚里.png │ ├── 早坂爱.png │ ├── 柚.png │ ├── 桃喰绮罗莉.png │ ├── 森川绫子.png │ ├── 泽尼娅.png │ ├── 生志摩妄.png │ ├── 白石奈奈.png │ ├── 白银圭.png │ ├── 白银御行.png │ ├── 相原舞.png │ ├── 福姬.png │ ├── 福路美穗子.png │ ├── 竹井久.png │ ├── 艾丽莎.png │ ├── 莎拉.png │ ├── 藤本绮罗.png │ ├── 藤田佳奈.png │ ├── 蛇喰梦子.png │ ├── 赤木茂.png │ ├── 辉夜姬.png │ ├── 雏桃.png │ └── 鹫巢岩.png └── screenshot ├── ControlRecord.png ├── OrderRecord.png ├── selectBasicInfo.png ├── selectExtendInfo.png └── selectRecord.png /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 |

2 | Majsoul_bot 3 |

4 |

Majsoul_bot

5 |

✨ 基于HoshinoBot V2的雀魂Majsoul多功能插件✨

6 |

🌸 天麻联动二号卡池已更新 🌸

7 |
8 | 说明文档   ·   9 | 指令列表   ·   10 | 常见问题 11 |
12 |

13 | 27 | 28 | 29 | ## **丨前言&插件简介** 30 | 一个雀魂信息查询 Bot 插件,该插件不包括本体,应该配合[**HoshinoBot**](https://github.com/Ice-Cirno/HoshinoBot)并结合[**go-cqhttp**](https://github.com/Mrs4s/go-cqhttp)使用: 31 | 32 | HoshinoBot雀魂插件交流群:[**244430070**](https://jq.qq.com/?_wv=1027&k=IHh5gtLm),可在该群体验插件功能、反馈BUG、约友人场(大概) 33 | 34 | 项目地址:https://github.com/DaiShengSheng/Majsoul_bot 35 | 36 | 本插件数据来源于雀魂牌谱屋:https://amae-koromo.sapk.ch/ 37 | 38 | 由于牌谱屋不收录铜之间以及银之间牌谱,故所有数据仅统计**2019年11月29日后**金场及以上场次的数据 39 | 40 | 这个项目使用的**HoshinoBot**的消息触发器,如果你了解其他QQ机器人框架的api(比如nonebot)可以只修改消息触发器就将本项目移植到其他框架 41 | 42 | 移植后转载及发布请标注本项目原地址,谢谢。 43 | 44 | ## 丨安装方法 45 | 下面介绍HoshinoBot的安装方法 46 | 47 | 1. 在 HoshinoBot\hoshino\modules 目录下使用以下命令拉取本项目 48 | ``` 49 | git clone https://github.com/Daishengsheng/Majsoul_bot.git 50 | ``` 51 | 2. 然后使用如下命令安装依赖 52 | ``` 53 | pip install -r requirements.txt 54 | ``` 55 | 3. 然后在 HoshinoBot\\hoshino\\config\\\__bot__.py 文件的 MODULES_ON 加入 Majsoul_bot 56 | 4. 重启 HoshinoBot,进入机器人在的群聊,即可正常使用本插件。 57 | 58 | ## 丨已实现的功能列表 59 | ### 丨战绩查询&订阅模块 60 | _基于雀魂牌谱屋提供的 API_ 61 | * 金之间以上的个人总体数据查询(包括总体对局信息、南场/东场个人的对局信息、放铳率、位次等) 62 | * 个人特定段位场的总体详细数数据查询(如个人在金之间/玉之间对局的的详细信息) 63 | * 金之间以上的个人牌谱查询(可查询近期个人最近五场的对局牌谱信息) 64 | * 对局信息订阅与播报(基于牌谱屋对绑定的昵称进行对局监控) 65 | ### 丨其他功能模块 66 | * 雀魂卡池的模拟抽卡(支持切换联动UP池) 67 | * 麻将猜手牌(麻兜,代码源自[**艾琳佬的插件**](https://github.com/yuyumoko/mahjong-hand-guess)) 68 | * 天凤牌理(不考虑七对子与国士无双) 69 | 70 | ## 丨效果演示 71 | ### 基本数据查询 72 | ![基本数据查询](https://github.com/DaiShengSheng/Majsoul_bot/blob/master/screenshot/selectBasicInfo.png) 73 | ### 详细数据查询 74 | ![详细数据查询](https://github.com/DaiShengSheng/Majsoul_bot/blob/master/screenshot/selectExtendInfo.png) 75 | ### 近期对局查询 76 | ![近期对局查询](https://github.com/DaiShengSheng/Majsoul_bot/blob/master/screenshot/selectRecord.png) 77 | ### 雀魂对局订阅 78 | ![雀魂对局订阅](https://github.com/DaiShengSheng/Majsoul_bot/blob/master/screenshot/OrderRecord.png) 79 | ### 订阅的开启与删除 80 | ![订阅的开启与删除](https://github.com/DaiShengSheng/Majsoul_bot/blob/master/screenshot/ControlRecord.png) 81 | 82 | ## 丨常见问题 Q&A 83 | ### 丨为何 Bot 启动时,报错类似No module named 'xxxxx'? 84 | 依赖未安装,使用命令pip install xxxxx即可. 85 | 86 | 若无效可尝试pip3 install xxxxx或者pip39 install xxxxx 87 | ### 丨为何我对局结束后 Bot 没有播报我的对局? 88 | 由于本插件使用的是牌谱屋的API,雀魂牌谱屋获取对局信息存在延迟,等待片刻即可。 89 | ### 丨为何查询不到我的个人信息? 90 | 由于牌谱屋只统计金之间以上的数据,请务必在查询或者订阅前在金之间对局一次,然后等待牌谱屋更新。 91 | 92 | 若还没有获取到相应信息,请再次进行查询。如果尝试几次都无法正常查询,请检查控制台后将报错截图提交到在issues当中 93 | 94 | ## | 感谢 95 | - [Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp的golang实现,轻量、原生跨平台. 96 | - [Ice-Cirno / HoshinoBot](https://github.com/Ice-Cirno/HoshinoBot) :绝赞的QQ机器人HoshinoBot. 97 | - [SAPikachu / amae-koromo](https://github.com/SAPikachu/amae-koromo) :雀魂牌谱屋!本插件查询功能API来源于此. 98 | - [yuyumoko / mahjong-hand-guess](https://github.com/yuyumoko/mahjong-hand-guess) :麻兜功能小游戏. 99 | -------------------------------------------------------------------------------- /Subscription/RecordLoader.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import aiohttp 3 | from os.path import dirname,join 4 | import urllib.request 5 | import urllib.error 6 | import urllib.parse 7 | import json 8 | import time 9 | 10 | baseurl = "https://ak-data-1.sapk.ch/api/v2/pl4" 11 | tribaseurl = "https://ak-data-1.sapk.ch/api/v2/pl3" 12 | path = dirname(__file__) 13 | 14 | 15 | async def getURL(url): 16 | try: 17 | headers = { 18 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36" 19 | } 20 | async with aiohttp.ClientSession() as session: 21 | async with await session.get(url=url,headers=headers) as response: 22 | info = await response.text() 23 | except: 24 | return "error" 25 | return info 26 | 27 | async def getID(nickname,num):#获取牌谱屋角色ID 28 | nickname = urllib.parse.quote(nickname) #UrlEncode转换 29 | if num == 4: 30 | url = baseurl + "/search_player/"+nickname+"?limit=9" 31 | else: 32 | url = tribaseurl + "/search_player/" + nickname + "?limit=9" 33 | data = await getURL(url) 34 | if data == "error": 35 | return -404 36 | datalist = json.loads(data) 37 | if datalist == [] : 38 | return -1 39 | return datalist 40 | 41 | async def selectRecord(id,num): 42 | localtime = time.time() 43 | urltime = str(int(localtime * 1000)) # 时间戳 44 | if num == 4: 45 | basicurl = baseurl + "/player_stats/" + str(id) + "/1262304000000/" + urltime + "?mode=16.12.9.15.11.8" 46 | else: 47 | basicurl = tribaseurl + "/player_stats/" + str(id) + "/1262304000000/" + urltime + "?mode=22.24.26.21.23.25" 48 | data = await getURL(basicurl) 49 | if data == "error": 50 | return -1 51 | count = str(json.loads(data)["count"]) 52 | if num == 4: 53 | recordurl = baseurl + "/player_records/"+str(id)+"/"+urltime+"/1262304000000?limit=2&mode=16.12.9.15.11.8&descending=true&tag="+count 54 | else: 55 | recordurl = tribaseurl + "/player_records/"+str(id)+"/"+urltime+"/1262304000000?limit=2&mode=22.24.26.21.23.25&descending=true&tag="+count 56 | record = await getURL(recordurl) 57 | if record == "error": 58 | return -1 59 | return record 60 | 61 | def localLoad(num): 62 | if num == 4: 63 | with open(join(path,'account.json'),encoding='utf-8') as fp: 64 | data = json.load(fp) 65 | #print(data[0]["uuid"]) 66 | else: 67 | with open(join(path,'tri_account.json'),encoding='utf-8') as fp: 68 | data = json.load(fp) 69 | return data 70 | 71 | 72 | def jsonWriter(Record,gid,id,num): 73 | localdata = localLoad(num) 74 | data = json.loads(Record) 75 | datalist = [] 76 | for i in range(0,len(localdata)): 77 | if localdata[i]["gid"] == str(gid) and localdata[i]["id"] == id: 78 | return False 79 | datalist.append(localdata[i]) 80 | binds = { 81 | "id": id, 82 | "uuid": str(data[0]["uuid"]), 83 | "endTime": int(data[0]["endTime"]), 84 | "gid": str(gid), 85 | "record_on": True, 86 | } 87 | datalist.append(binds) 88 | if num == 4: 89 | with open(join(path,'account.json'),'w',encoding='utf-8') as fp: 90 | json.dump(datalist,fp,indent=4) 91 | else: 92 | with open(join(path, 'tri_account.json'), 'w', encoding='utf-8') as fp: 93 | json.dump(datalist, fp, indent=4) 94 | return True 95 | -------------------------------------------------------------------------------- /Subscription/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .compareData import * 3 | from hoshino import Service 4 | from hoshino.typing import HoshinoBot,CQEvent 5 | from nonebot import get_bot 6 | 7 | sv = Service("雀魂对局订阅") 8 | 9 | 10 | @sv.on_prefix("雀魂订阅") 11 | async def orderInfo(bot, ev: CQEvent): 12 | nickname = ev.message.extract_plain_text() 13 | if len(nickname) > 15: 14 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 15 | IDdata = await getID(nickname,4) 16 | message = "" 17 | if IDdata == -404: 18 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 19 | if IDdata == -1: 20 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次再进行订阅") 21 | else: 22 | if len(IDdata) > 1: 23 | gid = ev["group_id"] 24 | playerRecord = await selectRecord(IDdata[0]["id"],4) # 获取对局记录 25 | if jsonWriter(playerRecord, gid, IDdata[0]["id"],4): 26 | message = message + "查询到多条角色昵称呢~,若订阅不是您想订阅的昵称,请补全昵称后重试\n" 27 | message = message + "昵称:" + str(IDdata[0]["nickname"]) + " 的对局已订阅成功" 28 | else: 29 | message = message + "该昵称在本群已被订阅,请不要重新订阅哦!" 30 | await bot.send(ev, message) 31 | else: 32 | gid = ev["group_id"] 33 | playerRecord = await selectRecord(IDdata[0]["id"],4) #获取对局记录 34 | if jsonWriter(playerRecord,gid,IDdata[0]["id"],4): 35 | message = message + "昵称:" + str(IDdata[0]["nickname"])+" 的对局已订阅成功" 36 | else: 37 | message = message + "该昵称在本群已被订阅,请不要重新订阅哦!" 38 | await bot.send(ev, message) 39 | 40 | @sv.on_prefix(("关闭雀魂订阅","取消雀魂订阅")) 41 | async def cancelOrder(bot,ev:CQEvent): 42 | nickname = ev.message.extract_plain_text() 43 | if len(nickname) > 15: 44 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 45 | gid = ev["group_id"] 46 | message = "" 47 | record = localLoad(4) 48 | flag = False 49 | IDdata = await getID(nickname,4) 50 | if IDdata == -404: 51 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 52 | datalist=[] 53 | if IDdata == -1: 54 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次后重试") 55 | else: 56 | for i in range(0,len(record)): 57 | if int(record[i]["gid"]) == int(gid) and IDdata[0]["id"]==record[i]["id"]: 58 | message = message + IDdata[0]["nickname"] 59 | record[i]["record_on"] = False 60 | flag = True 61 | datalist.append(record[i]) 62 | if flag: 63 | with open(join(path, 'account.json'), 'w', encoding='utf-8') as fp: 64 | json.dump(datalist, fp, indent=4) 65 | await bot.send(ev,"昵称:"+ message +" 在本群的四麻订阅已成功关闭") 66 | else: 67 | await bot.finish(ev,"没有找到该昵称在本群的订阅记录哦,请检查后重试") 68 | 69 | @sv.on_prefix("开启雀魂订阅") 70 | async def openOrder(bot,ev:CQEvent): 71 | nickname = ev.message.extract_plain_text() 72 | if len(nickname) > 15: 73 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 74 | gid = ev["group_id"] 75 | record = localLoad(4) 76 | flag = False 77 | IDdata = await getID(nickname,4) 78 | if IDdata == -404: 79 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 80 | message = "" 81 | datalist=[] 82 | if IDdata == -1: 83 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次后重试") 84 | else: 85 | for i in range(0,len(record)): 86 | if int(record[i]["gid"]) == int(gid) and IDdata[0]["id"]==record[i]["id"]: 87 | message = message + IDdata[0]["nickname"] 88 | record[i]["record_on"] = True 89 | flag = True 90 | datalist.append(record[i]) 91 | if flag: 92 | with open(join(path, 'account.json'), 'w', encoding='utf-8') as fp: 93 | json.dump(datalist, fp, indent=4) 94 | await bot.send(ev,"昵称:"+ message +"在本群的四麻订阅已成功开启") 95 | else: 96 | await bot.send(ev,"没有找到该昵称在本群的订阅记录哦,请检查后重试") 97 | 98 | @sv.scheduled_job('interval', minutes=3) 99 | async def record_scheduled(): 100 | bot = get_bot() 101 | record = localLoad(4) 102 | for i in range(0,len(record)): 103 | playerRecord = await selectRecord(record[i]["id"],4) 104 | if playerRecord == -1: 105 | sv.logger.info("获取" + str(record[i]["id"]) + "的对局数据超时已自动跳过") 106 | continue 107 | compareRecord = json.loads(playerRecord) 108 | sv.logger.info("正在检测更新"+str(record[i]["id"])+"的对局数据") 109 | if int(record[i]["endTime"]) < int(compareRecord[0]["endTime"]): 110 | message = updateData(playerRecord,record[i]["gid"],record[i]["id"],4) 111 | await bot.send_group_msg(group_id=int(record[i]["gid"]),message=message) 112 | 113 | @sv.on_fullmatch("雀魂订阅状态") 114 | async def orderSituation(bot,ev): 115 | gid = ev["group_id"] 116 | datalist = [] 117 | message = "" 118 | record = localLoad(4) 119 | for i in range(0,len(record)): 120 | if int(record[i]["gid"]) == int(gid): 121 | datalist.append(record[i]) 122 | if len(datalist) == 0: 123 | print(len(datalist)) 124 | await bot.finish(ev,"本群还没有雀魂对局的订阅哦~") 125 | else: 126 | message = message + "已查询到群"+str(gid)+"的订阅状态:\n" 127 | for i in range(0,len(datalist)): 128 | data = await selectNickname(datalist[i]["id"],"4") 129 | sv.logger.info("正在获取"+str(datalist[i]["id"])+"的昵称信息") 130 | if data == -1: 131 | await bot.finish(ev, "获取昵称信息失败,请重试") 132 | else: 133 | message = message + "昵称:" + data + " " 134 | if datalist[i]["record_on"]: 135 | message = message + "开启\n" 136 | else: 137 | message = message + "关闭\n" 138 | await bot.send(ev,message) 139 | 140 | 141 | @sv.on_prefix("删除雀魂订阅") 142 | async def delInfo(bot,ev): 143 | nickname = ev.message.extract_plain_text() 144 | if len(nickname) > 15: 145 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 146 | gid = ev["group_id"] 147 | record = localLoad(4) 148 | flag = False 149 | IDdata = await getID(nickname,4) 150 | if IDdata == -404: 151 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 152 | datalist = [] 153 | if IDdata == -1: 154 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次后重试") 155 | else: 156 | for i in range(0, len(record)): 157 | if int(record[i]["gid"]) == int(gid) and IDdata[0]["id"] == record[i]["id"]: 158 | flag = True 159 | continue 160 | else: 161 | datalist.append(record[i]) 162 | if flag: 163 | with open(join(path, 'account.json'), 'w', encoding='utf-8') as fp: 164 | json.dump(datalist, fp, indent=4) 165 | await bot.send(ev, "该昵称在本群的四麻订阅已删除") 166 | else: 167 | await bot.send(ev, "没有找到该昵称在本群的订阅记录哦,请检查后重试") 168 | 169 | @sv.on_prefix("三麻订阅") 170 | async def orderTriInfo(bot, ev: CQEvent): 171 | nickname = ev.message.extract_plain_text() 172 | if len(nickname) > 15: 173 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 174 | IDdata = await getID(nickname,3) 175 | message = "" 176 | if IDdata == -1: 177 | await bot.send(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次再进行订阅") 178 | else: 179 | if len(IDdata) > 1: 180 | gid = ev["group_id"] 181 | playerRecord = await selectRecord(IDdata[0]["id"],3) # 获取对局记录 182 | if jsonWriter(playerRecord, gid, IDdata[0]["id"],3): 183 | message = message + "查询到多条角色昵称呢~,若订阅不是您想订阅的昵称,请补全昵称后重试\n" 184 | message = message + "昵称:" + str(IDdata[0]["nickname"]) + " 的对局已订阅成功" 185 | else: 186 | message = message + "该昵称在本群已被订阅,请不要重新订阅哦!" 187 | await bot.send(ev, message) 188 | else: 189 | gid = ev["group_id"] 190 | playerRecord = await selectRecord(IDdata[0]["id"],3) #获取对局记录 191 | if jsonWriter(playerRecord,gid,IDdata[0]["id"],3): 192 | message = message + "昵称:" + str(IDdata[0]["nickname"])+" 的对局已订阅成功" 193 | else: 194 | message = message + "该昵称在本群已被订阅,请不要重新订阅哦!" 195 | await bot.send(ev, message) 196 | 197 | @sv.on_prefix(("关闭三麻订阅","取消三麻订阅")) 198 | async def cancelTriOrder(bot,ev:CQEvent): 199 | nickname = ev.message.extract_plain_text() 200 | if len(nickname) > 15: 201 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 202 | gid = ev["group_id"] 203 | message = "" 204 | record = localLoad(3) 205 | flag = False 206 | IDdata = await getID(nickname,3) 207 | datalist=[] 208 | if IDdata == -1: 209 | await bot.send(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次后重试") 210 | else: 211 | for i in range(0,len(record)): 212 | if int(record[i]["gid"]) == int(gid) and IDdata[0]["id"]==record[i]["id"]: 213 | message = message + IDdata[0]["nickname"] 214 | record[i]["record_on"] = False 215 | flag = True 216 | datalist.append(record[i]) 217 | if flag: 218 | with open(join(path, 'tri_account.json'), 'w', encoding='utf-8') as fp: 219 | json.dump(datalist, fp, indent=4) 220 | await bot.send(ev,"昵称:"+ message +" 在本群的三麻订阅已成功关闭") 221 | else: 222 | await bot.send(ev,"没有找到该昵称在本群的订阅记录哦,请检查后重试") 223 | 224 | @sv.on_prefix("开启三麻订阅") 225 | async def openTriOrder(bot,ev:CQEvent): 226 | nickname = ev.message.extract_plain_text() 227 | if len(nickname) > 15: 228 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 229 | gid = ev["group_id"] 230 | record = localLoad(3) 231 | flag = False 232 | IDdata = await getID(nickname,3) 233 | message = "" 234 | datalist=[] 235 | if IDdata == -1: 236 | await bot.send(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次后重试") 237 | else: 238 | for i in range(0,len(record)): 239 | if int(record[i]["gid"]) == int(gid) and IDdata[0]["id"]==record[i]["id"]: 240 | message = message + IDdata[0]["nickname"] 241 | record[i]["record_on"] = True 242 | flag = True 243 | datalist.append(record[i]) 244 | if flag: 245 | with open(join(path, 'tri_account.json'), 'w', encoding='utf-8') as fp: 246 | json.dump(datalist, fp, indent=4) 247 | await bot.send(ev,"昵称:"+ message +"在本群的三麻订阅已成功开启") 248 | else: 249 | await bot.send(ev,"没有找到该昵称在本群的订阅记录哦,请检查后重试") 250 | 251 | @sv.scheduled_job('interval', minutes=5) 252 | async def Trirecord_scheduled(): 253 | bot = get_bot() 254 | record = localLoad(3) 255 | for i in range(0,len(record)): 256 | playerRecord = await selectRecord(record[i]["id"],3) 257 | if playerRecord == -1: 258 | sv.logger.info("获取" + str(record[i]["id"]) + "的三麻对局数据超时已自动跳过") 259 | continue 260 | compareRecord = json.loads(playerRecord) 261 | sv.logger.info("正在检测更新"+str(record[i]["id"])+"的三麻对局数据") 262 | if int(record[i]["endTime"]) < int(compareRecord[0]["endTime"]): 263 | message = updateData(playerRecord,record[i]["gid"],record[i]["id"],3) 264 | await bot.send_group_msg(group_id=int(record[i]["gid"]),message=message) 265 | 266 | @sv.on_fullmatch("三麻订阅状态") 267 | async def orderSituation(bot,ev): 268 | gid = ev["group_id"] 269 | datalist = [] 270 | message = "" 271 | record = localLoad(3) 272 | for i in range(0,len(record)): 273 | if int(record[i]["gid"]) == int(gid): 274 | datalist.append(record[i]) 275 | if datalist == []: 276 | await bot.finish(ev,"本群还没有雀魂三麻对局的订阅哦~") 277 | else: 278 | message = message + "已查询到群"+str(gid)+"的订阅状态:\n" 279 | for i in range(0,len(datalist)): 280 | data = await selectNickname(datalist[i]["id"],"3") 281 | sv.logger.info("正在获取" + str(datalist[i]["id"]) + "的昵称信息") 282 | if data == -1: 283 | await bot.finish(ev, "获取昵称信息失败,请重试") 284 | else: 285 | message = message + "昵称:" + data + " " 286 | if datalist[i]["record_on"]: 287 | message = message + "开启\n" 288 | else: 289 | message = message + "关闭\n" 290 | await bot.send(ev,message) 291 | 292 | @sv.on_prefix("删除三麻订阅") 293 | async def delTriInfo(bot,ev): 294 | nickname = ev.message.extract_plain_text() 295 | if len(nickname) > 15: 296 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 297 | gid = ev["group_id"] 298 | record = localLoad(3) 299 | flag = False 300 | IDdata = await getID(nickname,3) 301 | datalist = [] 302 | if IDdata == -1: 303 | await bot.send(ev, "没有查询到该角色在金之间以上的对局数据呢~\n请在金之间以上房间对局一次后重试") 304 | else: 305 | for i in range(0, len(record)): 306 | if int(record[i]["gid"]) == int(gid) and IDdata[0]["id"] == record[i]["id"]: 307 | flag = True 308 | continue 309 | else: 310 | datalist.append(record[i]) 311 | if flag: 312 | with open(join(path, 'tri_account.json'), 'w', encoding='utf-8') as fp: 313 | json.dump(datalist, fp, indent=4) 314 | await bot.send(ev, "该昵称在本群的三麻订阅已删除") 315 | else: 316 | await bot.send(ev, "没有找到该昵称在本群的订阅记录哦,请检查后重试") -------------------------------------------------------------------------------- /Subscription/account.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /Subscription/compareData.py: -------------------------------------------------------------------------------- 1 | from .RecordLoader import * 2 | import base64 3 | from PIL import ImageFont,ImageDraw,Image 4 | from io import BytesIO 5 | import json,os 6 | 7 | FILE_PATH = os.path.dirname(os.path.dirname(__file__)) 8 | 9 | class ImgText: 10 | FONTS_PATH = os.path.join(FILE_PATH,'fonts') 11 | FONTS = os.path.join(FONTS_PATH,'msyh1.otf') 12 | font = ImageFont.truetype(FONTS, 14) 13 | def __init__(self, text): 14 | # 预设宽度 可以修改成你需要的图片宽度 15 | self.width = 600 16 | # 文本 17 | self.text = text 18 | # 段落 , 行数, 行高 19 | self.duanluo, self.note_height, self.line_height, self.drow_height = self.split_text() 20 | def get_duanluo(self, text): 21 | txt = Image.new('RGBA', (400, 800), (255, 255, 255, 0)) 22 | draw = ImageDraw.Draw(txt) 23 | # 所有文字的段落 24 | duanluo = "" 25 | # 宽度总和 26 | sum_width = 0 27 | # 几行 28 | line_count = 1 29 | # 行高 30 | line_height = 0 31 | for char in text: 32 | width, height = draw.textsize(char, ImgText.font) 33 | sum_width += width 34 | if sum_width > self.width: # 超过预设宽度就修改段落 以及当前行数 35 | line_count += 1 36 | sum_width = 0 37 | duanluo += '\n' 38 | duanluo += char 39 | line_height = max(height, line_height) 40 | if not duanluo.endswith('\n'): 41 | duanluo += '\n' 42 | return duanluo, line_height, line_count 43 | def split_text(self): 44 | # 按规定宽度分组 45 | max_line_height, total_lines = 0, 0 46 | allText = [] 47 | for text in self.text.split('\n'): 48 | duanluo, line_height, line_count = self.get_duanluo(text) 49 | max_line_height = max(line_height, max_line_height) 50 | total_lines += line_count 51 | allText.append((duanluo, line_count)) 52 | line_height = max_line_height 53 | total_height = total_lines * line_height 54 | drow_height = total_lines * line_height 55 | return allText, total_height, line_height, drow_height 56 | def draw_text(self): 57 | """ 58 | 绘图以及文字 59 | :return: 60 | """ 61 | im = Image.new("RGB", (600, self.drow_height), (255, 255, 255)) 62 | draw = ImageDraw.Draw(im) 63 | # 左上角开始 64 | x, y = 0, 0 65 | for duanluo, line_count in self.duanluo: 66 | draw.text((x, y), duanluo, fill=(0, 0, 0), font=ImgText.font) 67 | y += self.line_height * line_count 68 | bio = BytesIO() 69 | im.save(bio, format='PNG') 70 | base64_str = 'base64://' + base64.b64encode(bio.getvalue()).decode() 71 | mes = f"[CQ:image,file={base64_str}]" 72 | return mes 73 | 74 | 75 | def updateData(record,gid,id,num): 76 | localdata = localLoad(num) 77 | data = json.loads(record) 78 | datalist = [] 79 | message = "" 80 | for i in range(0,len(localdata)): 81 | if data[0]["uuid"] != localdata[i]["uuid"] and gid == localdata[i]["gid"] and localdata[i]["id"] == id: 82 | localdata[i]["uuid"] = data[0]["uuid"] 83 | localdata[i]["endTime"] = data[0]["endTime"] 84 | if localdata[i]["record_on"]: 85 | message = message + processdata(data,num) 86 | datalist.append(localdata[i]) 87 | if num == 4: 88 | with open(join(path,'account.json'),'w',encoding='utf-8') as fp: 89 | json.dump(datalist,fp,indent=4) 90 | else: 91 | with open(join(path,'tri_account.json'),'w',encoding='utf-8') as fp: 92 | json.dump(datalist,fp,indent=4) 93 | return message 94 | 95 | 96 | def processdata(data,num): 97 | message = "本群侦测到新的对局:" 98 | message = message + "\n对局场次:"+ str(judgeRoomLevel(data[0]["modeId"])) 99 | message = message + "\n牌谱ID:" + str(data[0]["uuid"]) + "\n" 100 | for j in range(0, num): 101 | message = message + str(data[0]["players"][j]["nickname"]) + "(" + str(data[0]["players"][j]["score"]) + ") " 102 | message = message + "\n" 103 | message = message + "对局开始时间:" + str(convertTime(data[0]["startTime"])) + " " 104 | message = message + "对局结束时间:" + str(convertTime(data[0]["endTime"])) + " \n" 105 | pic = ImgText(message) 106 | return pic.draw_text() 107 | 108 | def convertTime(datatime): 109 | timeArray = time.localtime(datatime) 110 | Time = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) 111 | return Time 112 | 113 | async def selectNickname(id,num): 114 | localtime = time.time() 115 | urltime = str(int(localtime * 1000)) # 时间戳 116 | if num == "4": 117 | basicurl = baseurl + "/player_stats/" + str(id) + "/1262304000000/" + urltime + "?mode=16.12.9.15.11.8" 118 | else: 119 | basicurl = tribaseurl + "/player_stats/" + str(id) + "/1262304000000/" + urltime + "?mode=22.24.26.21.23.25" 120 | data = await getURL(basicurl) 121 | if data == "error": 122 | return -404 123 | else: 124 | nickname = str(json.loads(data)["nickname"]) 125 | return nickname 126 | 127 | 128 | def judgeRoomLevel(level): 129 | if level == 8: return "金之间 四人东" 130 | elif level == 9: return "金之间 四人南" 131 | elif level == 11: return "玉之间 四人东" 132 | elif level == 12: return "玉之间 四人南" 133 | elif level == 15: return "王座之间 四人东" 134 | elif level == 16: return "王座之间 四人南" 135 | elif level == 21: return "金之间 三人南" 136 | elif level == 22: return "金之间 三人南" 137 | elif level == 23: return "玉之间 三人东" 138 | elif level == 24: return "玉之间 三人南" 139 | elif level == 25: return "王座之间 三人东" 140 | elif level == 26: return "王座之间 三人南" 141 | -------------------------------------------------------------------------------- /Subscription/tri_account.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from hoshino import Service 3 | 4 | sv = Service("雀魂帮助") 5 | 6 | 7 | help_txt = '''这是一个HoshinoBot的雀魂查询相关插件 8 | 本插件数据来源于雀魂牌谱屋:https://amae-koromo.sapk.ch/ 9 | 项目地址:https://github.com/DaiShengSheng/Majsoul_bot 10 | 由于牌谱屋不收录铜之间以及银之间牌谱,故所有数据仅统计2019年11月29日后金场及以上场次的数据 11 | 12 | 查询指令: 13 | 雀魂信息/雀魂查询 昵称:查询该ID的雀魂基本对局数据(包含金场以上所有) 14 | 三麻信息/三麻查询 昵称:查询该ID雀魂三麻的基本对局数据(包含金场以上所有) 15 | 雀魂信息/雀魂查询 (金/金之间/金场/玉/王座) 昵称:查询该ID在金/玉/王座之间的详细数据 16 | 三麻信息/三麻查询 (金/金之间/金场/玉/王座) 昵称:查询该ID在三麻金/玉/王座之间的详细数据 17 | 雀魂牌谱 昵称:查询该ID下最近五场的对局信息 18 | 三麻牌谱 昵称:查询该ID下最近五场的三麻对局信息 19 | 20 | 对局订阅指令: 21 | 雀魂订阅 昵称:订阅该昵称在金之间以上的四麻对局信息 22 | 三麻订阅 昵称:订阅该昵称在金之间以上的三麻对局信息 23 | (取消/关闭)雀魂订阅 昵称:将该昵称在本群的订阅暂时关闭 24 | (取消/关闭)三麻订阅 昵称:将该昵称在本群的三麻订阅暂时关闭 25 | 开启雀魂订阅 昵称:将该昵称在本群的订阅开启 26 | 开启三麻订阅 昵称:将该昵称在本群的三麻订阅开启 27 | 删除雀魂订阅 昵称:将该昵称在本群的订阅删除 28 | 删除三麻订阅 昵称:将该昵称在本群的三麻订阅删除 29 | 雀魂订阅状态:查询本群的雀魂订阅信息的开启状态 30 | 三麻订阅状态:查询本群的雀魂订阅信息的开启状态 31 | 32 | 其他指令: 33 | 雀魂十连:来一发当前群内卡池的十连抽 34 | 切换雀魂卡池 <卡池名称>:切换本群的雀魂卡池(当前up池、辉夜up池、天麻up池、标配池、斗牌传说up池、狂赌up池) 35 | 查看/当前雀魂卡池:查看本群当前生效的雀魂卡池 36 | 麻将猜手牌/开启麻兜:开启一局麻兜小游戏 37 | 结束猜手牌/结束麻兜:强制关闭正在进行的麻兜游戏 38 | 牌理 <手牌>:查询该手牌牌理(m万、s索、p饼、z字牌) 39 | ''' 40 | 41 | @sv.on_fullmatch("雀魂帮助") 42 | async def help(bot, ev): 43 | await bot.send(ev, help_txt) 44 | 45 | -------------------------------------------------------------------------------- /cal_shanten/__init__.py: -------------------------------------------------------------------------------- 1 | from hoshino import Service 2 | from hoshino.typing import HoshinoBot,CQEvent 3 | from .cal_mahjong import * 4 | 5 | sv = Service("麻将牌理") 6 | 7 | @sv.on_prefix('牌理') 8 | async def cal_mahjong(bot, ev: CQEvent): 9 | hands = ev.message.extract_plain_text() 10 | if len(hands) == 0: 11 | await bot.finish(ev, "查询的麻将手牌数量不可为空" , at_sender=True) 12 | return 13 | result = calc_shanten_14(hands) 14 | message = "" 15 | if isinstance(result,str): 16 | await bot.finish(ev, result , at_sender=True) 17 | return 18 | else: 19 | for i in range (0,len(result)): 20 | message = message + result[i] 21 | pic = ImgText(message) 22 | await bot.finish(ev, pic.draw_text(), at_sender=True) 23 | -------------------------------------------------------------------------------- /cal_shanten/cal_mahjong.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | from .utils import * 4 | from .dfs import * 5 | from PIL import ImageFont,ImageDraw,Image 6 | from io import BytesIO 7 | 8 | FILE_PATH = os.path.dirname(os.path.dirname(__file__)) 9 | 10 | class ImgText: 11 | FONTS_PATH = os.path.join(FILE_PATH,'fonts') 12 | FONTS = os.path.join(FONTS_PATH,'msyh1.otf') 13 | font = ImageFont.truetype(FONTS, 14) 14 | def __init__(self, text): 15 | # 预设宽度 可以修改成你需要的图片宽度 16 | self.width = 600 17 | # 文本 18 | self.text = text 19 | # 段落 , 行数, 行高 20 | self.duanluo, self.note_height, self.line_height, self.drow_height = self.split_text() 21 | def get_duanluo(self, text): 22 | txt = Image.new('RGBA', (400, 800), (255, 255, 255, 0)) 23 | draw = ImageDraw.Draw(txt) 24 | # 所有文字的段落 25 | duanluo = "" 26 | # 宽度总和 27 | sum_width = 0 28 | # 几行 29 | line_count = 1 30 | # 行高 31 | line_height = 0 32 | for char in text: 33 | width, height = draw.textsize(char, ImgText.font) 34 | sum_width += width 35 | if sum_width > self.width: # 超过预设宽度就修改段落 以及当前行数 36 | line_count += 1 37 | sum_width = 0 38 | duanluo += '\n' 39 | duanluo += char 40 | line_height = max(height, line_height) 41 | if not duanluo.endswith('\n'): 42 | duanluo += '\n' 43 | return duanluo, line_height, line_count 44 | def split_text(self): 45 | # 按规定宽度分组 46 | max_line_height, total_lines = 0, 0 47 | allText = [] 48 | for text in self.text.split('\n'): 49 | duanluo, line_height, line_count = self.get_duanluo(text) 50 | max_line_height = max(line_height, max_line_height) 51 | total_lines += line_count 52 | allText.append((duanluo, line_count)) 53 | line_height = max_line_height 54 | total_height = total_lines * line_height 55 | drow_height = total_lines * line_height 56 | return allText, total_height, line_height, drow_height 57 | def draw_text(self): 58 | """ 59 | 绘图以及文字 60 | :return: 61 | """ 62 | im = Image.new("RGB", (600, self.drow_height), (255, 255, 255)) 63 | draw = ImageDraw.Draw(im) 64 | # 左上角开始 65 | x, y = 0, 0 66 | for duanluo, line_count in self.duanluo: 67 | draw.text((x, y), duanluo, fill=(0, 0, 0), font=ImgText.font) 68 | y += self.line_height * line_count 69 | bio = BytesIO() 70 | im.save(bio, format='PNG') 71 | base64_str = 'base64://' + base64.b64encode(bio.getvalue()).decode() 72 | mes = f"[CQ:image,file={base64_str}]" 73 | return mes 74 | 75 | def calc_shanten_13(hc=None, hc_list=None): 76 | if hc_list: 77 | hc = hc_list 78 | else: 79 | hc = convert_hc_to_list(hc) 80 | if sum(hc) != 13: 81 | raise ValueError("请传入13位手牌.") 82 | m = get_mianzi(hc) 83 | # 没有面子拆解的情况 传入空数组 84 | if not m: 85 | m = [[]] 86 | # 最大8向听 87 | xt_list = [ 88 | [], 89 | [], 90 | [], 91 | [], 92 | [], 93 | [], 94 | [], 95 | [], 96 | ] 97 | for x in m: 98 | # 面子数量 99 | mianzi_count = len(x) 100 | thc = get_trimed_hc(hc.copy(), x) 101 | dazi_list = get_dazi(thc) 102 | da_list_xt_min = 999 103 | for dazi in dazi_list: 104 | # 是否有雀头 105 | if_quetou = 0 106 | for y in dazi: 107 | if y[1] > 0: 108 | if_quetou = 1 109 | dazi_count = len(dazi) 110 | xt = calc_xiangting(mianzi_count, dazi_count, if_quetou) 111 | if xt <= da_list_xt_min: 112 | tthc = get_trimed_dazi(thc.copy(), dazi) 113 | # 孤张 114 | guzhang_list = get_guzhang(tthc) 115 | # 进张 116 | tenpai = get_tenpai_from_dazi(dazi, xt) 117 | 118 | # TODO 或许有更多情况 119 | # 向听为0 120 | if xt == 0: 121 | # 无搭子 即单吊 122 | if not dazi: 123 | tenpai += guzhang_list 124 | # 向听数为1 125 | if xt == 1: 126 | if dazi_count == 1: 127 | if if_quetou: 128 | ga = get_guzhang_around(guzhang_list) 129 | tenpai += ga 130 | tenpai += guzhang_list 131 | else: 132 | tenpai += guzhang_list 133 | if dazi_count == 2: 134 | # 搭子自身可以减少向听 135 | for d in dazi: 136 | i = d[0] 137 | if d[1] > 0: 138 | tenpai.append(i) 139 | elif d[2] > 0: 140 | tenpai.append(i) 141 | tenpai.append(i + 1) 142 | elif d[3] > 0: 143 | tenpai.append(i) 144 | tenpai.append(i + 2) 145 | # 向听为2以上 146 | if xt >= 2: 147 | if mianzi_count + dazi_count < 5: 148 | # 4搭子0雀头, 不需要新的搭子(顺子型) 149 | if mianzi_count + dazi_count == 4 and not if_quetou: 150 | less_than5 = get_md_less_than5(tthc,0) 151 | tenpai += less_than5 152 | else: 153 | less_than5 = get_md_less_than5(tthc) 154 | tenpai += less_than5 155 | #pass 156 | elif mianzi_count + dazi_count >=5: 157 | # 超载时 搭子自身可以化为雀头 孤张也可 158 | if not if_quetou: 159 | for d in dazi: 160 | i = d[0] 161 | if d[1] > 0: 162 | tenpai.append(i) 163 | elif d[2] > 0: 164 | tenpai.append(i) 165 | tenpai.append(i + 1) 166 | elif d[3] > 0: 167 | tenpai.append(i) 168 | tenpai.append(i + 2) 169 | tenpai += guzhang_list 170 | tenpai = list(set(tenpai)) 171 | tenpai.sort() 172 | xt_list[xt] += tenpai 173 | for y in range(len(xt_list)): 174 | if xt_list[y]: 175 | # (向听数, 进张列表) 176 | return (y, list(set(xt_list[y]))) 177 | 178 | 179 | # 一般形牌理分析 180 | def calc_shanten_14(hc: str): 181 | result_list = [] 182 | result_list.append("PS:本插件牌理暂不考虑七对子与国士无双听牌\n当前手牌:" + hc + "\n") 183 | hc = convert_hc_to_list(hc) 184 | if sum(hc) != 14: 185 | return "手牌数量存在问题,请输入14张手牌或检查输入牌型是否正常。" 186 | for amount in hc: 187 | if amount >4: 188 | return "手牌枚数异常,请检查输入牌型是否存在问题。" 189 | xt_list = [] 190 | for x in range(len(hc)): 191 | if hc[x] > 0: 192 | # 变位 193 | hc[x] -= 1 194 | xt = calc_shanten_13(hc_list=hc) 195 | if xt: 196 | xt_list.append([x, xt]) 197 | # 复位 198 | hc[x] += 1 199 | # 最小向听数 200 | if xt_list == []: 201 | result_list.append("手牌状态:十三不搭\n\n") 202 | result_list.append("依据场况切手牌中任意一张牌即可。\n") 203 | return result_list 204 | xt_min = min([x[1][0] for x in xt_list]) 205 | if xt_min == 0: 206 | result_list.append("手牌状态:聴牌\n\n") 207 | else: 208 | result_list.append("手牌状态:" + f"{xt_min}向听\n\n") 209 | card_advice_list = [] 210 | for xxt in xt_list: 211 | xt = xxt[1] 212 | if xt[0] == xt_min: 213 | xt[1].sort() 214 | msum = calc_tenpai_sum(hc, xt[1]) 215 | card_advice_list.append([xxt[0], xt[1], msum]) 216 | card_advice_list.sort(key=lambda x: x[2], reverse=1) 217 | for x in card_advice_list: 218 | choice = "打" + convert_num_to_card(x[0]) +" 可摸进:[" 219 | for i in range (0,len(x[1])): 220 | choice = choice + convert_num_to_card(x[1][i]) 221 | if i != len(x[1])-1: 222 | choice = choice + "、" 223 | choice = choice + "] 共" + str(x[2]) +"枚\n" 224 | result_list.append(choice) 225 | if not xt: 226 | return "出现错误,请检查错误日志。" 227 | return result_list 228 | -------------------------------------------------------------------------------- /cal_shanten/dfs.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | # 检查牌是否同一区间 4 | def check_same_area(a, b): 5 | if a < 9 and b < 9: 6 | return True 7 | elif 9 <= a < 18 and 9 <= b < 18: 8 | return True 9 | elif 18 <= a < 27 and 18 <= b < 27: 10 | return True 11 | elif a >= 27 and b >= 27: 12 | return True 13 | 14 | 15 | # 获取面子的组合形式 16 | def get_mianzi(single_color_cards): 17 | single_color_cards = single_color_cards # [1,0,1,1,0,0,2,1,0] 18 | depth = math.floor(sum(single_color_cards) / 3) 19 | available = [[0, 0] for x in range(len(single_color_cards))] 20 | myava_list = [] 21 | 22 | # 顺子 23 | for x in range(len(single_color_cards) - 2): 24 | # 字牌无顺子 25 | if x < 27: 26 | if all([single_color_cards[x], single_color_cards[x + 1], single_color_cards[x + 2]]) and math.floor(x / 9) == math.floor((x + 1) / 9) == math.floor((x + 2) / 9): 27 | available[x][0] = 1 28 | # 刻子 29 | for x in range(len(single_color_cards)): 30 | if single_color_cards[x] >= 3: 31 | available[x][1] = 1 32 | 33 | myava_list = [(x, available[x][0], 0) for x in range(len(single_color_cards)) if available[x][0] > 0] 34 | myava_list += [(x, 0, available[x][1]) for x in range(len(single_color_cards)) if available[x][1] > 0] 35 | 36 | # myava_list = [(2, 1, 0), (3, 1, 0), (4, 1, 0), (4, 0, 1), (5, 0, 1)] 37 | m = [] 38 | mlist = [] 39 | stack = [] 40 | stack.append(single_color_cards) 41 | 42 | def dfs(mlist, d): # d:深度 43 | # depth 深度 44 | if len(mlist) < depth: 45 | continue_count = 0 46 | for x in myava_list: 47 | stack_diff = len(stack) - d - 1 48 | for y in range(stack_diff): 49 | stack.pop() 50 | hc = stack[d].copy() 51 | diff = len(mlist) - d 52 | for y in range(diff): 53 | mlist.pop() 54 | origin_index = x[0] 55 | if x[1] > 0: 56 | # 变位 57 | hc[origin_index] -= 1 58 | hc[origin_index + 1] -= 1 59 | hc[origin_index + 2] -= 1 60 | if hc[origin_index] >= 0 and hc[origin_index + 1] >= 0 and hc[origin_index + 2] >= 0: 61 | mlist.append(x) 62 | else: 63 | # 复位 64 | hc[origin_index] += 1 65 | hc[origin_index + 1] += 1 66 | hc[origin_index + 2] += 1 67 | continue_count += 1 68 | # 连续len(myava_list)次continue 为终点 69 | if continue_count >= len(myava_list): 70 | mlist_copy = mlist.copy() 71 | mlist_copy.sort() 72 | if mlist_copy not in m: 73 | m.append(mlist_copy) 74 | continue 75 | elif x[2] > 0: 76 | # 变位 77 | hc[origin_index] -= 3 78 | if hc[origin_index] >= 0: 79 | mlist.append(x) 80 | else: 81 | # 复位 82 | hc[origin_index] += 3 83 | 84 | continue_count += 1 85 | # 连续len(myava_list)次continue 为终点 86 | if continue_count >= len(myava_list): 87 | mlist_copy = mlist.copy() 88 | mlist_copy.sort() 89 | if mlist_copy not in m: 90 | m.append(mlist_copy) 91 | continue 92 | stack.append(hc) 93 | dfs(mlist, d + 1) 94 | else: 95 | mlist_copy = mlist.copy() 96 | mlist_copy.sort() 97 | if mlist_copy not in m: 98 | m.append(mlist_copy) 99 | 100 | dfs(mlist, 0) 101 | # 面子回退 应对 1345型拆解 -> 13 45 or 1 345 102 | mianzi_count_max = 0 103 | for x in m: 104 | mianzi_count = len(x) 105 | if mianzi_count > mianzi_count_max: 106 | mianzi_count_max = mianzi_count 107 | for x in m: 108 | if len(x) == mianzi_count_max: 109 | for y in range(len(x)): 110 | z = x[0:y] + x[y + 1 :] 111 | z.sort() 112 | if z not in m: 113 | m.append(z) 114 | return m 115 | 116 | 117 | # 获取搭子的组合形式 118 | def get_dazi(single_color_cards): 119 | single_color_cards = single_color_cards 120 | depth = math.floor(sum(single_color_cards) / 2) 121 | available = [[0, 0, 0] for x in range(len(single_color_cards))] 122 | myava_list = [] 123 | 124 | # [2] 125 | for x in range(len(single_color_cards)): 126 | if single_color_cards[x] >= 2: 127 | available[x][0] = 1 128 | 129 | # [11] 130 | for x in range(len(single_color_cards) - 1): 131 | # 字牌无顺子 132 | if x < 27: 133 | if all([single_color_cards[x], single_color_cards[x + 1]]) and check_same_area(x, x + 1): 134 | available[x][1] = 1 135 | 136 | # [101] 137 | for x in range(len(single_color_cards) - 2): 138 | # 字牌无顺子 139 | if x < 27: 140 | if all([single_color_cards[x], single_color_cards[x + 2]]) and check_same_area(x, x + 2): 141 | available[x][2] = 1 142 | myava_list = [(x, available[x][0], 0, 0) for x in range(len(single_color_cards)) if available[x][0] > 0] 143 | myava_list += [(x, 0, available[x][1], 0) for x in range(len(single_color_cards)) if available[x][1] > 0] 144 | myava_list += [(x, 0, 0, available[x][2]) for x in range(len(single_color_cards)) if available[x][2] > 0] 145 | m = [] 146 | d = 0 147 | stack = [] 148 | stack.append(single_color_cards) 149 | mlist = [] 150 | 151 | def dfs(mlist, d): 152 | # depth 深度 153 | if len(mlist) < depth: 154 | continue_count = 0 155 | for x in myava_list: 156 | stack_diff = len(stack) - d - 1 157 | for y in range(stack_diff): 158 | stack.pop() 159 | hc = stack[d].copy() 160 | diff = len(mlist) - d 161 | for y in range(diff): 162 | mlist.pop() 163 | origin_index = x[0] 164 | # [2] 165 | if x[1] > 0: 166 | # 变位 167 | hc[origin_index] -= 2 168 | if hc[origin_index] >= 0: 169 | mlist.append(x) 170 | else: 171 | # 复位 172 | hc[origin_index] += 2 173 | 174 | continue_count += 1 175 | # 连续len(myava_list)次continue 为终点 176 | if continue_count >= len(myava_list): 177 | mlist_copy = mlist.copy() 178 | mlist_copy.sort() 179 | if mlist_copy not in m: 180 | m.append(mlist_copy) 181 | continue 182 | # [11] 183 | elif x[2] > 0: 184 | # 变位 185 | hc[origin_index] -= 1 186 | hc[origin_index + 1] -= 1 187 | if hc[origin_index] >= 0 and hc[origin_index + 1] >= 0: 188 | mlist.append(x) 189 | else: 190 | # 复位 191 | hc[origin_index] += 1 192 | hc[origin_index + 1] += 1 193 | 194 | continue_count += 1 195 | # 连续len(myava_list)次continue 为终点 196 | if continue_count >= len(myava_list): 197 | mlist_copy = mlist.copy() 198 | mlist_copy.sort() 199 | if mlist_copy not in m: 200 | m.append(mlist_copy) 201 | continue 202 | elif x[3] > 0: 203 | # 变位 204 | hc[origin_index] -= 1 205 | hc[origin_index + 2] -= 1 206 | if hc[origin_index] >= 0 and hc[origin_index + 2] >= 0: 207 | mlist.append(x) 208 | else: 209 | # 复位 210 | hc[origin_index] += 1 211 | hc[origin_index + 2] += 1 212 | continue_count += 1 213 | # 连续len(myava_list)次continue 为终点 214 | if continue_count >= len(myava_list): 215 | mlist_copy = mlist.copy() 216 | mlist_copy.sort() 217 | if mlist_copy not in m: 218 | m.append(mlist_copy) 219 | continue 220 | stack.append(hc) 221 | dfs(mlist, d + 1) 222 | else: 223 | mlist_copy = mlist.copy() 224 | mlist_copy.sort() 225 | if mlist_copy not in m: 226 | m.append(mlist_copy) 227 | 228 | dfs(mlist, 0) 229 | 230 | return m 231 | -------------------------------------------------------------------------------- /cal_shanten/utils.py: -------------------------------------------------------------------------------- 1 | import re, math 2 | 3 | # 指定数量的顺子在三花色中组合 4 | def compose_gen_sz(sz) -> list: 5 | mycompose = [] 6 | 7 | def myrecursion(fr=None, br=None): 8 | if fr is not None and br is not None: 9 | mycompose.append([sz - fr, br, fr - br]) 10 | return 11 | else: 12 | for k in range(0, fr + 1): 13 | myrecursion(fr, k) 14 | 15 | for x in range(sz + 1): 16 | fr = sz - x 17 | myrecursion(x) 18 | return mycompose 19 | 20 | 21 | # 指定数量刻子在三花色及字牌中组合 22 | def compose_gen_kz(kz) -> list: 23 | mycompose = [] 24 | 25 | def myrecursion(fr=None, br=None, cr=None): 26 | if fr is not None and br is not None and cr is not None: 27 | mycompose.append([kz - fr, br, cr, fr - br - cr]) 28 | return 29 | elif fr is not None and br is not None: 30 | for k in range(0, fr - br + 1): 31 | myrecursion(fr, br, k) 32 | else: 33 | for k in range(0, fr + 1): 34 | myrecursion(fr, k) 35 | 36 | for x in range(kz + 1): 37 | fr = kz - x 38 | myrecursion(x) 39 | return mycompose 40 | 41 | 42 | # 生产7位花色的刻子 字牌 43 | def produce_kz_zipai(index) -> list: 44 | mykz = [0] * 7 45 | mykz[index] += 3 46 | return mykz 47 | 48 | 49 | # 生产9位花色的刻子 50 | def produce_kz(index) -> list: 51 | mykz = [0] * 9 52 | mykz[index] += 3 53 | return mykz 54 | 55 | 56 | # 生产9位花色的顺子 57 | def produce_sz(index) -> list: 58 | mysz = [0] * 9 59 | mysz[index] += 1 60 | mysz[index + 1] += 1 61 | mysz[index + 2] += 1 62 | return mysz 63 | 64 | 65 | # 对手牌进行编码 66 | def encode_hand_cards(hc: list) -> str: 67 | def encode_hc(ehc, zipai=False): 68 | if zipai: 69 | mhc = "0".join(str(y) for y in ehc) 70 | else: 71 | mhc = "".join(str(y) for y in ehc) 72 | mhc = mhc.strip("0") 73 | mhc = re.sub(r"0{2,}", "0", mhc) 74 | return mhc 75 | 76 | hc_wan = hc[0:9] 77 | hc_tiao = hc[9:18] 78 | hc_tong = hc[18:27] 79 | hc_zi = hc[27:34] 80 | 81 | ehc_to_join = [] 82 | for x in [ 83 | encode_hc(hc_wan), 84 | encode_hc(hc_tiao), 85 | encode_hc(hc_tong), 86 | encode_hc(hc_zi, zipai=True), 87 | ]: 88 | if x: 89 | ehc_to_join.append(x) 90 | return "0".join(ehc_to_join) 91 | 92 | 93 | # 将字符串手牌转为列表 94 | def convert_hc_to_list(hc: str) -> list: 95 | if not hc: 96 | raise ValueError 97 | # 赤宝牌处理 98 | hc = hc.replace("0", "5") 99 | hc_list = [0] * 34 100 | pattern = re.compile(r"\d+[mspz]") 101 | result = pattern.findall(hc) 102 | for x in result: 103 | if x[-1] == "m": 104 | for y in x[:-1]: 105 | hc_list[int(y) - 1] += 1 106 | if x[-1] == "s": 107 | for y in x[:-1]: 108 | hc_list[int(y) - 1 + 9] += 1 109 | if x[-1] == "p": 110 | for y in x[:-1]: 111 | hc_list[int(y) - 1 + 18] += 1 112 | if x[-1] == "z": 113 | for y in x[:-1]: 114 | hc_list[int(y) - 1 + 27] += 1 115 | return hc_list 116 | 117 | 118 | # 不考虑花色边界和牌数 编码手牌 119 | def encode_arbitrary_cards(hc: list): 120 | def encode_hc(ehc, if_zi=False): 121 | if if_zi: 122 | mhc = "0".join(str(y) for y in ehc) 123 | else: 124 | mhc = "".join(str(y) for y in ehc) 125 | mhc = mhc.strip("0") 126 | mhc = re.sub(r"0{2,}", "0", mhc) 127 | 128 | return mhc 129 | 130 | return encode_hc(hc) 131 | 132 | 133 | # 根据数字返回牌名 134 | def convert_num_to_card(num: int): 135 | mcard = None 136 | if num < 9: 137 | mcard = str(num + 1) + "万" 138 | elif 9 <= num < 18: 139 | mcard = str(num - 9 + 1) + "条" 140 | elif 18 <= num < 27: 141 | mcard = str(num - 18 + 1) + "筒" 142 | else: 143 | mcard = ["东", "南", "西", "北", "白", "发", "中"][num - 27] 144 | return mcard 145 | 146 | 147 | # 从手牌中减去面子牌 148 | def get_trimed_hc(hc, mianzi): 149 | for x in mianzi: 150 | i = x[0] 151 | if x[1] > 0: 152 | hc[i] -= 1 153 | hc[i + 1] -= 1 154 | hc[i + 2] -= 1 155 | elif x[2] > 0: 156 | hc[i] -= 3 157 | return hc 158 | 159 | 160 | # 从手牌中减去搭子牌 161 | def get_trimed_dazi(hc, dazi): 162 | for x in dazi: 163 | i = x[0] 164 | if x[1] > 0: 165 | hc[i] -= 2 166 | elif x[2] > 0: 167 | hc[i] -= 1 168 | hc[i + 1] -= 1 169 | elif x[3] > 0: 170 | hc[i] -= 1 171 | hc[i + 2] -= 1 172 | return hc 173 | 174 | 175 | # 孤张获取 176 | def get_guzhang(hc): 177 | guzhang_list = [] 178 | for x in range(len(hc)): 179 | if hc[x] == 1: 180 | guzhang_list.append(x) 181 | return guzhang_list 182 | 183 | 184 | # 获取孤张附近能组成搭子的牌 185 | def get_guzhang_around(guzhang_list: list): 186 | g = [] 187 | for x in guzhang_list: 188 | if x < 27: 189 | for y in [x - 2, x - 1, x + 1, x + 2]: 190 | if y >= 0 and math.floor(y / 9) == math.floor(x / 9): 191 | g.append(y) 192 | return g 193 | 194 | 195 | # m+d < 5时 减少向听数的进张 196 | def get_md_less_than5(hc, new_dazi = 1): 197 | guzhang_list = [] 198 | for x in range(len(hc)): 199 | if hc[x] == 1: 200 | guzhang_list.append(x) 201 | if x < 27 and new_dazi: 202 | for y in [x - 2, x - 1, x + 1, x + 2]: 203 | if y >= 0 and math.floor(y / 9) == math.floor(x / 9): 204 | guzhang_list.append(y) 205 | return guzhang_list 206 | 207 | 208 | # 根据搭子和当前向听数 返回能够减少向听数的牌 209 | def get_tenpai_from_dazi(dazi, xt): 210 | # dazi = [(2, 1, 0, 0), (18, 0, 0, 1)] 211 | tenpai = [] 212 | # 已经听牌的情况 213 | if xt == 0: 214 | if len(dazi) == 2: 215 | if dazi[0][1] > 0 and dazi[1][1] > 0: 216 | tenpai.append(dazi[0][0]) 217 | tenpai.append(dazi[1][0]) 218 | else: 219 | for x in dazi: 220 | index = x[0] 221 | # [11] 222 | if x[2] > 0: 223 | if index in [0, 9, 18]: 224 | tenpai.append(index + 2) 225 | elif index in [7, 16, 25]: 226 | tenpai.append(index - 1) 227 | else: 228 | tenpai.append(index - 1) 229 | tenpai.append(index + 2) 230 | # [101] 231 | if x[3] > 0: 232 | tenpai.append(index + 1) 233 | return tenpai 234 | # 1向听及以上 235 | for x in dazi: 236 | index = x[0] 237 | # [2] 238 | if x[1] > 0: 239 | tenpai.append(x[0]) 240 | # [11] 241 | if x[2] > 0: 242 | if index in [0, 9, 18]: 243 | tenpai.append(index + 2) 244 | elif index in [7, 16, 25]: 245 | tenpai.append(index - 1) 246 | else: 247 | tenpai.append(index - 1) 248 | tenpai.append(index + 2) 249 | # [101] 250 | if x[3] > 0: 251 | tenpai.append(index + 1) 252 | return tenpai 253 | 254 | 255 | # 向听计算公式 256 | def calc_xiangting(m, d, if_quetou): 257 | if m + d <= 5: 258 | c = 0 259 | else: 260 | c = m + d - 5 261 | if m + d <= 4: 262 | q = 1 263 | else: 264 | if if_quetou: 265 | q = 1 266 | else: 267 | q = 0 268 | x = 9 - 2 * m - d + c - q 269 | return x 270 | 271 | 272 | # 计算枚数 273 | def calc_tenpai_sum(hc: list, tenpai: list): 274 | msum = 0 275 | for x in tenpai: 276 | msum += 4 - hc[x] 277 | return msum 278 | -------------------------------------------------------------------------------- /fonts/msyh1.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/fonts/msyh1.otf -------------------------------------------------------------------------------- /gacha/__init__.py: -------------------------------------------------------------------------------- 1 | from hoshino import Service, priv 2 | from nonebot import MessageSegment 3 | from hoshino.util import DailyNumberLimiter 4 | from hoshino.typing import HoshinoBot,CQEvent 5 | from .gacha import * 6 | 7 | sv = Service("雀魂抽卡") 8 | daily_limiter_10 = DailyNumberLimiter(15) 9 | 10 | 11 | 12 | @sv.on_fullmatch('雀魂十连') 13 | async def majsoul_gacha(bot, ev: CQEvent): 14 | userid = ev['user_id'] 15 | if not daily_limiter_10.check(userid): 16 | await bot.send(ev, '今天已经抽了很多次啦,明天再来吧~') 17 | return 18 | img = run_gacha(ev["group_id"]) 19 | daily_limiter_10.increase(userid) 20 | await bot.send(ev, MessageSegment.image(img), at_sender=True) 21 | 22 | @sv.on_prefix('切换雀魂卡池') 23 | async def change_gacha(bot, ev: CQEvent): 24 | user_input = ev.message.extract_plain_text() 25 | poolname = get_pool_id(user_input) 26 | if poolname == None: 27 | await bot.finish(ev, "没有找到该名称的卡池,请查看输入的卡池名称是否正确,当前支持的卡池有:" 28 | +"当前up池、辉夜up池、天麻up池1、天麻up池2、标配池、斗牌传说up池、狂赌up池\n(请输入 切换雀魂卡池 卡池名称 进行切换)", at_sender=True) 29 | 30 | group_id = ev["group_id"] 31 | group_pool = group_pool_loader() 32 | group_pool_list = [] 33 | check_flag = 0 34 | 35 | for i in range(0,len(group_pool)): 36 | if group_pool[i]["gid"] == str(group_id): 37 | check_flag = 1 38 | binds = { 39 | "gid": str(group_id), 40 | "poolname": poolname 41 | } 42 | group_pool_list.append(binds) 43 | else: 44 | group_pool_list.append(group_pool[i]) 45 | if check_flag == 0: 46 | binds = { 47 | "gid": str(group_id), 48 | "poolname": poolname 49 | } 50 | group_pool_list.append(binds) 51 | 52 | with open(join(path, 'group_pool.json'), 'w', encoding='utf-8') as fp: 53 | json.dump(group_pool_list, fp, indent=4) 54 | await bot.finish(ev, "已成功将本群卡池切换到:" + get_pool_name(poolname), at_sender=True) 55 | 56 | @sv.on_fullmatch(('查看雀魂卡池','当前雀魂卡池')) 57 | async def view_gacha(bot, ev: CQEvent): 58 | group_id = ev["group_id"] 59 | group_pool = group_pool_loader() 60 | group_pool_list = [] 61 | check_flag = 0 62 | 63 | for i in range(0, len(group_pool)): 64 | if group_pool[i]["gid"] == str(group_id): 65 | check_flag = 1 66 | await bot.finish(ev, "本群启用的雀魂卡池为:" + get_pool_name(group_pool[i]["poolname"]), at_sender=True) 67 | group_pool_list.append(group_pool[i]) 68 | if check_flag == 0: 69 | binds = { 70 | "gid": str(group_id), 71 | "poolname": "up" 72 | } 73 | group_pool_list.append(binds) 74 | with open(join(path, 'group_pool.json'), 'w', encoding='utf-8') as fp: 75 | json.dump(group_pool_list, fp, indent=4) 76 | await bot.finish(ev, "本群启用的雀魂卡池为:当前up池", at_sender=True) 77 | -------------------------------------------------------------------------------- /gacha/gacha.json: -------------------------------------------------------------------------------- 1 | { 2 | "up":["竹井久","新子憧","园城寺怜","福路美穗子"], 3 | "normal":["藤田佳奈","三上千织","相原舞","抚子","八木唯","九条璃雨","泽尼娅","北见纱和子", 4 | "卡维","莎拉","二之宫花","白石奈奈","小鸟游雏田","五十岚阳菜","凉宫杏树","雏桃", 5 | "藤本绮罗","辉夜姬","艾丽莎","寺崎千穗理","福姬","七海礼奈","姬川响","森川绫子", 6 | "小野寺七羽","柚"], 7 | "saki1": ["原村和","天江衣","宫永咲","宫永照"], 8 | "saki2": ["竹井久","新子憧","园城寺怜","福路美穗子"], 9 | "douhun": ["鹫巢岩","赤木茂"], 10 | "kuangdu" : ["蛇喰梦子","早乙女芽亚里","桃喰绮罗莉","生志摩妄"], 11 | "huiye" : ["白银御行","四宫辉夜","白银圭","早坂爱"], 12 | "purple_gift" : ["02-香喷喷曲奇.jpg","05-次世代游戏机.jpg","08-经典名画.jpg","11-82年的拉菲.jpg", 13 | "14-海洋之心.jpg","17-熊公仔XXL.jpg","20-精美同人志.jpg","23-华丽的小裙子.jpg"] 14 | } 15 | -------------------------------------------------------------------------------- /gacha/gacha.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname,join 2 | from PIL import Image 3 | from io import BytesIO 4 | import base64 5 | import os 6 | import random 7 | import json 8 | 9 | 10 | path = dirname(__file__) 11 | abspath = dirname(path) 12 | 13 | def gacha_loader(): 14 | with open(join(path,"gacha.json"), encoding='utf-8') as fp: 15 | data = json.load(fp) 16 | return data 17 | 18 | def run_gacha(group_id): 19 | result = [] 20 | pool = gacha_loader() 21 | group_pool = group_pool_loader() 22 | group_pool_list = [] 23 | purple_gift = pool["purple_gift"] 24 | purple_flag = 0 25 | poolname = None 26 | 27 | #定位该群卡池 28 | for i in range(0,len(group_pool)): 29 | if group_pool[i]["gid"] == str(group_id): 30 | poolname = group_pool[i]["poolname"] 31 | else: 32 | group_pool_list.append(group_pool[i]) 33 | if poolname == None: 34 | poolname = "up" 35 | binds = { 36 | "gid": str(group_id), 37 | "poolname": "up" 38 | } 39 | group_pool_list.append(binds) 40 | with open(join(path, 'group_pool.json'), 'w', encoding='utf-8') as fp: 41 | json.dump(group_pool_list, fp, indent=4) 42 | 43 | for i in range (0,10): 44 | result.append(single_pull(pool,poolname)) 45 | if result[i][0] < 80 and (result[i][1] not in purple_gift): 46 | purple_flag = purple_flag + 1 47 | if purple_flag == 10 and result[9][0] <= 95: 48 | result[9][0] = 1 49 | result[9][1] = purple_gift[random.randint(0, len(purple_gift) - 1)] 50 | return concat_images(result,poolname) 51 | 52 | 53 | def single_pull(pool,pool_name): 54 | up_pool = [] 55 | for i in range (0,len(pool[pool_name])): 56 | up_pool.append(pool[pool_name][i] + ".png") 57 | normal_pool = [] 58 | for i in range (0,len(pool["normal"])): 59 | normal_pool.append(pool["normal"][i] + ".png") 60 | 61 | gift_list = file_loader("gift") # 读取礼物 62 | decoration = file_loader("decoration") # 读取特效装扮 63 | person = file_loader("person") # 读取人物 64 | if pool_name != "normal" and pool_name != "up" and pool_name != "kuangdu" and pool_name != "douhun": 65 | tmp_list = [] 66 | for filename in os.walk(abspath + "/resources/decoration/" + pool_name + "/"): 67 | tmp_list.append(filename) 68 | decoration = decoration + tmp_list[0][2] 69 | if pool_name == "up": 70 | tmp_list = [] 71 | for filename in os.walk(abspath + "/resources/decoration/saki2/"): 72 | tmp_list.append(filename) 73 | decoration = decoration + tmp_list[0][2] 74 | objint = random.randint(1,100) 75 | if objint < 80: 76 | prop = gift_list[random.randint(0, len(gift_list)-1)] 77 | elif objint >= 80 and objint <= 95: 78 | prop = decoration[random.randint(0, len(decoration)-1)] 79 | else: 80 | objint_person = random.randint(1,100) 81 | if objint_person <=51: 82 | prop = up_pool[random.randint(0, len(up_pool)-1)] 83 | else: 84 | prop = normal_pool[random.randint(0, len(normal_pool) - 1)] 85 | data = [] 86 | data.append(objint) 87 | data.append(prop) 88 | return data 89 | 90 | def file_loader(file_type): 91 | filelist = [] 92 | for filename in os.walk(abspath + "/resources/" + file_type): 93 | filelist.append(filename) 94 | return filelist[0][2] 95 | 96 | def concat_images(image,pool_name): 97 | if pool_name == "up": 98 | pool_name = "saki2" 99 | COL = 5 # 指定拼接图片的列数 100 | ROW = 2 # 指定拼接图片的行数 101 | UNIT_HEIGHT_SIZE = 266 # 图片高度 102 | UNIT_WIDTH_SIZE = 266 # 图片宽度 103 | image_names = [] 104 | for tmp_image in image: 105 | image_names.append(tmp_image[1]) 106 | image_files = [] 107 | for index in range(COL * ROW): 108 | if image[index][0] < 80: 109 | imgpath = abspath + "/resources/gift/" 110 | elif image[index][0] > 95: 111 | imgpath = abspath + "/resources/person/" 112 | else: 113 | imgpath = abspath + "/resources/decoration/" 114 | if os.path.exists(imgpath + image_names[index]) == False: 115 | imgpath = abspath + "/resources/decoration/" + pool_name + "/" 116 | img = Image.open(imgpath + image_names[index]) 117 | img = img.resize((256, 256), Image.ANTIALIAS) 118 | image_files.append(img) # 读取所有用于拼接的图片 119 | 120 | target = Image.new('RGB', (UNIT_WIDTH_SIZE * COL+10, UNIT_HEIGHT_SIZE * ROW+10),(255,255,255)) # 创建成品图的画布 121 | for row in range(ROW): 122 | for col in range(COL): 123 | target.paste(image_files[COL * row + col], (10 + UNIT_WIDTH_SIZE * col, 10 + UNIT_HEIGHT_SIZE * row)) 124 | return pil2b64(target) 125 | 126 | def pil2b64(data): 127 | bio = BytesIO() 128 | data = data.convert("RGB") 129 | data.save(bio, format='JPEG', quality=75) 130 | base64_str = base64.b64encode(bio.getvalue()).decode() 131 | return 'base64://' + base64_str 132 | 133 | def group_pool_loader(): 134 | with open(join(path,'group_pool.json'),encoding='utf-8') as fp: 135 | data = json.load(fp) 136 | return data 137 | 138 | def get_pool_id(name): 139 | if name == "up" or name == "当前up池": return "up" 140 | elif "辉夜" in name or name == "辉夜up池": return "huiye" 141 | elif name == "天麻up池1": return "saki1" 142 | elif name == "天麻up池2": return "saki2" 143 | elif "标配" in name or name == "标配池": return "normal" 144 | elif "斗牌" in name or name == "斗牌传说up池": return "douhun" 145 | elif "狂赌" in name or name == "狂赌up池": return "kuangdu" 146 | else : return None 147 | 148 | def get_pool_name(id): 149 | if id == "up": return "当前up池" 150 | elif id == "huiye": return "辉夜up池" 151 | elif id == "saki1": return "天麻up池1" 152 | elif id == "saki2": return "天麻up池2" 153 | elif id == "normal": return "标配池" 154 | elif id == "douhun": return "斗牌传说up池" 155 | elif id == "kuangdu": return "狂赌up池" -------------------------------------------------------------------------------- /gacha/group_pool.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /mahjong_handle/__init__.py: -------------------------------------------------------------------------------- 1 | from hoshino import Service, priv 2 | from nonebot import MessageSegment 3 | from .handler import HandGuess 4 | from .utils import get_path 5 | 6 | sv_help = """ 7 | [麻将猜手牌/开启麻兜] 开始一轮猜测当前手牌游戏 8 | """.strip() 9 | 10 | sv = Service( 11 | name="麻兜", # 功能名 12 | use_priv=priv.NORMAL, # 使用权限 13 | manage_priv=priv.ADMIN, # 管理权限 14 | visible=True, # 可见性 15 | enable_on_default=True, # 默认启用 16 | bundle="娱乐", # 分组归类 17 | help_=sv_help, # 帮助说明 18 | ) 19 | 20 | 21 | @sv.on_fullmatch(("麻将猜手牌","开启麻兜")) 22 | async def main(bot, ev): 23 | 24 | hg = HandGuess(ev["user_id"], ev["group_id"]) 25 | res = await hg.start() 26 | if res["error"]: 27 | await bot.finish(ev, res["msg"]) 28 | await bot.send(ev, f"开始一轮猜手牌, 每个人有{hg.MAX_GUESS}次机会") 29 | 30 | rule_path = get_path("assets", "rule.png") 31 | await bot.send(ev, MessageSegment.image(f"file:///{rule_path}")) 32 | 33 | @sv.on_fullmatch(("结束猜手牌","结束麻兜")) 34 | async def end_game(bot, ev): 35 | hg = HandGuess(ev["user_id"], ev["group_id"]) 36 | res = await hg.end_game() 37 | 38 | @sv.on_message("group") 39 | async def on_input_chara_name(bot, ev): 40 | msg = ev["raw_message"] 41 | hg = HandGuess(ev["user_id"], ev["group_id"]) 42 | 43 | if hg.is_start(): 44 | res = await hg.guesses_handler(msg) 45 | if res.get("img"): 46 | await bot.send(ev, MessageSegment.image(res["img"]), at_sender=True) 47 | 48 | if res.get("msg"): 49 | await bot.send(ev, res["msg"], at_sender=True) 50 | -------------------------------------------------------------------------------- /mahjong_handle/assets/correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/mahjong_handle/assets/correct.png -------------------------------------------------------------------------------- /mahjong_handle/assets/exist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/mahjong_handle/assets/exist.png -------------------------------------------------------------------------------- /mahjong_handle/assets/font/HYWenHei 65W.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/mahjong_handle/assets/font/HYWenHei 65W.ttf -------------------------------------------------------------------------------- /mahjong_handle/assets/font/HYWenHei 85W.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/mahjong_handle/assets/font/HYWenHei 85W.ttf -------------------------------------------------------------------------------- /mahjong_handle/assets/no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/mahjong_handle/assets/no.png -------------------------------------------------------------------------------- /mahjong_handle/assets/rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/mahjong_handle/assets/rule.png -------------------------------------------------------------------------------- /mahjong_handle/db/db.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/mahjong_handle/db/db.sqlite -------------------------------------------------------------------------------- /mahjong_handle/handler.py: -------------------------------------------------------------------------------- 1 | import linecache 2 | import random 3 | import re 4 | from collections import defaultdict, namedtuple 5 | from enum import Enum 6 | 7 | from mahjong.hand_calculating.hand import HandCalculator 8 | from mahjong.hand_calculating.hand_config import HandConfig 9 | from mahjong.tile import TilesConverter as TC 10 | from nonebot import get_bot, MessageSegment 11 | from PIL import Image 12 | 13 | from .imghandler import draw_text_by_line, easy_paste, get_font 14 | from .mahjong_image import MahjongImage, TilebackType 15 | from .utils import call_later, cancel_call_later, get_path, pil2b64 16 | from .user import User 17 | 18 | 19 | class TileAsciiMap(Enum): 20 | 万 = "m" 21 | 筒 = "p" 22 | 索 = "s" 23 | 东 = "1z" 24 | 南 = "2z" 25 | 西 = "3z" 26 | 北 = "4z" 27 | 白 = "5z" 28 | 发 = "6z" 29 | 中 = "7z" 30 | 31 | 32 | TileMap = ["万", "筒", "索", "东", "南", "西", "北", "白", "发", "中"] 33 | 34 | HandSplit = namedtuple("HandSplit", "man pin sou honors") 35 | HandResult = namedtuple( 36 | "HandResult", "tiles tiles_ascii win_tile tsumo result raw hand_index" 37 | ) 38 | 39 | 40 | async def get_hand(hand_index=None, **kwargs) -> HandResult: 41 | calculator = HandCalculator() 42 | 43 | hand_list = linecache.getlines(get_path("hands.txt")) # 读取手牌列表 44 | hand_index = hand_index or random.randint(0, len(hand_list)) # 指定或者随机一组手牌 45 | hand_raw = hand_list[hand_index].strip()[:-3] 46 | raw = hand_raw.replace("+", "") 47 | tsumo = hand_raw[26] == "+" # 是否为自摸 48 | last_tile = (hand_raw[26:28], hand_raw[27:29])[tsumo] # 和牌 49 | 50 | tiles = TC.one_line_string_to_136_array(raw) 51 | win_tile = TC.one_line_string_to_136_array(last_tile)[0] 52 | 53 | result = calculator.estimate_hand_value( 54 | tiles, 55 | win_tile, 56 | config=HandConfig(is_riichi=True, is_tsumo=tsumo), 57 | **kwargs, 58 | ) 59 | 60 | tiles = TC.one_line_string_to_136_array(hand_raw[:26]) 61 | tiles_ascii = HandGuess.format_split_hand(hand_raw[:26]) 62 | 63 | return HandResult(tiles, tiles_ascii, last_tile, tsumo, result, raw, hand_index) 64 | 65 | 66 | UserState = namedtuple("UserState", "hit_count") 67 | GroupState = namedtuple("GroupState", "start hand users") 68 | 69 | HandGuessProcess = defaultdict(lambda: GroupState(False, None, {})) 70 | 71 | 72 | class HandGuess: 73 | __slots__ = ["qq", "group", "user"] 74 | 75 | MAX_GUESS = 6 # 每人最大猜测次数 76 | GUESS_DEDUCT_POINTS = 1000 # 超出每回扣除的积分 77 | SHOW_WIN_TILE_POINTS = 2000 # 查看胡牌扣除积分 78 | 79 | TIMEOUT = 10 * 60 # 一局结束超时时间 80 | 81 | def __init__(self, qq: int, group: int): 82 | self.qq = qq 83 | self.group = group 84 | self.user = User(self.qq) 85 | 86 | @property 87 | def status(self) -> GroupState: 88 | return HandGuessProcess[self.group] 89 | 90 | def is_start(self): 91 | return self.status.start 92 | 93 | def reset_game(self): 94 | HandGuessProcess[self.group] = GroupState(False, None, {}) 95 | 96 | async def timeout(self): 97 | bot = get_bot() 98 | await bot.send_group_msg(group_id=self.group, message="游戏已超时, 请重新开始") 99 | ans = await self.guesses_handler("", only_answer=True) 100 | await bot.send_group_msg( 101 | group_id=self.group, message=MessageSegment.image(ans["img"]) 102 | ) 103 | self.reset_game() 104 | 105 | async def end_game(self): 106 | bot = get_bot() 107 | await bot.send_group_msg(group_id=self.group, message="游戏已强制结束, 请重新开始") 108 | ans = await self.guesses_handler("", only_answer=True) 109 | await bot.send_group_msg( 110 | group_id=self.group, message=MessageSegment.image(ans["img"]) 111 | ) 112 | self.reset_game() 113 | 114 | async def start(self): 115 | if self.is_start(): 116 | return dict(error=True, msg="当前游戏已经开始") 117 | 118 | # 生成手牌 119 | hand_res = await get_hand() 120 | HandGuessProcess[self.group] = GroupState( 121 | True, hand_res, defaultdict(lambda: UserState(0)) 122 | ) 123 | print(TC.to_one_line_string(hand_res.tiles) + hand_res.win_tile) 124 | call_later(self.TIMEOUT, self.timeout, "HandGuessGame") 125 | 126 | return dict(error=False) 127 | 128 | @staticmethod 129 | def format_hand_msg(msg: str): 130 | hand = "" 131 | for w in msg: 132 | if w in TileMap: 133 | hand += TileAsciiMap[w].value 134 | else: 135 | hand += w 136 | 137 | if hand[:-2][-1].isdigit(): 138 | hand = hand[:-2] + hand[-1] + hand[-2:] 139 | return hand 140 | 141 | @staticmethod 142 | def format_split_hand(hand: str): 143 | split_start = 0 144 | result = "" 145 | for index, i in enumerate(hand): 146 | if i == "m": 147 | result += "m".join(hand[split_start:index]) + "m" 148 | split_start = index + 1 149 | if i == "p": 150 | result += "p".join(hand[split_start:index]) + "p" 151 | split_start = index + 1 152 | if i == "s": 153 | result += "s".join(hand[split_start:index]) + "s" 154 | split_start = index + 1 155 | if i == "z" or i == "h": 156 | result += "z".join(hand[split_start:index]) + "z" 157 | split_start = index + 1 158 | return [result[i * 2 : i * 2 + 2] for i in range(int(len(result) / 2))] 159 | 160 | def inc_user_count(self): 161 | info = self.status.users[self.qq] 162 | count = info.hit_count + 1 163 | self.status.users[self.qq] = info._replace(hit_count=count) 164 | 165 | def is_win(self, tiles: list): 166 | set_tiles = self.status.hand.tiles_ascii + [self.status.hand.win_tile] 167 | return set_tiles == tiles 168 | 169 | def win_game(self, points: int): 170 | self.reset_game() 171 | cancel_call_later("HandGuessGame") 172 | self.user.add_points(points) 173 | return f"恭喜你, 猜对了, 积分增加 {points} 点, 当前积分 {format(self.user.points, ',')}" 174 | 175 | def is_show_win_tile_msg(self, msg: str): 176 | if msg != "查看和牌": 177 | return dict(error=True) 178 | if self.user.points < self.SHOW_WIN_TILE_POINTS: 179 | return dict(error=False, msg=f"你的积分({self.user.points})不足", img=None) 180 | 181 | self.user.sub_points(self.SHOW_WIN_TILE_POINTS) 182 | blue = MahjongImage(TilebackType.blue) 183 | return dict( 184 | error=False, img=pil2b64(blue.tile(self.status.hand.win_tile)), msg="" 185 | ) 186 | 187 | async def guesses_handler(self, msg: str, only_answer=False): 188 | msg = (msg, self.status.hand.raw)[only_answer] 189 | msg = msg.strip().replace(" ", "") 190 | 191 | show_win_tile = self.is_show_win_tile_msg(msg) 192 | if not show_win_tile["error"]: 193 | return dict(error=False, img=show_win_tile["img"], msg=show_win_tile["msg"]) 194 | 195 | # pass不合法的信息 196 | if re.search(f"[^\dmpszh{''.join(TileMap)}]", msg): 197 | return dict(error=True, msg="") 198 | 199 | use_deduct_points = False 200 | if self.status.users[self.qq].hit_count >= self.MAX_GUESS and not only_answer: 201 | if self.user.points < self.GUESS_DEDUCT_POINTS: 202 | return dict(error=True, msg=f"你的积分({self.user.points})不足") 203 | else: 204 | use_deduct_points = True 205 | self.user.sub_points(self.GUESS_DEDUCT_POINTS) 206 | 207 | msg_hand = HandGuess.format_hand_msg(msg) 208 | msg_win_tile = msg_hand[-2:] 209 | 210 | msg_tiles = TC.one_line_string_to_136_array(msg_hand) 211 | if len(msg_tiles) != 14: 212 | return dict(error=True, msg="不是, 说好的14张牌呢") 213 | 214 | win_tile = TC.one_line_string_to_136_array(msg_win_tile)[0] 215 | calculator = HandCalculator() 216 | # 默认立直 , 是否自摸看生成的牌组 217 | result = calculator.estimate_hand_value( 218 | msg_tiles, 219 | win_tile, 220 | config=HandConfig(is_riichi=True, is_tsumo=self.status.hand.tsumo), 221 | ) 222 | 223 | if result.han is None: 224 | return dict(error=True, msg="你这牌都没胡啊") 225 | if result.han == 0: 226 | return dict(error=True, msg="你无役了") 227 | 228 | current_tiles = HandGuess.format_split_hand(msg_hand[:-2]) 229 | 230 | blue = MahjongImage(TilebackType.blue) 231 | orange = MahjongImage(TilebackType.orange) 232 | no_color = MahjongImage(TilebackType.no_color) 233 | 234 | # 手牌 235 | hand_img = Image.new("RGB", (80 * 13, 130), "#6c6c6c") 236 | group_tiles_box = self.status.hand.tiles_ascii + [self.status.hand.win_tile] 237 | 238 | for index, tile in enumerate(current_tiles): 239 | ascii_tile = self.status.hand.tiles_ascii[index] 240 | pos = (index * 80, 0) 241 | if tile == ascii_tile and tile in group_tiles_box: 242 | # 如果位置正确 243 | easy_paste(hand_img, blue.tile(tile), pos) 244 | elif tile in group_tiles_box: 245 | # 如果存在 246 | easy_paste(hand_img, orange.tile(tile), pos) 247 | else: 248 | # 否则不存在 249 | easy_paste(hand_img, no_color.tile(tile), pos) 250 | 251 | tile in group_tiles_box and group_tiles_box.remove(tile) 252 | 253 | # 胡牌 254 | wind_img = Image.new("RGB", (80, 130), "#6c6c6c") 255 | pos = (0, 0) 256 | if ( 257 | msg_win_tile == self.status.hand.win_tile 258 | and msg_win_tile in group_tiles_box 259 | ): 260 | easy_paste(wind_img, blue.tile(msg_win_tile), pos) 261 | elif msg_win_tile in self.status.hand.tiles_ascii: 262 | # 如果存在 263 | easy_paste(wind_img, orange.tile(msg_win_tile), pos) 264 | else: 265 | # 否则不存在 266 | easy_paste(wind_img, no_color.tile(msg_win_tile), pos) 267 | 268 | # 役提示 269 | yaku = [x for x in self.status.hand.result.yaku if x.yaku_id not in [0, 1]] 270 | yaku.reverse() 271 | tip = "提示: " + " ".join([x.japanese for x in yaku]) 272 | 273 | # 番提示 274 | status_han = self.status.hand.result.han 275 | status_fu = self.status.hand.result.fu 276 | status_cost = ( 277 | self.status.hand.result.cost["main"] 278 | + self.status.hand.result.cost["additional"] 279 | ) 280 | tsumo_tip = ("", ",自摸")[self.status.hand.tsumo] 281 | han_tip = f"{status_han}番{status_fu}符 {status_cost}点 (包括立直{tsumo_tip})" 282 | 283 | background = Image.new("RGB", (1200, 400), "#EEEEEE") 284 | 285 | if not only_answer: 286 | if use_deduct_points: 287 | draw_text_by_line( 288 | background, 289 | (26.5, 25), 290 | f"-1000 ({format(self.user.points, ',')})", 291 | get_font(30), 292 | "#475463", 293 | 255, 294 | ) 295 | else: 296 | last = self.MAX_GUESS - self.status.users[self.qq].hit_count - 1 297 | draw_text_by_line( 298 | background, (26.5, 25), f"剩余{last}回", get_font(40), "#475463", 255 299 | ) 300 | 301 | draw_text_by_line( 302 | background, 303 | (26.5, 70), 304 | f"超出每回扣除{self.GUESS_DEDUCT_POINTS}积分", 305 | get_font(30, "65"), 306 | "#475463", 307 | 800, 308 | ) 309 | 310 | draw_text_by_line( 311 | background, (403.5, 25), tip, get_font(40), "#475463", 800, True 312 | ) 313 | draw_text_by_line( 314 | background, 315 | (194.5, 130), 316 | han_tip, 317 | get_font(40), 318 | "#475463", 319 | 1200, 320 | True, 321 | ) 322 | 323 | draw_text_by_line( 324 | background, 325 | (900, 25), 326 | f"支付{self.SHOW_WIN_TILE_POINTS}点[查看和牌]", 327 | get_font(25), 328 | "#475463", 329 | 500, 330 | ) 331 | 332 | easy_paste(background, hand_img.convert("RGBA"), (30, 226)) 333 | easy_paste(background, wind_img.convert("RGBA"), (13 * 80 + 50, 226)) 334 | 335 | ret_msg = "" 336 | if not only_answer: 337 | if self.is_win(current_tiles + [msg_win_tile]): 338 | ret_msg = self.win_game(status_cost) 339 | else: 340 | self.inc_user_count() 341 | 342 | return dict(error=False, img=pil2b64(background), msg=ret_msg) 343 | -------------------------------------------------------------------------------- /mahjong_handle/imghandler.py: -------------------------------------------------------------------------------- 1 | import math 2 | from typing import List, Tuple 3 | from PIL import Image, ImageDraw, ImageFont 4 | from .utils import get_path 5 | 6 | 7 | def get_font(size, w="85"): 8 | return ImageFont.truetype( 9 | get_path("assets", "font", f"HYWenHei {w}W.ttf"), size=size 10 | ) 11 | 12 | 13 | def draw_text_by_line( 14 | img, pos, text, font, fill, max_length, center=False, line_space=None 15 | ): 16 | """ 17 | 在图片上写长段文字, 自动换行 18 | max_length单行最大长度, 单位像素 19 | line_space 行间距, 单位像素, 默认是字体高度的0.3倍 20 | """ 21 | x, y = pos 22 | _, h = font.getsize("X") 23 | if line_space is None: 24 | y_add = math.ceil(1.3 * h) 25 | else: 26 | y_add = math.ceil(h + line_space) 27 | draw = ImageDraw.Draw(img) 28 | row = "" # 存储本行文字 29 | length = 0 # 记录本行长度 30 | for character in text: 31 | w, h = font.getsize(character) # 获取当前字符的宽度 32 | if length + w * 2 <= max_length: 33 | row += character 34 | length += w 35 | else: 36 | row += character 37 | if center: 38 | font_size = font.getsize(row) 39 | x = math.ceil((img.size[0] - font_size[0]) / 2) 40 | draw.text((x, y), row, font=font, fill=fill) 41 | row = "" 42 | length = 0 43 | y += y_add 44 | if row != "": 45 | if center: 46 | font_size = font.getsize(row) 47 | x = math.ceil((img.size[0] - font_size[0]) / 2) 48 | draw.text((x, y), row, font=font, fill=fill) 49 | 50 | 51 | def cut_sprites( 52 | img: Image.Image, parameter, box: Tuple = None, width_padding=0, sprite_call=None 53 | ) -> List: 54 | """ 55 | sprites匀切分割 56 | 57 | img: sprite图 58 | 59 | parameter:参数 60 | 61 | - (width, height):按icon宽度和icon高度均匀切割,适用于多行多列 62 | 63 | - (amount, 'x/y'):沿x轴或y轴均匀切割成指定数量,适用于单行或单列 64 | 65 | box:指定图像区域 66 | 67 | scale:图像缩放 68 | """ 69 | if box: 70 | img = img.crop(box) 71 | 72 | max_width, max_height = img.size 73 | if isinstance(parameter[1], int): 74 | width, height = parameter 75 | else: 76 | if parameter[1] == "x": 77 | width = max_width / parameter[0] 78 | height = max_height 79 | else: 80 | width = max_width 81 | height = max_height / parameter[0] 82 | max_num = round(max_width / width) * round(max_height / height) 83 | sprite_list = [] 84 | x1 = 0 85 | y1 = 0 86 | x2 = width 87 | y2 = height 88 | for i in range(0, max_num): 89 | box = (x1, y1, x2, y2) 90 | section = img.crop(box) 91 | 92 | if sprite_call: 93 | section = sprite_call(section) 94 | 95 | sprite_list.append(section) 96 | x1 += width + width_padding 97 | x2 += width + width_padding 98 | if max_width - x1 < width: 99 | x1 = 0 100 | x2 = width 101 | y1 += height 102 | y2 += height 103 | return sprite_list 104 | 105 | 106 | def easy_alpha_composite( 107 | im: Image, im_paste: Image, pos=(0, 0), direction="lt" 108 | ) -> Image: 109 | """ 110 | 透明图像快速粘贴 111 | """ 112 | base = Image.new("RGBA", im.size) 113 | easy_paste(base, im_paste, pos, direction) 114 | base = Image.alpha_composite(im, base) 115 | return base 116 | 117 | 118 | def easy_paste(im: Image, im_paste: Image, pos=(0, 0), direction="lt"): 119 | """ 120 | inplace method 121 | 快速粘贴, 自动获取被粘贴图像的坐标。 122 | pos应当是粘贴点坐标,direction指定粘贴点方位,例如lt为左上 123 | """ 124 | x, y = pos 125 | size_x, size_y = im_paste.size 126 | if "d" in direction: 127 | y = y - size_y 128 | if "r" in direction: 129 | x = x - size_x 130 | if "c" in direction: 131 | x = x - int(0.5 * size_x) 132 | y = y - int(0.5 * size_y) 133 | im.paste(im_paste, (x, y, x + size_x, y + size_y), im_paste) 134 | -------------------------------------------------------------------------------- /mahjong_handle/mahjong_image.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from enum import Enum 3 | 4 | from PIL import Image 5 | 6 | from .imghandler import cut_sprites 7 | from .utils import get_path 8 | 9 | __all__ = ["MahjongImage", "TilebackType"] 10 | 11 | TilebackMap = [ 12 | "7s", 13 | "6p", 14 | "5m", 15 | "9m", 16 | "0p", 17 | "0s", 18 | "1m", 19 | "1p", 20 | "5p", 21 | "6s", 22 | "7z", 23 | "9p", 24 | "1s", 25 | "2s", 26 | "0m", 27 | "3m", 28 | "5s", 29 | "6z", 30 | "8m", 31 | "9s", 32 | "1z", 33 | "3p", 34 | "4m", 35 | "4p", 36 | "5z", 37 | "7m", 38 | "8p", 39 | "back", 40 | "2m", 41 | "3s", 42 | "4s", 43 | "no_image", 44 | "6m", 45 | "7p", 46 | "8s", 47 | "2z", 48 | "2p", 49 | "3z", 50 | "4z", 51 | "no_image2", 52 | ] 53 | 54 | 55 | class TilebackType(Enum): 56 | blue = "correct.png" 57 | orange = "exist.png" 58 | no_color = "no.png" 59 | 60 | 61 | MahjongImageObj = defaultdict(dict) 62 | 63 | for filename in TilebackType: 64 | img = Image.open(get_path("assets", filename.value)) 65 | MahjongImageObj[filename] = dict( 66 | zip( 67 | TilebackMap, 68 | cut_sprites( 69 | img, 70 | (80, 130), 71 | width_padding=1, 72 | sprite_call=lambda img: img.convert("RGBA"), 73 | ), 74 | ) 75 | ) 76 | img.close() 77 | 78 | 79 | class MahjongImage: 80 | __slots__ = ["type"] 81 | 82 | def __init__(self, type: TilebackType): 83 | self.type = type 84 | 85 | def tile(self, name): 86 | if name not in TilebackMap: 87 | return TilebackMap[self.type]["back"] 88 | else: 89 | return MahjongImageObj[self.type][name] 90 | -------------------------------------------------------------------------------- /mahjong_handle/user.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from .utils import init_db 3 | 4 | UserDb = init_db(tablename="user_db") 5 | 6 | UserInfo = namedtuple("UserInfo", "points", defaults=(0,)) 7 | 8 | 9 | class User: 10 | __slots__ = ["user_id"] 11 | 12 | def __init__(self, user_id): 13 | self.user_id = user_id 14 | 15 | def get_info(self): 16 | info = UserDb.get(self.user_id) 17 | return UserInfo(**info) if info else UserInfo() 18 | 19 | @property 20 | def points(self): 21 | return self.get_info().points 22 | 23 | def save(self, **kwargs): 24 | UserDb[self.user_id] = UserInfo(**kwargs)._asdict() 25 | 26 | def sub_points(self, points): 27 | info = self.get_info() 28 | assert info.points >= points 29 | end_points = info.points - points 30 | self.save(points=end_points) 31 | 32 | def add_points(self, points): 33 | info = self.get_info() 34 | end_points = info.points + points 35 | self.save(points=end_points) 36 | 37 | @staticmethod 38 | def points_rank(): 39 | return 40 | -------------------------------------------------------------------------------- /mahjong_handle/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import datetime 3 | import os 4 | from io import BytesIO 5 | 6 | import ujson 7 | from apscheduler.triggers.date import DateTrigger 8 | from nonebot import scheduler 9 | from sqlitedict import SqliteDict 10 | 11 | 12 | def get_path(*paths): 13 | return os.path.join(os.path.dirname(__file__), *paths) 14 | 15 | 16 | def pil2b64(data): 17 | bio = BytesIO() 18 | data = data.convert("RGB") 19 | data.save(bio, format="JPEG", quality=75) 20 | base64_str = base64.b64encode(bio.getvalue()).decode() 21 | return "base64://" + base64_str 22 | 23 | 24 | def cancel_call_later(job_id): 25 | scheduler.remove_job(job_id, "default") 26 | 27 | 28 | def call_later(delay, func, job_id): 29 | if scheduler.get_job(job_id, "default"): 30 | cancel_call_later(job_id) 31 | now = datetime.datetime.now() 32 | notify_time = now + datetime.timedelta(seconds=delay) 33 | return scheduler.add_job( 34 | func, 35 | trigger=DateTrigger(notify_time), 36 | id=job_id, 37 | misfire_grace_time=60, 38 | coalesce=True, 39 | jobstore="default", 40 | max_instances=1, 41 | ) 42 | 43 | 44 | db = {} 45 | 46 | 47 | def init_db(db_dir="db", db_name="db.sqlite", tablename="unnamed") -> SqliteDict: 48 | if db.get(db_name): 49 | return db[db_name] 50 | db[db_name] = SqliteDict( 51 | get_path(db_dir, db_name), 52 | tablename=tablename, 53 | encode=ujson.dumps, 54 | decode=ujson.loads, 55 | autocommit=True, 56 | ) 57 | return db[db_name] 58 | -------------------------------------------------------------------------------- /majsoul_Info/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .processData import * 3 | from hoshino import Service 4 | from hoshino.typing import HoshinoBot,CQEvent 5 | 6 | sv = Service("雀魂信息查询") 7 | 8 | @sv.on_prefix(('雀魂信息','雀魂查询')) 9 | async def majsoulInfo(bot, ev: CQEvent): 10 | args = ev.message.extract_plain_text().split() 11 | if len(args) == 1: 12 | nickname = ev.message.extract_plain_text() 13 | if len(nickname) > 15: 14 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 15 | message = "\n" 16 | IDdata = getID(nickname) 17 | if IDdata == -404: 18 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 19 | sv.logger.info("正在查询" + nickname + "的对局数据") 20 | if IDdata == -1: 21 | await bot.send(ev, "没有查询到该角色在金之间以上的对局数据呢~") 22 | else: 23 | if len(IDdata)>1: 24 | message = message + "查询到多条角色昵称呢~,若输出不是您想查找的昵称,请补全查询昵称\n\n" 25 | message = message + printBasicInfo(IDdata[0],"0","4") 26 | await bot.send(ev, message, at_sender=True) 27 | else: 28 | message = message+printBasicInfo(IDdata[0],"0","4") 29 | await bot.send(ev,message,at_sender=True) 30 | elif len(args) == 2: 31 | nickname = args[1] 32 | if len(nickname) > 15: 33 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 34 | sv.logger.info("正在查询" + nickname + "的对局数据 ") 35 | message = "\n" 36 | room_level = "" 37 | if args[0] == "金场" or args[0] == "金" or args[0] == "金之间": 38 | room_level = "1" 39 | elif args[0] == "玉场" or args[0] == "玉" or args[0] == "玉之间": 40 | room_level = "2" 41 | elif args[0] == "王座" or args[0] == "王座之间": 42 | room_level = "3" 43 | else: 44 | await bot.finish(ev, "房间等级输入不正确,请重新输入",at_sender=True) 45 | IDdata = getID(nickname) 46 | if IDdata == -404: 47 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 48 | if IDdata == -1: 49 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~") 50 | else: 51 | if len(IDdata) > 1: 52 | message = message + "查询到多条角色昵称呢~,若输出不是您想查找的昵称,请补全查询昵称\n" 53 | message = message + printExtendInfo(IDdata[0], room_level,"4") 54 | await bot.send(ev, message, at_sender=True) 55 | else: 56 | pic = printExtendInfo(IDdata[0], room_level,"4") 57 | await bot.send(ev, pic, at_sender=True) 58 | else: 59 | await bot.finish(ev, "查询信息输入不正确,请重新输入", at_sender=True) 60 | 61 | 62 | @sv.on_prefix(('雀魂牌谱','牌谱查询')) 63 | async def RecordInfo(bot, ev: CQEvent): 64 | nickname = ev.message.extract_plain_text() 65 | if len(nickname) > 15: 66 | sv.logger.info("昵称长度超过雀魂最大限制,已跳过") 67 | return 68 | IDdata = getID(nickname) 69 | if IDdata == -404: 70 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 71 | message = "\n" 72 | sv.logger.info("正在查询" + nickname + "的牌谱数据") 73 | if IDdata == -1: 74 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~") 75 | else: 76 | if len(IDdata) > 1: 77 | message = message + "查询到多条角色昵称呢~,若输出不是您想查找的昵称,请补全查询昵称\n" 78 | message = message + printRecordInfo(IDdata[0],4) 79 | await bot.send(ev, message, at_sender=True) 80 | else: 81 | message = message + printRecordInfo(IDdata[0],4) 82 | await bot.send(ev, message, at_sender=True) 83 | 84 | @sv.on_prefix(('三麻信息','三麻查询')) 85 | async def TrimajsoulInfo(bot, ev: CQEvent): 86 | args = ev.message.extract_plain_text().split() 87 | if len(args) == 1: 88 | nickname = ev.message.extract_plain_text() 89 | if len(nickname) > 15: 90 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 91 | message = "\n" 92 | sv.logger.info("正在查询" + nickname + "的对局数据") 93 | IDdata = gettriID(nickname) 94 | if IDdata == -404: 95 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 96 | if IDdata == -1: 97 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~") 98 | else: 99 | if len(IDdata)>1: 100 | message = message + "查询到多条角色昵称呢~,若输出不是您想查找的昵称,请补全查询昵称\n\n" 101 | message = message + printBasicInfo(IDdata[0],"0","3") 102 | await bot.send(ev, message, at_sender=True) 103 | else: 104 | message = message + printBasicInfo(IDdata[0],"0","3") 105 | await bot.send(ev,message,at_sender=True) 106 | elif len(args) == 2: 107 | nickname = args[1] 108 | if len(nickname) > 15: 109 | await bot.finish(ev, "昵称长度超过雀魂最大限制") 110 | sv.logger.info("正在查询" + nickname + "的对局数据") 111 | message = "\n" 112 | room_level = "" 113 | if args[0] == "金场" or args[0] == "金" or args[0] == "金之间": 114 | room_level = "1" 115 | elif args[0] == "玉场" or args[0] == "玉" or args[0] == "玉之间": 116 | room_level = "2" 117 | elif args[0] == "王座" or args[0] == "王座之间": 118 | room_level = "3" 119 | else: 120 | await bot.finish(ev, "房间等级输入不正确,请重新输入",at_sender=True) 121 | sv.logger.info("正在查询" + nickname + "的对局数据") 122 | IDdata = gettriID(nickname) 123 | if IDdata == -404: 124 | await bot.finish(ev, "获取牌谱屋的数据超时了呢,请稍后再试哦~") 125 | if IDdata == -1: 126 | await bot.finish(ev, "没有查询到该角色在金之间以上的对局数据呢~") 127 | else: 128 | if len(IDdata) > 1: 129 | message = message + "查询到多条角色昵称呢~,若输出不是您想查找的昵称,请补全查询昵称\n" 130 | message = message + printExtendInfo(IDdata[0], room_level,"3") 131 | await bot.send(ev, message, at_sender=True) 132 | else: 133 | pic = printExtendInfo(IDdata[0], room_level,"3") 134 | await bot.send(ev, pic, at_sender=True) 135 | else: 136 | await bot.finish(ev, "查询信息输入不正确,请重新输入", at_sender=True) 137 | 138 | @sv.on_prefix('三麻牌谱') 139 | async def TriRecordInfo(bot, ev: CQEvent): 140 | nickname = ev.message.extract_plain_text() 141 | if len(nickname) > 15: 142 | sv.logger.info("昵称长度超过雀魂最大限制,已跳过") 143 | return 144 | IDdata = gettriID(nickname) 145 | sv.logger.info("正在查询" + nickname + "的牌谱数据") 146 | message = "\n" 147 | if IDdata == -1: 148 | await bot.send(ev, "没有查询到该角色在金之间以上的对局数据呢~") 149 | else: 150 | if len(IDdata) > 1: 151 | message = message + "查询到多条角色昵称呢~,若输出不是您想查找的昵称,请补全查询昵称\n" 152 | message = message + printRecordInfo(IDdata[0],3) 153 | await bot.send(ev, message, at_sender=True) 154 | else: 155 | message = message + printRecordInfo(IDdata[0],3) 156 | await bot.send(ev, message, at_sender=True) 157 | -------------------------------------------------------------------------------- /majsoul_Info/majsoul_Spider.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from bs4 import BeautifulSoup 3 | import urllib.request 4 | import urllib.error 5 | import urllib.parse 6 | import json 7 | import time 8 | 9 | baseurl = "https://ak-data-1.sapk.ch/api/v2/pl4" 10 | tribaseurl = "https://ak-data-1.sapk.ch/api/v2/pl3" 11 | 12 | 13 | def getURL(url): 14 | headers = { 15 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36" 16 | } 17 | try: 18 | response = urllib.request.Request(url=url, headers=headers, method="GET") 19 | req = urllib.request.urlopen(response,timeout=3) 20 | info = str(BeautifulSoup(req.read().decode('utf-8'), "html.parser")) 21 | except urllib.error.URLError as e: 22 | return e 23 | return info 24 | 25 | def getID(nickname):#获取牌谱屋角色ID 26 | nickname = urllib.parse.quote(nickname) #UrlEncode转换 27 | url = baseurl + "/search_player/"+nickname+"?limit=9" 28 | data = getURL(url) 29 | if isinstance(data,urllib.error.URLError): 30 | return -404 31 | datalist = json.loads(data) 32 | if datalist == [] : 33 | return -1 34 | return datalist 35 | 36 | def gettriID(nickname): 37 | nickname = urllib.parse.quote(nickname) #UrlEncode转换 38 | url = tribaseurl + "/search_player/"+nickname+"?limit=9" 39 | data = getURL(url) 40 | if isinstance(data,urllib.error.URLError): 41 | return -404 42 | datalist = json.loads(data) 43 | if datalist == [] : 44 | return -1 45 | return datalist 46 | 47 | 48 | def selectLevel(room_level): 49 | level_list = [] 50 | if room_level == "0": 51 | level_list.append("16.12.9")#所有南场信息 52 | level_list.append("15.11.8")#所有东场信息 53 | elif room_level == "1": 54 | level_list.append("9")#金南 55 | level_list.append("8")#金东 56 | elif room_level == "2": 57 | level_list.append("12")#玉南 58 | level_list.append("11")#玉东 59 | elif room_level == "3": 60 | level_list.append("16")#王座南 61 | level_list.append("15")#王座东 62 | return level_list 63 | 64 | def select_triLevel(room_level): 65 | level_list = [] 66 | if room_level == "0": 67 | level_list.append("22.24.26")#所有南场信息 68 | level_list.append("21.23.25")#所有东场信息 69 | elif room_level == "1": 70 | level_list.append("22")#金南 71 | level_list.append("21")#金东 72 | elif room_level == "2": 73 | level_list.append("24")#玉南 74 | level_list.append("23")#玉东 75 | elif room_level == "3": 76 | level_list.append("26")#王座南 77 | level_list.append("25")#王座东 78 | return level_list 79 | 80 | def select_triInfo(id,room_level): #信息查询 81 | localtime = time.time() 82 | urltime = str(int(localtime*1000)) #时间戳 83 | basicurl = tribaseurl+"/player_stats/"+str(id)+"/1262304000000/"+urltime+"?mode=" 84 | extendurl = tribaseurl+"/player_extended_stats/"+str(id)+"/1262304000000/"+urltime+"?mode=" 85 | data_list = [] 86 | level_list = select_triLevel(room_level) 87 | for i in range(0,2): 88 | data_list.append(getURL(basicurl + level_list[i])) 89 | data_list.append(getURL(extendurl + level_list[i])) 90 | return data_list 91 | 92 | 93 | def selectInfo(id,room_level): #信息查询 94 | localtime = time.time() 95 | urltime = str(int(localtime*1000)) #时间戳 96 | basicurl = baseurl+"/player_stats/"+str(id)+"/1262304000000/"+urltime+"?mode=" 97 | extendurl = baseurl+"/player_extended_stats/"+str(id)+"/1262304000000/"+urltime+"?mode=" 98 | data_list = [] 99 | level_list = selectLevel(room_level) 100 | for i in range(0,2): 101 | data_list.append(getURL(basicurl + level_list[i])) 102 | data_list.append(getURL(extendurl + level_list[i])) 103 | return data_list 104 | 105 | def selectRecord(id): 106 | localtime = time.time() 107 | urltime = str(int(localtime * 1000)) # 时间戳 108 | basicurl = baseurl + "/player_stats/" + str(id) + "/1262304000000/" + urltime + "?mode=16.12.9.15.11.8" 109 | count = str(json.loads(getURL(basicurl))["count"]) 110 | recordurl = baseurl + "/player_records/"+str(id)+"/"+urltime+"/1262304000000?limit=5&mode=16.12.9.15.11.8&descending=true&tag="+count 111 | record = getURL(recordurl) 112 | return record 113 | 114 | def select_triRecord(id): 115 | localtime = time.time() 116 | urltime = str(int(localtime * 1000)) # 时间戳 117 | basicurl = tribaseurl + "/player_stats/" + str(id) + "/1262304000000/" + urltime + "?mode=22.24.26.21.23.25" 118 | count = str(json.loads(getURL(basicurl))["count"]) 119 | recordurl = tribaseurl + "/player_records/"+str(id)+"/"+urltime+"/1262304000000?limit=5&mode=22.24.26.21.23.25&descending=true&tag="+count 120 | record = getURL(recordurl) 121 | return record 122 | -------------------------------------------------------------------------------- /majsoul_Info/processData.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .majsoul_Spider import * 3 | import json,os 4 | import base64 5 | from PIL import ImageFont,ImageDraw,Image 6 | from io import BytesIO 7 | 8 | 9 | FILE_PATH = os.path.dirname(os.path.dirname(__file__)) 10 | 11 | class ImgText: 12 | FONTS_PATH = os.path.join(FILE_PATH,'fonts') 13 | FONTS = os.path.join(FONTS_PATH,'msyh1.otf') 14 | font = ImageFont.truetype(FONTS, 14) 15 | def __init__(self, text): 16 | # 预设宽度 可以修改成你需要的图片宽度 17 | self.width = 600 18 | # 文本 19 | self.text = text 20 | # 段落 , 行数, 行高 21 | self.duanluo, self.note_height, self.line_height, self.drow_height = self.split_text() 22 | def get_duanluo(self, text): 23 | txt = Image.new('RGBA', (400, 800), (255, 255, 255, 0)) 24 | draw = ImageDraw.Draw(txt) 25 | # 所有文字的段落 26 | duanluo = "" 27 | # 宽度总和 28 | sum_width = 0 29 | # 几行 30 | line_count = 1 31 | # 行高 32 | line_height = 0 33 | for char in text: 34 | width, height = draw.textsize(char, ImgText.font) 35 | sum_width += width 36 | if sum_width > self.width: # 超过预设宽度就修改段落 以及当前行数 37 | line_count += 1 38 | sum_width = 0 39 | duanluo += '\n' 40 | duanluo += char 41 | line_height = max(height, line_height) 42 | if not duanluo.endswith('\n'): 43 | duanluo += '\n' 44 | return duanluo, line_height, line_count 45 | def split_text(self): 46 | # 按规定宽度分组 47 | max_line_height, total_lines = 0, 0 48 | allText = [] 49 | for text in self.text.split('\n'): 50 | duanluo, line_height, line_count = self.get_duanluo(text) 51 | max_line_height = max(line_height, max_line_height) 52 | total_lines += line_count 53 | allText.append((duanluo, line_count)) 54 | line_height = max_line_height 55 | total_height = total_lines * line_height 56 | drow_height = total_lines * line_height 57 | return allText, total_height, line_height, drow_height 58 | def draw_text(self): 59 | """ 60 | 绘图以及文字 61 | :return: 62 | """ 63 | im = Image.new("RGB", (600, self.drow_height), (255, 255, 255)) 64 | draw = ImageDraw.Draw(im) 65 | # 左上角开始 66 | x, y = 0, 0 67 | for duanluo, line_count in self.duanluo: 68 | draw.text((x, y), duanluo, fill=(0, 0, 0), font=ImgText.font) 69 | y += self.line_height * line_count 70 | bio = BytesIO() 71 | im.save(bio, format='PNG') 72 | base64_str = 'base64://' + base64.b64encode(bio.getvalue()).decode() 73 | mes = f"[CQ:image,file={base64_str}]" 74 | return mes 75 | 76 | def chooseID(IDdata): 77 | message = "" 78 | for i in range(0,len(IDdata)): 79 | message = message + "【" + str(i+1) + "】"+str(IDdata[i]["nickname"])+"("+judgeLevel(str(IDdata[i]["level"]["id"]))+")\n" 80 | message = message + "若列表内没有您要找的昵称,请将昵称补全以便于查找" 81 | return message 82 | 83 | 84 | def printBasicInfo(IDdata,room_level,num): 85 | message = "PS:本数据不包含金之间以下对局以及2019.11.29之前的对局\n" 86 | message = message + "昵称:" + str(IDdata["nickname"])+" " 87 | score = int(IDdata["level"]["score"])+int(IDdata["level"]["delta"]) 88 | message = message + processLevelInfo(score,str(IDdata["level"]["id"])) 89 | if num == "4": 90 | data_list = selectInfo(IDdata["id"],room_level) 91 | room = "四" 92 | else: 93 | data_list = select_triInfo(IDdata["id"], room_level) 94 | room = "三" 95 | if isinstance(data_list[0], urllib.error.URLError): 96 | message = message + "\n没有查询到在" + room + "人南的对局数据呢~\n" 97 | else: 98 | message = message + processBasicInfo(data_list[0], room_level, room + "人南",num)+"\n" 99 | if isinstance(data_list[2], urllib.error.URLError): 100 | message = message + "\n没有查询到在" + room + "人东的对局数据呢~\n" 101 | else: 102 | message = message + processBasicInfo(data_list[2], room_level, room + "人东",num)+"\n" 103 | pic = ImgText(message) 104 | return pic.draw_text() 105 | 106 | 107 | def printExtendInfo(IDdata,room_level,num): 108 | message = "PS:本数据不包含金之间以下对局以及2019.11.29之前的对局\n" 109 | message = message + "昵称:" + str(IDdata["nickname"]) + " " 110 | score = int(IDdata["level"]["score"]) + int(IDdata["level"]["delta"]) 111 | message = message + processLevelInfo(score, str(IDdata["level"]["id"])) 112 | if num == "4": 113 | data_list = selectInfo(IDdata["id"],room_level) 114 | room = "四" 115 | else: 116 | data_list = select_triInfo(IDdata["id"], room_level) 117 | room = "三" 118 | if isinstance(data_list[0], urllib.error.URLError): 119 | message = message + "\n没有查询到在"+ judgeRoom(room_level) + room + "人南的对局数据呢~\n" 120 | else: 121 | message = message + processBasicInfo(data_list[0], room_level, room +"人南",num) 122 | message = message + processExtendInfo(data_list[1], room_level, room +"人南") 123 | if isinstance(data_list[2], urllib.error.URLError): 124 | message = message + "\n没有查询到在"+ judgeRoom(room_level) + room +"人东的对局数据呢~\n" 125 | else: 126 | message = message + processBasicInfo(data_list[2], room_level, room +"人东",num) 127 | message = message + processExtendInfo(data_list[3], room_level, room +"人东") 128 | pic = ImgText(message) 129 | return pic.draw_text() 130 | 131 | def printRecordInfo(IDdata,num): 132 | message = "PS:本数据不包含金之间以下对局以及2019.11.29之前的对局\n" 133 | message = message + "昵称:" + str(IDdata["nickname"]) + " " 134 | score = int(IDdata["level"]["score"]) + int(IDdata["level"]["delta"]) 135 | message = message + processLevelInfo(score, str(IDdata["level"]["id"])) 136 | if num == 3: 137 | record = select_triRecord(IDdata["id"]) 138 | elif num == 4: 139 | record = selectRecord(IDdata["id"]) 140 | if isinstance(record, urllib.error.URLError): 141 | message = message + "没有查询到在该玩家近期的对局数据呢~\n" 142 | else: 143 | message = message + processRecordInfo(record,num) 144 | pic = ImgText(message) 145 | return pic.draw_text() 146 | 147 | def processExtendInfo(info,room_level,sessions): 148 | data = json.loads(info) 149 | message = "\n【" + judgeRoom(room_level)+ sessions + "进阶数据】\n" 150 | message = message + "和牌率:" + str(round(float(removeNull(data["和牌率"]))*100,2)) + "% " 151 | message = message + "自摸率:" + str(round(float(removeNull(data["自摸率"]))*100,2)) + "% " 152 | message = message + "默听率:" + str(round(float(removeNull(data["默听率"]))*100,2)) + "% " 153 | message = message + "放铳率:" + str(round(float(removeNull(data["放铳率"]))*100,2)) + "% \n" 154 | message = message + "副露率:" + str(round(float(removeNull(data["副露率"]))*100,2)) + "% " 155 | message = message + "立直率:" + str(round(float(removeNull(data["立直率"]))*100,2)) + "% " 156 | message = message + "流局率:" + str(round(float(removeNull(data["副露率"]))*100,2)) + "% " 157 | message = message + "流听率:" + str(round(float(removeNull(data["流听率"]))*100,2)) + "% \n" 158 | message = message + "一发率:" + str(round(float(removeNull(data["一发率"]))*100,2)) + "% " 159 | message = message + "里宝率:" + str(round(float(removeNull(data["里宝率"]))*100,2)) + "% " 160 | message = message + "先制率:" + str(round(float(removeNull(data["先制率"]))*100,2)) + "% " 161 | message = message + "追立率:" + str(round(float(removeNull(data["追立率"]))*100,2)) + "% \n" 162 | message = message + "平均打点:" + str(removeNull(data["平均打点"])) + " " 163 | message = message + "平均铳点:" + str(removeNull(data["平均铳点"])) + " " 164 | try: 165 | message = message + "最大连庄:" + str(removeNull(data["最大连庄"])) + " " 166 | except: 167 | message = message + "最大连庄:0 " 168 | message = message + "和了巡数:" + str(round(float(removeNull(data["和了巡数"])),2)) + " \n" 169 | return message 170 | 171 | def processBasicInfo(info,room_level,sessions,num): 172 | data = json.loads(info) 173 | message = "\n【" + judgeRoom(room_level)+ sessions + "基础数据】\n" 174 | message = message + "总场次:" + str(data["count"])+"\n" 175 | message = message + "一位率:" + str(round(float(data["rank_rates"][0])*100,2)) + "% " 176 | message = message + "二位率:" + str(round(float(data["rank_rates"][1])*100,2)) + "% \n" 177 | message = message + "三位率:" + str(round(float(data["rank_rates"][2])*100,2)) + "% " 178 | if num=="4": 179 | message = message + "四位率:" + str(round(float(data["rank_rates"][3])*100,2)) + "%" 180 | return message 181 | 182 | def processLevelInfo(score,level): 183 | message = "" 184 | intlevel = int(level) 185 | if score < 0: 186 | if intlevel % 10 ==1: 187 | intlevel = intlevel-98 188 | level = str(intlevel) 189 | else: 190 | level = str(intlevel-1) 191 | score = level_start(level) 192 | elif score >= level_max(level): 193 | if intlevel % 10 == 3: 194 | intlevel = intlevel+98 195 | if intlevel == 10601 or intlevel == 20601: 196 | intlevel = intlevel + 100 197 | level = str(intlevel) 198 | else: 199 | level = str(intlevel+1) 200 | score = level_start(level) 201 | message = message + "当前段位:" + judgeLevel(level)+" " 202 | if judgeLevel(level)[0:2] == "魂天": 203 | score = score / 100 204 | message = message + "当前pt数:" + str(score)+"\n" 205 | return message 206 | 207 | def processRecordInfo(record,num): 208 | data = json.loads(record) 209 | message = "\n该玩家最近的对局信息如下:\n" 210 | if len(data) < 5: 211 | count = len(data) 212 | else: 213 | count = 5 214 | for i in range(0,count): 215 | message = message + "\n【" + str(i+1) + "】牌谱ID:" + str(data[i]["uuid"]) +"\n" 216 | for j in range(0,num): 217 | message = message + str(data[i]["players"][j]["nickname"]) + "(" + str(data[i]["players"][j]["score"])+") " 218 | message = message + "\n" 219 | message = message + "对局开始时间:" + str(convertTime(data[i]["startTime"]))+" " 220 | message = message + "对局结束时间:" + str(convertTime(data[i]["endTime"]))+" \n" 221 | return message 222 | 223 | def judgeLevel(level): 224 | if level == "10203" or level == "20203": return "雀士三" 225 | elif level == "10301" or level == "20301": return "雀杰一" 226 | elif level == "10302" or level == "20302": return "雀杰二" 227 | elif level == "10303" or level == "20303": return "雀杰三" 228 | elif level == "10401" or level == "20401": return "雀豪一" 229 | elif level == "10402" or level == "20402": return "雀豪二" 230 | elif level == "10403" or level == "20403": return "雀豪三" 231 | elif level == "10501" or level == "20501": return "雀圣一" 232 | elif level == "10502" or level == "20502": return "雀圣二" 233 | elif level == "10503" or level == "20503": return "雀圣三" 234 | elif level[0:3] == "107" or level[0:3] == "207": return "魂天"+str(int(level[-2:])) 235 | 236 | def judgeRoom(room_level): 237 | if room_level == "0": return "总体" 238 | elif room_level == "1": return "金之间" 239 | elif room_level == "2": return "玉之间" 240 | elif room_level == "3": return "王座之间" 241 | 242 | def removeNull(data): 243 | if data == None: 244 | return "0" 245 | else: 246 | return data 247 | 248 | def convertTime(datatime): 249 | timeArray = time.localtime(datatime) 250 | Time = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) 251 | return Time 252 | 253 | def level_max(level): 254 | if level == "10203" or level == "20203": return 1000 255 | elif level == "10301" or level == "20301": return 1200 256 | elif level == "10302" or level == "20302": return 1400 257 | elif level == "10303" or level == "20303": return 2000 258 | elif level == "10401" or level == "20401": return 2800 259 | elif level == "10402" or level == "20402": return 3200 260 | elif level == "10403" or level == "20403": return 3600 261 | elif level == "10501" or level == "20501": return 4000 262 | elif level == "10502" or level == "20502": return 6000 263 | elif level == "10503" or level == "20503": return 9000 264 | elif level[0:3] == "107" or level[0:3] == "207": return 2000 265 | #elif level == "10601" or level == "20601": return 9999999 266 | 267 | def level_start(level): 268 | if level == "10203" or level == "20203": return 500 269 | elif level == "10301" or level == "20301": return 600 270 | elif level == "10302" or level == "20302": return 700 271 | elif level == "10303" or level == "20303": return 1000 272 | elif level == "10401" or level == "20401": return 1400 273 | elif level == "10402" or level == "20402": return 1600 274 | elif level == "10403" or level == "20403": return 1800 275 | elif level == "10501" or level == "20501": return 2000 276 | elif level == "10502" or level == "20502": return 3000 277 | elif level == "10503" or level == "20503": return 4500 278 | elif level[0:3] == "107" or level[0:3] == "207": return 1000 279 | #elif level == "10601" or level == "20601": return 10000 280 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bs4 2 | urllib3 3 | pillow 4 | ujson 5 | mahjong 6 | sqlitedict 7 | aiohttp 8 | -------------------------------------------------------------------------------- /resources/decoration/24K金棒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/24K金棒.jpg -------------------------------------------------------------------------------- /resources/decoration/huiye/和牌-恋之降临.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/huiye/和牌-恋之降临.jpg -------------------------------------------------------------------------------- /resources/decoration/huiye/桌布-恋之见证.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/huiye/桌布-恋之见证.jpg -------------------------------------------------------------------------------- /resources/decoration/huiye/牌背-恋之背影.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/huiye/牌背-恋之背影.jpg -------------------------------------------------------------------------------- /resources/decoration/huiye/立直-恋之箭矢.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/huiye/立直-恋之箭矢.jpg -------------------------------------------------------------------------------- /resources/decoration/saki1/和牌-花天月地.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki1/和牌-花天月地.jpg -------------------------------------------------------------------------------- /resources/decoration/saki1/和牌-龙卷雷霆.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki1/和牌-龙卷雷霆.jpg -------------------------------------------------------------------------------- /resources/decoration/saki1/桌布-赛前小憩.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki1/桌布-赛前小憩.jpg -------------------------------------------------------------------------------- /resources/decoration/saki1/牌背-艾托企鹅.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki1/牌背-艾托企鹅.jpg -------------------------------------------------------------------------------- /resources/decoration/saki1/立直棒-墨西哥卷饼.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki1/立直棒-墨西哥卷饼.jpg -------------------------------------------------------------------------------- /resources/decoration/saki2/和牌-未来视.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki2/和牌-未来视.jpg -------------------------------------------------------------------------------- /resources/decoration/saki2/和牌-高岭之花.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki2/和牌-高岭之花.jpg -------------------------------------------------------------------------------- /resources/decoration/saki2/桌布-清凉假日.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki2/桌布-清凉假日.jpg -------------------------------------------------------------------------------- /resources/decoration/saki2/牌背-摇曳气球.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki2/牌背-摇曳气球.jpg -------------------------------------------------------------------------------- /resources/decoration/saki2/立直棒-爱心便当.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/saki2/立直棒-爱心便当.jpg -------------------------------------------------------------------------------- /resources/decoration/一触即发.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/一触即发.jpg -------------------------------------------------------------------------------- /resources/decoration/出阵.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/出阵.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-KO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-KO.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-天罚.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-天罚.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-安可.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-安可.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-幽灵嗷嗷.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-幽灵嗷嗷.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-方舟反应堆.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-方舟反应堆.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-旋风.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-旋风.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-核心裂变.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-核心裂变.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-樱花.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-樱花.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-烈焰.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-烈焰.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-爆炎龙卷.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-爆炎龙卷.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-红玫瑰.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-红玫瑰.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-逆鳞.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-逆鳞.jpg -------------------------------------------------------------------------------- /resources/decoration/和牌-黑炎.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/和牌-黑炎.jpg -------------------------------------------------------------------------------- /resources/decoration/咸鱼立直棒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/咸鱼立直棒.jpg -------------------------------------------------------------------------------- /resources/decoration/大葱立直棒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/大葱立直棒.jpg -------------------------------------------------------------------------------- /resources/decoration/孔雀绿桌布.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/孔雀绿桌布.jpg -------------------------------------------------------------------------------- /resources/decoration/果绿牌背.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/果绿牌背.jpg -------------------------------------------------------------------------------- /resources/decoration/桌布-吃瓜.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/桌布-吃瓜.jpg -------------------------------------------------------------------------------- /resources/decoration/橘猫爪.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/橘猫爪.jpg -------------------------------------------------------------------------------- /resources/decoration/淡黄牌背.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/淡黄牌背.jpg -------------------------------------------------------------------------------- /resources/decoration/激斗.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/激斗.jpg -------------------------------------------------------------------------------- /resources/decoration/牌背-天然呆幽灵.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/牌背-天然呆幽灵.jpg -------------------------------------------------------------------------------- /resources/decoration/狗骨头立直棒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/狗骨头立直棒.jpg -------------------------------------------------------------------------------- /resources/decoration/猩红立直棒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/猩红立直棒.jpg -------------------------------------------------------------------------------- /resources/decoration/玫瑰红牌背.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/玫瑰红牌背.jpg -------------------------------------------------------------------------------- /resources/decoration/真剑胜负.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/真剑胜负.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-叮.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-叮.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-幻影.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-幻影.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-开场曲.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-开场曲.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-火焰.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-火焰.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-碎冰.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-碎冰.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-苍火.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-苍火.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-虚拟导航.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-虚拟导航.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-蝙蝠.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-蝙蝠.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-雷电环锁.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-雷电环锁.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-飞羽.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-飞羽.jpg -------------------------------------------------------------------------------- /resources/decoration/立直-龙腾.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直-龙腾.jpg -------------------------------------------------------------------------------- /resources/decoration/立直棒-仿生喵.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直棒-仿生喵.jpg -------------------------------------------------------------------------------- /resources/decoration/立直棒-小恶魔蝙蝠.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直棒-小恶魔蝙蝠.jpg -------------------------------------------------------------------------------- /resources/decoration/立直棒-应援棒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直棒-应援棒.jpg -------------------------------------------------------------------------------- /resources/decoration/立直棒-恋之反省.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直棒-恋之反省.jpg -------------------------------------------------------------------------------- /resources/decoration/立直棒-断恶.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直棒-断恶.jpg -------------------------------------------------------------------------------- /resources/decoration/立直棒-陨石法杖.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直棒-陨石法杖.jpg -------------------------------------------------------------------------------- /resources/decoration/立直棒-雪糕.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/立直棒-雪糕.jpg -------------------------------------------------------------------------------- /resources/decoration/紫罗兰桌布.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/紫罗兰桌布.jpg -------------------------------------------------------------------------------- /resources/decoration/莲藕紫桌布.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/decoration/莲藕紫桌布.jpg -------------------------------------------------------------------------------- /resources/gift/00-手工曲奇.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/00-手工曲奇.jpg -------------------------------------------------------------------------------- /resources/gift/01-蓝罐曲奇.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/01-蓝罐曲奇.jpg -------------------------------------------------------------------------------- /resources/gift/02-香喷喷曲奇.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/02-香喷喷曲奇.jpg -------------------------------------------------------------------------------- /resources/gift/03-怀旧掌机.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/03-怀旧掌机.jpg -------------------------------------------------------------------------------- /resources/gift/04-Twitch掌机.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/04-Twitch掌机.jpg -------------------------------------------------------------------------------- /resources/gift/05-次世代游戏机.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/05-次世代游戏机.jpg -------------------------------------------------------------------------------- /resources/gift/06-简易美术品.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/06-简易美术品.jpg -------------------------------------------------------------------------------- /resources/gift/07-精美挂画.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/07-精美挂画.jpg -------------------------------------------------------------------------------- /resources/gift/08-经典名画.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/08-经典名画.jpg -------------------------------------------------------------------------------- /resources/gift/09-美味果酒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/09-美味果酒.jpg -------------------------------------------------------------------------------- /resources/gift/10-香醇红酒.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/10-香醇红酒.jpg -------------------------------------------------------------------------------- /resources/gift/11-82年的拉菲.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/11-82年的拉菲.jpg -------------------------------------------------------------------------------- /resources/gift/12-普通的碎钻.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/12-普通的碎钻.jpg -------------------------------------------------------------------------------- /resources/gift/13-鸽子蛋宝石.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/13-鸽子蛋宝石.jpg -------------------------------------------------------------------------------- /resources/gift/14-海洋之心.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/14-海洋之心.jpg -------------------------------------------------------------------------------- /resources/gift/15-熊公仔.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/15-熊公仔.jpg -------------------------------------------------------------------------------- /resources/gift/16-熊公仔L.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/16-熊公仔L.jpg -------------------------------------------------------------------------------- /resources/gift/17-熊公仔XXL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/17-熊公仔XXL.jpg -------------------------------------------------------------------------------- /resources/gift/18-同人小册子.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/18-同人小册子.jpg -------------------------------------------------------------------------------- /resources/gift/19-简装同人志.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/19-简装同人志.jpg -------------------------------------------------------------------------------- /resources/gift/20-精美同人志.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/20-精美同人志.jpg -------------------------------------------------------------------------------- /resources/gift/21-朴素的小裙子.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/21-朴素的小裙子.jpg -------------------------------------------------------------------------------- /resources/gift/22-普通的小裙子.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/22-普通的小裙子.jpg -------------------------------------------------------------------------------- /resources/gift/23-华丽的小裙子.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/gift/23-华丽的小裙子.jpg -------------------------------------------------------------------------------- /resources/jades/光明宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/光明宝玉.jpg -------------------------------------------------------------------------------- /resources/jades/勇气宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/勇气宝玉.jpg -------------------------------------------------------------------------------- /resources/jades/希望宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/希望宝玉.jpg -------------------------------------------------------------------------------- /resources/jades/意志宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/意志宝玉.jpg -------------------------------------------------------------------------------- /resources/jades/慈爱宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/慈爱宝玉.jpg -------------------------------------------------------------------------------- /resources/jades/智慧宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/智慧宝玉.jpg -------------------------------------------------------------------------------- /resources/jades/纯真宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/纯真宝玉.jpg -------------------------------------------------------------------------------- /resources/jades/诚实宝玉.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/jades/诚实宝玉.jpg -------------------------------------------------------------------------------- /resources/person/七海礼奈.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/七海礼奈.png -------------------------------------------------------------------------------- /resources/person/三上千织.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/三上千织.png -------------------------------------------------------------------------------- /resources/person/九条璃雨.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/九条璃雨.png -------------------------------------------------------------------------------- /resources/person/二之宫花.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/二之宫花.png -------------------------------------------------------------------------------- /resources/person/五十岚阳菜.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/五十岚阳菜.png -------------------------------------------------------------------------------- /resources/person/八木唯.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/八木唯.png -------------------------------------------------------------------------------- /resources/person/凉宫杏树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/凉宫杏树.png -------------------------------------------------------------------------------- /resources/person/北见纱和子.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/北见纱和子.png -------------------------------------------------------------------------------- /resources/person/卡维.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/卡维.png -------------------------------------------------------------------------------- /resources/person/原村和.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/原村和.png -------------------------------------------------------------------------------- /resources/person/四宫辉夜.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/四宫辉夜.png -------------------------------------------------------------------------------- /resources/person/园城寺怜.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/园城寺怜.png -------------------------------------------------------------------------------- /resources/person/天江衣.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/天江衣.png -------------------------------------------------------------------------------- /resources/person/姬川响.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/姬川响.png -------------------------------------------------------------------------------- /resources/person/宫永咲.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/宫永咲.png -------------------------------------------------------------------------------- /resources/person/宫永照.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/宫永照.png -------------------------------------------------------------------------------- /resources/person/寺崎千穗理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/寺崎千穗理.png -------------------------------------------------------------------------------- /resources/person/小野寺七羽.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/小野寺七羽.png -------------------------------------------------------------------------------- /resources/person/小鸟游雏田.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/小鸟游雏田.png -------------------------------------------------------------------------------- /resources/person/抚子.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/抚子.png -------------------------------------------------------------------------------- /resources/person/新子憧.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/新子憧.png -------------------------------------------------------------------------------- /resources/person/早乙女芽亚里.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/早乙女芽亚里.png -------------------------------------------------------------------------------- /resources/person/早坂爱.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/早坂爱.png -------------------------------------------------------------------------------- /resources/person/柚.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/柚.png -------------------------------------------------------------------------------- /resources/person/桃喰绮罗莉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/桃喰绮罗莉.png -------------------------------------------------------------------------------- /resources/person/森川绫子.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/森川绫子.png -------------------------------------------------------------------------------- /resources/person/泽尼娅.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/泽尼娅.png -------------------------------------------------------------------------------- /resources/person/生志摩妄.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/生志摩妄.png -------------------------------------------------------------------------------- /resources/person/白石奈奈.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/白石奈奈.png -------------------------------------------------------------------------------- /resources/person/白银圭.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/白银圭.png -------------------------------------------------------------------------------- /resources/person/白银御行.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/白银御行.png -------------------------------------------------------------------------------- /resources/person/相原舞.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/相原舞.png -------------------------------------------------------------------------------- /resources/person/福姬.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/福姬.png -------------------------------------------------------------------------------- /resources/person/福路美穗子.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/福路美穗子.png -------------------------------------------------------------------------------- /resources/person/竹井久.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/竹井久.png -------------------------------------------------------------------------------- /resources/person/艾丽莎.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/艾丽莎.png -------------------------------------------------------------------------------- /resources/person/莎拉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/莎拉.png -------------------------------------------------------------------------------- /resources/person/藤本绮罗.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/藤本绮罗.png -------------------------------------------------------------------------------- /resources/person/藤田佳奈.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/藤田佳奈.png -------------------------------------------------------------------------------- /resources/person/蛇喰梦子.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/蛇喰梦子.png -------------------------------------------------------------------------------- /resources/person/赤木茂.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/赤木茂.png -------------------------------------------------------------------------------- /resources/person/辉夜姬.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/辉夜姬.png -------------------------------------------------------------------------------- /resources/person/雏桃.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/雏桃.png -------------------------------------------------------------------------------- /resources/person/鹫巢岩.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/resources/person/鹫巢岩.png -------------------------------------------------------------------------------- /screenshot/ControlRecord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/screenshot/ControlRecord.png -------------------------------------------------------------------------------- /screenshot/OrderRecord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/screenshot/OrderRecord.png -------------------------------------------------------------------------------- /screenshot/selectBasicInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/screenshot/selectBasicInfo.png -------------------------------------------------------------------------------- /screenshot/selectExtendInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/screenshot/selectExtendInfo.png -------------------------------------------------------------------------------- /screenshot/selectRecord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaiShengSheng/Majsoul_bot/7672aefb429b88b85669df9153ae97c1f52694b6/screenshot/selectRecord.png --------------------------------------------------------------------------------