Failed Conditions
Branch BonHowi (9d60b2)
by Bartosz
03:20
created

DatabaseCog.db_get_member_monsters()   A

Complexity

Conditions 1

Size

Total Lines 14
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 14
rs 9.7
c 0
b 0
f 0
cc 1
nop 2
1
import os
2
3
import discord
4
from discord.ext import commands, tasks
5
from discord_slash import cog_ext, SlashContext
6
from modules.get_settings import get_settings
7
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey, \
8
    BigInteger, update, select, DateTime, delete
9
from sqlalchemy.dialects.mysql import insert
10
from sqlalchemy.sql import func
11
import cogs.cogbase as cogbase
12
from datetime import datetime
13
import pandas as pd
14
15
metadata_obj = MetaData()
16
17
member = Table('member', metadata_obj,
18
               Column('id', BigInteger, primary_key=True),
19
               Column('name', String(50), nullable=False),
20
               Column('display_name', String(50), nullable=False)
21
               )
22
fk_member_id = "member.id"
23
24
warn = Table('warn', metadata_obj,
25
             Column('id', Integer, primary_key=True),
26
             Column('member_id', BigInteger, ForeignKey(fk_member_id)),
27
             Column('reason', String(120), nullable=False),
28
             Column('date', DateTime, nullable=False),
29
             )
30
31
coords = Table('coords', metadata_obj,
32
               Column('id', Integer, primary_key=True),
33
               Column('coords', String(100), nullable=False),
34
               Column('monster_type', String(20), nullable=False)
35
               )
36
37
spots = Table('spots', metadata_obj,
38
              Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
39
              Column('legendary', Integer, default=0),
40
              Column('rare', Integer, default=0),
41
              Column('common', Integer, default=0),
42
              Column('event1', Integer, default=0),
43
              Column('event2', Integer, default=0)
44
              )
45
46
spots_temp = Table('spots_temp', metadata_obj,
47
                   Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
48
                   Column('legendary', Integer, default=0),
49
                   Column('rare', Integer, default=0),
50
                   Column('common', Integer, default=0),
51
                   Column('event1', Integer, default=0),
52
                   Column('event2', Integer, default=0)
53
                   )
54
55
spots_lege = Table('spots_lege', metadata_obj,
56
                   Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
57
                   Column('AncientLeshen', Integer, default=0),
58
                   Column('Archgriffin', Integer, default=0),
59
                   Column('CopperWyvern', Integer, default=0),
60
                   Column('D\'jinni', Integer, default=0),
61
                   Column('DungShaelmaar', Integer, default=0),
62
                   Column('Erynia', Integer, default=0),
63
                   Column('Frightener', Integer, default=0),
64
                   Column('GraphiteSlyzard', Integer, default=0),
65
                   Column('GrimHag', Integer, default=0),
66
                   Column('Hym', Integer, default=0),
67
                   Column('IceElemental', Integer, default=0),
68
                   Column('IceGiant', Integer, default=0),
69
                   Column('IceTroll', Integer, default=0),
70
                   Column('Katakan', Integer, default=0),
71
                   Column('MottledGarkain', Integer, default=0),
72
                   Column('Penitent', Integer, default=0),
73
                   Column('PlagueMaiden', Integer, default=0),
74
                   Column('Sandcrab', Integer, default=0),
75
                   Column('SilverBasilisk', Integer, default=0),
76
                   Column('SwampHag', Integer, default=0),
77
                   Column('TarryChort', Integer, default=0),
78
                   Column('Tormented', Integer, default=0),
79
                   Column('Ulfhedinn', Integer, default=0),
80
                   Column('UnseenElder', Integer, default=0),
81
                   Column('WaterDevil', Integer, default=0),
82
                   Column('WhiteStriga', Integer, default=0)
83
                   )
