├── 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 |   
 3 | 
 4 | Majsoul_bot
 5 | 
 6 | 🌸 天麻联动二号卡池已更新 🌸 
 7 | 
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 |  
73 | ### 详细数据查询
74 |  
75 | ### 近期对局查询
76 |  
77 | ### 雀魂对局订阅
78 | 
79 | ### 订阅的开启与删除
80 | 
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
--------------------------------------------------------------------------------