1
+ # coding=utf-8
1
2
import asyncio
3
+ import contextlib
2
4
import aiohttp
3
5
import psutil
6
+ import logging
7
+
4
8
from discord import Client as DiscordClient
5
- from . import exceptions
6
- import contextlib
9
+ from typing import Optional , Coroutine , Union , List , Dict , Iterable
10
+ from discord .ext .commands import Context
11
+
12
+ # this could be relative, but apparently Python doesn't like it
13
+ from statcord import exceptions
7
14
8
15
9
16
class Client :
10
17
"""Client for using the statcord API"""
18
+
11
19
def __init__ (self , bot , token , ** kwargs ):
12
- if not isinstance (bot ,DiscordClient ):
20
+ self .logger = logging .getLogger ("statcord" )
21
+ self .logging_level = kwargs .get ("logging_level" , logging .WARNING )
22
+ self .logger .setLevel (self .logging_level )
23
+
24
+ if not isinstance (bot , DiscordClient ):
13
25
raise TypeError (f"Expected class deriving from discord.Client for arg bot not { bot .__class__ .__qualname__ } " )
14
- if not isinstance (token ,str ):
26
+ if not isinstance (token , str ):
15
27
raise TypeError (f"Expected str for arg token not { token .__class__ .__qualname__ } " )
16
28
17
- self .bot = bot
18
- self .key = token
19
- self .base = "https://statcord.com/logan/"
20
- self .session = aiohttp .ClientSession (loop = bot .loop )
29
+ self .bot : DiscordClient = bot
30
+ self .key : str = token
31
+ self .base : str = "https://statcord.com/logan/"
32
+ self .session : aiohttp . ClientSession = aiohttp .ClientSession (loop = bot .loop )
21
33
34
+ self .mem : Optional [bool ] = None
22
35
if kwargs .get ("mem" ):
23
- if isinstance (kwargs ["mem" ],bool ):
24
- self .mem = kwargs ["mem" ]
36
+ if isinstance (kwargs ["mem" ], bool ):
37
+ self .mem = kwargs ["mem" ]
25
38
else :
26
- raise TypeError (f"Memory config : expected type bool not { kwargs ['mem' ].__class__ .__qualname__ } " )
39
+ raise TypeError (f"Memory config: expected type bool not { kwargs ['mem' ].__class__ .__qualname__ } . " )
27
40
else :
28
- self .mem = True
41
+ self .mem = True
29
42
43
+ self .cpu : Optional [bool ] = None
30
44
if kwargs .get ("cpu" ):
31
- if isinstance (kwargs ["cpu" ],bool ):
32
- self .cpu = kwargs ["cpu" ]
45
+ if isinstance (kwargs ["cpu" ], bool ):
46
+ self .cpu = kwargs ["cpu" ]
33
47
else :
34
- raise TypeError (f"CPU config : expected type bool not { kwargs ['cpu' ].__class__ .__qualname__ } " )
48
+ raise TypeError (f"CPU config: expected type bool not { kwargs ['cpu' ].__class__ .__qualname__ } " )
35
49
else :
36
50
self .cpu = True
37
51
52
+ self .bandwidth : Optional [bool ] = None
38
53
if kwargs .get ("bandwidth" ):
39
- if isinstance (kwargs ["bandwidth" ],bool ):
40
- self .bandwidth = kwargs ["bandwidth" ]
54
+ if isinstance (kwargs ["bandwidth" ], bool ):
55
+ self .bandwidth = kwargs ["bandwidth" ]
41
56
else :
42
- raise TypeError ("Bandwidth config : expected type bool" )
57
+ raise TypeError ("Bandwidth config: expected type bool" )
43
58
else :
44
59
self .bandwidth = True
45
60
61
+ self .debug : Optional [bool ] = None
46
62
if kwargs .get ("debug" ):
47
- if isinstance (kwargs ["debug" ],bool ):
48
- self .debug = kwargs ["debug" ]
63
+ if isinstance (kwargs ["debug" ], bool ):
64
+ self .debug = kwargs ["debug" ]
49
65
else :
50
- raise TypeError (f"Debug config : expected type bool not { kwargs ['debug' ].__class__ .__qualname__ } " )
66
+ raise TypeError (f"Debug config: expected type bool not { kwargs ['debug' ].__class__ .__qualname__ } " )
51
67
else :
52
68
self .debug = False
53
69
54
- self .custom1 = kwargs .get ("custom1" ) or False
55
- self .custom2 = kwargs .get ("custom2" ) or False
56
- self .active = []
57
- self .commands = 0
58
- self .popular = []
59
- self .previous_bandwidth = psutil .net_io_counters ().bytes_sent + psutil .net_io_counters ().bytes_recv
60
- psutil .cpu_percent ()
70
+ self .custom1 : Optional [Coroutine ] = kwargs .get ("custom1" ) or None
71
+ self .custom2 : Optional [Coroutine ] = kwargs .get ("custom2" ) or None
72
+ self .active : List [int ] = []
73
+ self .commands : int = 0
74
+ self .popular : List [Dict [str , Union [str , int ]]] = []
75
+ self .previous_bandwidth : int = psutil .net_io_counters ().bytes_sent + psutil .net_io_counters ().bytes_recv
61
76
62
- if self .debug :
63
- print ("Statcord debug mode enabled" )
77
+ self .logger .debug ("Statcord debug mode enabled" )
64
78
65
- def __headers (self ):
79
+ @staticmethod
80
+ def __headers () -> Dict [str , str ]:
66
81
return {'Content-Type' : 'application/json' }
67
82
83
+ # noinspection SpellCheckingInspection
68
84
async def __handle_response (self , res : aiohttp .ClientResponse ) -> dict :
69
85
try :
70
86
msg = await res .json () or {}
71
87
except aiohttp .ContentTypeError :
72
88
msg = await res .text ()
89
+ self .logger .debug (f"Handling response ({ res !r} ): { msg !s} " )
73
90
status = res .status
74
91
if status == 200 :
92
+ self .logger .debug (f"Code 200 OK" )
75
93
return msg
76
94
elif status == 429 :
77
- raise exceptions .TooManyRequests (status ,msg ,int (msg .get ("wait" )))
95
+ self .logger .debug (f"Code 429 Too Many Requests: ratelimited for { msg .get ('timeleft' )} " )
96
+ raise exceptions .TooManyRequests (status , msg , int (msg .get ("timeleft" )))
78
97
else :
79
- raise exceptions .RequestFailure (status = status ,response = msg )
80
-
81
- return msg
98
+ self .logger .debug (f"Code { status } " )
99
+ raise exceptions .RequestFailure (status = status , response = msg )
82
100
83
101
@property
84
- def servers (self ):
102
+ def servers (self ) -> str :
85
103
return str (len (self .bot .guilds ))
86
104
87
105
@property
88
- def _user_counter (self ):
106
+ def _user_counter (self ) -> Iterable [ int ] :
89
107
for g in self .bot .guilds :
90
108
with contextlib .suppress (AttributeError ):
91
109
yield g .member_count
92
110
93
111
@property
94
- def users (self ):
112
+ def users (self ) -> str :
95
113
return str (sum (self ._user_counter ))
96
114
97
- async def post_data (self ):
98
- id = str (self .bot .user .id )
115
+ async def post_data (self ) -> None :
116
+ self .logger .debug ("Got request to post data." )
117
+ bot_id = str (self .bot .user .id )
99
118
commands = str (self .commands )
100
119
101
120
if self .mem :
102
121
mem = psutil .virtual_memory ()
103
- memactive = str (mem .used )
104
- memload = str (mem .percent )
122
+ mem_used = str (mem .used )
123
+ mem_load = str (mem .percent )
105
124
else :
106
- memactive = "0"
107
- memload = "0"
125
+ mem_used = "0"
126
+ mem_load = "0"
108
127
109
128
if self .cpu :
110
- cpuload = str (psutil .cpu_percent ())
129
+ cpu_load = str (psutil .cpu_percent ())
111
130
else :
112
- cpuload = "0"
131
+ cpu_load = "0"
113
132
114
133
if self .bandwidth :
115
134
current_bandwidth = psutil .net_io_counters ().bytes_sent + psutil .net_io_counters ().bytes_recv
@@ -119,76 +138,76 @@ async def post_data(self):
119
138
bandwidth = "0"
120
139
121
140
if self .custom1 :
141
+ # who knows why PyCharm gets annoyed there /shrug
142
+ # noinspection PyCallingNonCallable
122
143
custom1 = str (await self .custom1 ())
123
144
else :
124
145
custom1 = "0"
125
146
126
147
if self .custom2 :
148
+ # who knows why PyCharm gets annoyed there /shrug
149
+ # noinspection PyCallingNonCallable
127
150
custom2 = str (await self .custom2 ())
128
151
else :
129
152
custom2 = "0"
130
153
154
+ # noinspection SpellCheckingInspection
131
155
data = {
132
- "id" :id ,
133
- "key" :self .key ,
134
- "servers" :self .servers ,
135
- "users" :self .users ,
136
- "commands" :commands ,
137
- "active" :self .active ,
138
- "popular" :self .popular ,
139
- "memactive" :memactive ,
140
- "memload" :memload ,
141
- "cpuload" :cpuload ,
142
- "bandwidth" :bandwidth ,
143
- "custom1" :custom1 ,
144
- "custom2" :custom2 ,
156
+ "id" : bot_id ,
157
+ "key" : self .key ,
158
+ "servers" : self .servers ,
159
+ "users" : self .users ,
160
+ "commands" : commands ,
161
+ "active" : self .active ,
162
+ "popular" : self .popular ,
163
+ "memactive" : mem_used ,
164
+ "memload" : mem_load ,
165
+ "cpuload" : cpu_load ,
166
+ "bandwidth" : bandwidth ,
167
+ "custom1" : custom1 ,
168
+ "custom2" : custom2 ,
145
169
}
146
- if self .debug :
147
- print ("Posting data" )
148
- print (data )
170
+ self .logger .debug (f"Posting stats: { data !s} " )
149
171
self .active = []
150
172
self .commands = 0
151
173
self .popular = []
152
174
153
175
async with self .session .post (url = self .base + "stats" , json = data , headers = self .__headers ()) as resp :
154
- res = await self .__handle_response (resp )
155
- if self .debug :
156
- print (res )
176
+ await self .__handle_response (resp )
157
177
158
- def start_loop (self ):
178
+ def start_loop (self ) -> None :
159
179
self .bot .loop .create_task (self .__loop ())
160
180
161
- def command_run (self ,ctx ) :
181
+ def command_run (self , ctx : Context ) -> None :
162
182
self .commands += 1
163
183
if ctx .author .id not in self .active :
164
184
self .active .append (ctx .author .id )
165
185
166
186
command = ctx .command .name
167
- found = False
168
- popular = list (self .popular )
169
- self .popular = []
170
- for cmd in popular :
171
- if cmd ["name" ] == command :
172
- found = True
173
- cmd ["count" ] = str (int (cmd ["count" ]) + 1 )
174
- self .popular .append (cmd )
175
-
176
- if not found :
177
- self .popular .append ({"name" :command ,"count" :"1" })
178
-
179
- async def __loop (self ):
187
+ self .logger .debug (f"Command { command } has been run by { ctx .author .id } " )
188
+ for cmd in filter (lambda x : x ["name" ] == command , self .popular ):
189
+ cmd ["count" ] = str (int (cmd ["count" ]) + 1 )
190
+ break
191
+ else :
192
+ self .popular .append ({"name" : command , "count" : "1" })
193
+
194
+ async def __loop (self ) -> None :
180
195
"""
181
196
The internal loop used for automatically posting server/guild count stats
182
197
"""
183
198
await self .bot .wait_until_ready ()
184
199
if self .debug :
185
- print ("Statcord Auto Post has started!" )
200
+ self . logger . debug ("Statcord Auto Post has started!" )
186
201
while not self .bot .is_closed ():
202
+ self .logger .debug ("Posting stats..." )
187
203
try :
188
204
await self .post_data ()
189
205
except Exception as e :
206
+ self .logger .debug ("Got error, dispatching error handlers." )
190
207
await self .on_error (e )
208
+ else :
209
+ self .logger .debug ("Posted stats successfully." )
191
210
await asyncio .sleep (60 )
192
211
193
- async def on_error (self ,error ) :
194
- print ( f "Statcord posting exception occured: { error . __class__ . __qualname__ } - { error } " )
212
+ async def on_error (self , error : BaseException ) -> None :
213
+ self . logger . exception ( "Statcord posting exception occurred." , exc_info = error )
0 commit comments