84
85
spots_rare = Table('spots_rare', metadata_obj,
86
                   Column('member_id', BigInteger, ForeignKey(fk_member_id), primary_key=True),
87
                   Column('Beann\'She', Integer, default=0),
88
                   Column('BlueForktail', Integer, default=0),
89
                   Column('Bruxa', Integer, default=0),
90
                   Column('Burier', Integer, default=0),
91
                   Column('Cockatrice', Integer, default=0),
92
                   Column('DepthLurker', Integer, default=0),
93
                   Column('Devourer', Integer, default=0),
94
                   Column('DrownedDead', Integer, default=0),
95
                   Column('EndregaCharger', Integer, default=0),
96
                   Column('EndregaWarrior', Integer, default=0),
97
                   Column('Farbaut', Integer, default=0),
98
                   Column('FireElemental', Integer, default=0),
99
                   Column('GarkainAlpha', Integer, default=0),
100
                   Column('Gernichora', Integer, default=0),
101
                   Column('GraySlyzard', Integer, default=0),
102
                   Column('GreenHarpy', Integer, default=0),
103
                   Column('Grimnir', Integer, default=0),
104
                   Column('Grottore', Integer, default=0),
105
                   Column('Howler', Integer, default=0),
106
                   Column('IgnisFatuus', Integer, default=0),
107
                   Column('KikimoreWarrior', Integer, default=0),
108
                   Column('Leshen', Integer, default=0),
109
                   Column('LeshenHound', Integer, default=0),
110
                   Column('Liho', Integer, default=0),
111
                   Column('Lycanthrope', Integer, default=0),
112
                   Column('MagmaTroll', Integer, default=0),
113
                   Column('NekkerShaman', Integer, default=0),
114
                   Column('Nightmare', Integer, default=0),
115
                   Column('NightSuccubus', Integer, default=0),
116
                   Column('Putrifier', Integer, default=0),
117
                   Column('RoyalFoglet', Integer, default=0),
118
                   Column('RoyalNekker', Integer, default=0),
119
                   Column('RoyalWyvern', Integer, default=0),
120
                   Column('RussetShaelmaar', Integer, default=0),
121
                   Column('Scurver', Integer, default=0),
122
                   Column('Shrieker', Integer, default=0),
123
                   Column('SpottedAlghoul', Integer, default=0),
124
                   Column('StoneGolem', Integer, default=0),
125
                   Column('Striga', Integer, default=0),
126
                   Column('SylvanDearg', Integer, default=0),
127
                   Column('Wailwraith', Integer, default=0)
128
                   )
129
130
131
class DatabaseCog(cogbase.BaseCog):
132
    user = get_settings("DB_U")
133
    password = get_settings("DB_P")
134
    engine = create_engine(f"mysql+mysqldb://{user}:{password}@localhost/server_database?charset=utf8mb4")
135
    metadata_obj.create_all(engine)
136
    conn = None
137
138
    def __init__(self, base):
139
        super().__init__(base)
140
141
        # Connect to database
142
        self.engine = DatabaseCog.engine
143
        metadata_obj.create_all(self.engine)
144
        self.db_update_loop.start()
145
        self.conn = DatabaseCog.conn
146
147
    def cog_unload(self) -> None:
148
        self.db_update_loop.cancel()
149
150
    # ----- BASE DATABASE OPERATIONS -----
151
152
    # Add or update member in member table
153
    def db_add_update_member(self, guild_member) -> None:
154
        self.conn = self.engine.connect()
155
        stmt = insert(member).values(
156
            id=guild_member.id, name=guild_member.name,
157
            display_name=guild_member.display_name)
158
        do_update_stmt = stmt.on_duplicate_key_update(
159
            name=stmt.inserted.name, display_name=stmt.inserted.display_name
160
        )
161
        self.conn.execute(do_update_stmt)
162
        self.conn.close()
163
164
    # Add or update spots in spots table
165
    def db_add_update_spots(self, spots_table, guild_member) -> None:
166
        self.conn = self.engine.connect()
167
        stmt = insert(spots_table).values(
168
            member_id=guild_member.id)
169
        do_update_stmt = stmt.on_duplicate_key_update(member_id=stmt.inserted.member_id)
170
        self.conn.execute(do_update_stmt)
171
        self.conn.close()
172
173
    # Add or refresh all guild members and spots to database
174
    async def db_update(self) -> None:
175
        self.conn = self.engine.connect()
176
        guild = self.bot.get_guild(self.bot.guild[0])
177
        self.create_log_msg(f"Refreshing member and spots tables")
178
        for guild_member in guild.members:
179
            # Member tables
180
            self.db_add_update_member(guild_member)
181
            # Spots tables
182
            self.db_add_update_spots(spots, guild_member)
183
            self.db_add_update_spots(spots_temp, guild_member)
184
            self.db_add_update_spots(spots_lege, guild_member)
185
            self.db_add_update_spots(spots_rare, guild_member)
186
        self.create_log_msg(f"Member and spots tables refreshed")
187
        self.conn.close()
188
189
    @tasks.loop(hours=12)
190
    async def db_update_loop(self) -> None:
191
        await self.db_update()
192
        await self.db_backup_database()
193
194
    @db_update_loop.before_loop
195
    async def before_db_update_loop(self) -> None:
196
        self.create_log_msg(f"Waiting until Bot is ready")
197
        await self.bot.wait_until_ready()
198
199
    # Add member to database on member join
200
    @commands.Cog.listener()
201
    async def on_member_join(self, guild_member) -> None:
202
        self.db_add_update_member(guild_member)
203
        self.db_add_update_spots(spots, guild_member)
204
        self.db_add_update_spots(spots_temp, guild_member)
205
        self.db_add_update_spots(spots_lege, guild_member)
206
        self.db_add_update_spots(spots_rare, guild_member)
207
208
    # Backup database
209
    async def db_backup_database(self) -> None:
210
        now = datetime.now()
211
        cmd = f"mysqldump -u {get_settings('DB_U')} " \
212
              f"--result-file=database_backup/backup-{now.strftime('%m-%d-%Y')}.sql " \
213
              f"-p{get_settings('DB_P')} server_database"
214
        os.system(cmd)
215
        self.create_log_msg(f"Database backed up")
216
217
    # ----- SPOTTING OPERATIONS -----
218
219
    # Update spots tables
220
    @classmethod
221
    async def db_count_spot(cls, _id: int, monster_type: str, monster_name: str) -> None:
222
        cls.conn = cls.engine.connect()
223
        cls.db_count_spot_table(spots, _id, monster_type, monster_name, False)
224
        cls.db_count_spot_table(spots_temp, _id, monster_type, monster_name, True)
225
        cls.conn.close()
226
227
    @classmethod
228
    def db_count_spot_table(cls, table, _id: int, monster_type: str, monster_name: str,
229
                            temp_table: bool = True) -> None:
230
        cls.conn = cls.engine.connect()
231
        stmt = select(table.c.member_id, table.c.legendary, table.c.rare, table.c.common,
232
                      table.c.event1,
233
                      table.c.event2).where(
234
            table.c.member_id == _id)
235
        result = cls.conn.execute(stmt)
236
        cls.conn.close()
237
        counter = 0
238
        for nr_of_kills in result.columns(monster_type, 'legendary'):
239
            counter = nr_of_kills[0]
240
        if monster_type == "event1":
241
            values = cls.db_count_spot_table_event(table, _id, monster_type, counter)
242
        else:
243
            values = {f"{monster_type}": counter + 1}
244
        stmt = update(table).where(table.c.member_id == _id).values(values)
245
        cls.conn = cls.engine.connect()
246
        cls.conn.execute(stmt)
247
        cls.conn.close()
248
        if not temp_table:
249
            cls.db_count_monster_spot(_id, monster_type, monster_name)
250
251
    @classmethod
252
    def db_count_monster_spot(cls, _id: int, monster_type: str, monster_name: str) -> None:
253
        bot_id = 881167775635234877
254
        if monster_type == "legendary":
255
            values_lege_member = cls.db_count_spot_table_monster(spots_lege, _id, monster_name)
256
            stmt = update(spots_lege).where(spots_lege.c.member_id == _id).values(values_lege_member)
257
            cls.conn = cls.engine.connect()
258
            cls.conn.execute(stmt)
259
            cls.conn.close()
260
            values_lege_total = cls.db_count_spot_table_monster(spots_lege, bot_id, monster_name)
261
            stmt = update(spots_lege).where(spots_lege.c.member_id == bot_id).values(values_lege_total)
262
            cls.conn = cls.engine.connect()
263
            cls.conn.execute(stmt)
264
            cls.conn.close()
265
        elif monster_type == "rare":
266
            values_rare_member = cls.db_count_spot_table_monster(spots_rare, _id, monster_name)
267
            stmt = update(spots_rare).where(spots_rare.c.member_id == _id).values(values_rare_member)
268
            cls.conn = cls.engine.connect()
269
            cls.conn.execute(stmt)
270
            cls.conn.close()
271
            values_rare_total = cls.db_count_spot_table_monster(spots_rare, bot_id, monster_name)
272
            stmt = update(spots_rare).where(spots_rare.c.member_id == bot_id).values(values_rare_total)
273
            cls.conn = cls.engine.connect()
274
            cls.conn.execute(stmt)
275
            cls.conn.close()
276
277
    @classmethod
278
    def db_count_spot_table_event(cls, table, _id, monster_type: str, counter: int) -> dict:
279
        cls.conn = cls.engine.connect()
280
        stmt = select(table.c.member_id, table.c.legendary, table.c.rare, table.c.common,
281
                      table.c.event1,
282
                      table.c.event2).where(
283
            table.c.member_id == _id)
284
        result = cls.conn.execute(stmt)
285
        counter_leg = 0
286
        for nr_of_kills_leg in result.columns('legendary'):
287
            counter_leg = nr_of_kills_leg[0]
288
        values = {f"{monster_type}": counter + 1, "legendary": counter_leg + 1}
289
        cls.conn.close()
290
        return values
291
292
    @classmethod
293
    def db_count_spot_table_monster(cls, table, guild_member_id: int, monster_name: str) -> dict:
294
        cls.conn = cls.engine.connect()
295
        stmt = select(table).where(table.c.member_id == guild_member_id)
296
        result = cls.conn.execute(stmt)
297
        counter = 0
298
        for nr_of_kills_leg in result.columns(f"{monster_name}"):
299
            counter = nr_of_kills_leg[0]
300
        values = {f"{monster_name}": counter + 1}
301
        cls.conn.close()
302
        return values
303
304
    # Save coords from spotting channels to database
305
    @classmethod
306
    async def db_save_coords(cls, coord: str, monster_type: str) -> None:
307
        cls.conn = cls.engine.connect()
308
        stmt = insert(coords).values(coords=coord, monster_type=monster_type)
309
        cls.conn.execute(stmt)
310
        cls.conn.close()
311
312
    # Clear data from spots_temp table(for events etc)
313
    @classmethod
314
    async def db_clear_spots_temp_table(cls) -> None:
315
        cls.conn = cls.engine.connect()
316
        stmt = delete(spots_temp)
317
        cls.conn.execute(stmt)
318
        cls.conn.close()
319
320
    # ----- LEADERBOARD OPERATIONS -----
321
322
    # Return total spotting stats
323
    @classmethod
324
    async def db_get_total_spots_df(cls, member_id: int, leaderboard_type: int) -> pd.DataFrame:
325
        df = pd.DataFrame
326
        cls.conn = cls.engine.connect()
327
        if leaderboard_type == 1:
328
            stmt = select(spots_lege)
329
            df = pd.read_sql(stmt, cls.conn)
330
        elif leaderboard_type == 0:
331
            stmt = select(spots_rare)
332
            df = pd.read_sql(stmt, cls.conn)
333
        df = df.loc[df['member_id'] == member_id]
334
        cls.conn.close()
335
        return df
336
337
    # Return all members' spots
338
    @classmethod
339
    async def db_get_spots_df(cls) -> pd.DataFrame:
340
        cls.conn = cls.engine.connect()
341
        stmt = select(spots.c.member_id, member.c.display_name, spots.c.legendary, spots.c.rare,
342
                      spots.c.common, spots.c.event1, spots.c.event2
343
                      ).select_from(member
344
                                    ).join(spots, member.c.id == spots.c.member_id)
345
        cls.conn.execute(stmt)
346
        df = pd.read_sql(stmt, cls.conn)
347
        cls.conn.close()
348
        return df
349
350
    @classmethod
351
    async def db_get_common_sum(cls) -> int:
352
        cls.conn = cls.engine.connect()
353
        stmt = select(func.sum(spots.c.common).label("sum"))
354
        result = cls.conn.execute(stmt)
355
        sum_common = 0
356
        for nr_of_kills_leg in result.columns("sum"):
357
            sum_common = nr_of_kills_leg[0]
358
        cls.conn.close()
359
        return sum_common
360
361
    @classmethod
362
    async def db_get_monster_spots_df(cls) -> pd.DataFrame:
363
        # TODO: Why does tables join not work?
364
        cls.conn = cls.engine.connect()
365
        stmt = select(spots_lege)
366
        cls.conn.execute(stmt)
367
        df_lege = pd.read_sql(stmt, cls.conn)
368
        cls.conn.close()
369
        cls.conn = cls.engine.connect()
370
        stmt = select(spots_rare)
371
        cls.conn.execute(stmt)
372
        df_rare = pd.read_sql(stmt, cls.conn)
373
        cls.conn.close()
374
        df_monsters_merged = pd.merge(df_lege, df_rare, on=["member_id"])
375
        return df_monsters_merged
376
377
    @classmethod
378
    async def db_get_member_names(cls) -> pd.DataFrame:
379
        cls.conn = cls.engine.connect()
380
        stmt = select(member.c.id.label("member_id"), member.c.display_name)
381
        cls.conn.execute(stmt)
382
        df_member_names = pd.read_sql(stmt, cls.conn)
383
        cls.conn.close()
384
        return df_member_names
385
386
    # ----- WARN OPERATIONS -----
387
388
    # Add member's warn to database
389
    @classmethod
390
    async def db_add_warn(cls, guild_member_id: int, reason: str) -> None:
391
        cls.conn = cls.engine.connect()
392
        stmt = insert(warn).values(member_id=guild_member_id, reason=reason, date=datetime.now())
393
        cls.conn.execute(stmt)
394
        cls.conn.close()
395
396
    # Get member's warns from database
397
    @classmethod
398
    async def db_get_warns(cls, guild_member_id: int) -> tuple:
399
        cls.conn = cls.engine.connect()
400
        stmt = select(warn.c.reason, warn.c.date).select_from(member).join(warn, member.c.id == warn.c.member_id).where(
401
            member.c.id == guild_member_id)
402
        result = cls.conn.execute(stmt)
403
        date_warn = []
404
        counter = 0
405
        for warns in result.columns("reason", "date"):
406
            reason_with_date = [warns[1], warns[0]]
407
            date_warn.append(reason_with_date)
408
            counter += 1
409
        warns_list = [': \t'.join([str(elem) for elem in sublist]) for sublist in date_warn]
410
        cls.conn.close()
411
        return warns_list, counter
412
413
    # Remove all member's warns
414
    @classmethod
415
    async def db_remove_warns(cls, guild_member: int) -> None:
416
        cls.conn = cls.engine.connect()
417
        stmt = delete(warn).where(warn.c.member_id == guild_member)
418
        cls.conn.execute(stmt)
419
        cls.conn.close()
420
421
    # ----- MEMBER OPERATIONS -----
422
423
    # Return member's spots
424
    @classmethod
425
    async def db_get_member_stats(cls, guild_member: int) -> pd.DataFrame:
426
        cls.conn = cls.engine.connect()
427
        stmt = select(spots.c.member_id, member.c.display_name, spots.c.legendary, spots.c.rare, spots.c.common
428
                      ).select_from(member).join(spots,
429
                                                 member.c.id == spots.c.member_id)\
430
            .where(spots.c.member_id == guild_member)
431
        df = pd.read_sql(stmt, cls.conn)
432
        cls.conn.close()
433
        return df
434
435
    @classmethod
436
    async def db_get_member_monsters(cls, guild_member: int) -> pd.DataFrame:
437
        cls.conn = cls.engine.connect()
438
        stmt = select(spots_lege).where(spots_lege.c.member_id == guild_member)
439
        cls.conn.execute(stmt)
440
        df_lege = pd.read_sql(stmt, cls.conn)
441
        cls.conn.close()
442
        cls.conn = cls.engine.connect()
443
        stmt = select(spots_rare).where(spots_rare.c.member_id == guild_member)
444
        cls.conn.execute(stmt)
445
        df_rare = pd.read_sql(stmt, cls.conn)
446
        cls.conn.close()
447
        df_monsters_merged = pd.merge(df_lege, df_rare, on=["member_id"])
448
        return df_monsters_merged
449
450
    @cog_ext.cog_slash(name="changeMemberSpots", guild_ids=cogbase.GUILD_IDS,
451
                       description="Change member spotting stats",
452
                       default_permission=False,
453
                       permissions=cogbase.PERMISSION_ADMINS)
454
    async def change_member_spots(self, ctx: SlashContext, user: discord.Member, spot_type: str, number: int) -> None:
455
        self.conn = self.engine.connect()
456
        stmt = f"""UPDATE server_database.spots SET {spot_type} = {number} """ \
457
               f"""WHERE (member_id = {user.id});"""
458
        self.conn.execute(stmt)
459
        await ctx.send(f"{user.display_name} spots changed", hidden=True)
460
        self.conn.close()
461
462
    # ----- COORDS OPERATIONS -----
463
464
    # Return coords
465
    @classmethod
466
    async def db_get_coords(cls) -> pd.DataFrame:
467
        cls.conn = cls.engine.connect()
468
        stmt = select(coords.c.id, coords.c.coords, coords.c.monster_type).select_from(coords)
469
        df = pd.read_sql(stmt, cls.conn)
470
        cls.conn.close()
471
        return df
472
473
474
def setup(bot: commands.Bot) -> None:
475
    bot.add_cog(DatabaseCog(bot))
476