-
Notifications
You must be signed in to change notification settings - Fork 1
/
name_lib.py
234 lines (189 loc) · 7.79 KB
/
name_lib.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# This library implements a NameTracker object to query and alert on changes in
# names for a specified Discord user.
#
# Requires Python version >= 3.6.
# This library queries the DeBank API.
# Creates a savefile CSV called <address>[-<tag>].csv to save results of recent
# queries.
from absl import logging
from datetime import datetime
from datetime import timezone
from pathlib import Path
from typing import List
from typing import Optional
from typing import Tuple
import asyncio
import csv
import discord
import utils
SAVEFILE_FIELDS = ['time', 'user_id', 'tag', 'name']
class Name(object):
def __init__(self, **kwargs):
for key, value in kwargs.items():
if key == 'time':
self.time = value
elif key == 'user_id':
self.user_id = value
elif key == 'tag':
self.tag = value
elif key == 'name':
self.name = value
elif key == 'csv_row':
self.from_csv_row(value)
break
else:
logging.fatal('Ineligible key: ' + key)
def from_csv_row(self, dict_in: dict):
self.time = utils.parse_storage_time(dict_in['time'])
self.user_id = dict_in['user_id']
self.tag = dict_in['tag']
self.name = dict_in['name']
def to_csv_row(self) -> dict:
result = {}
result['time'] = utils.format_storage_time(self.time)
result['user_id'] = self.user_id
result['tag'] = self.tag
result['name'] = self.name
return result
def _get_savefile(user_id: int, tag: str) -> str:
filename = f'nametracker-{user_id}-{tag}'
return f'{filename}.csv'
def _query_prev_name(savefile: str) -> Optional[Name]:
last_row = None
with open(savefile, newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
last_row = row
return Name(csv_row=last_row) if last_row else None
def _write_name(name: Name, savefile: str):
with open(savefile, 'a', newline='') as csvfile:
writer = csv.DictWriter(csvfile, SAVEFILE_FIELDS)
writer.writerow(name.to_csv_row())
class NameTracker(object):
def __init__(self,
client: discord.Client,
user_id: str,
tag: str,
subscribe_command: str,
last_alert_time: Optional[str],
channels: Optional[List[int]]):
# Discord client for querying user info.
self._client = client
# Discord ID of the user being tracked.
self._user_id = user_id
# A human-readable tag to associate with the user.
self._tag = tag
# Subscribe command for the bot invoking this tracker.
self._subscribe_command = subscribe_command
# Path for saving the data for this tracker.
self._savefile = _get_savefile(user_id, tag)
# A datetime representing the last time the state of the tracker was
# updated. This is set after running the get_last_update() call.
self._last_update_time = utils.MIN_TIME
# A list of channel IDs subscribed to this tracker.
self._channels = channels if channels else []
# The last time the tracker raised an alert. This is set internally by
# the sync_last_alert_time() call, as well as externally during
# construction.
if last_alert_time:
self._last_alert_time = utils.parse_storage_time(last_alert_time)
else:
self._last_alert_time = utils.MIN_TIME
# The contents of the latest message that was produced from running
# update() or _get_last_update().
self._last_message = f'{self.get_name()} tracker just initialized.'
# Creates a new savefile if needed.
if not Path(self._savefile).is_file():
with open(self._savefile, 'w', newline='') as csvfile:
writer = csv.DictWriter(csvfile, SAVEFILE_FIELDS)
writer.writeheader()
# Load the latest saved debt data.
self._get_last_update()
def get_name(self) -> str:
return f'{self._tag}'
def get_user_id(self) -> str:
return self._user_id
def get_identifier(self) -> str:
return self.get_user_id()
def get_tag(self) -> Optional[str]:
return self._tag
def get_last_update_time(self) -> datetime:
return self._last_update_time
def get_last_alert_time(self) -> datetime:
return self._last_alert_time
def get_last_message(self) -> str:
return self._last_message
def get_channels(self) -> List[int]:
return self._channels
def has_channel(self, channel_id: int) -> bool:
return channel_id in self._channels
def add_channel(self, channel_id: int):
self._channels.append(channel_id)
def get_subscribe_command(self) -> str:
return self._subscribe_command
# Sets the last alert time to the last update time. This is useful when
# there is some caller that is using a different criteria for triggering an
# alert.
def sync_last_alert_time(self):
self._last_alert_time = self._last_update_time
# An internal function that fetches the current state of the tracker without
# performing new queries.
def _get_last_update(self) -> Tuple[bool, str]:
savefile = self._savefile
name = _query_prev_name(savefile)
if not name:
return False, f'No name inferred for {self.get_name()}.'
message = f'**{self.get_name()}** tracker: Current name is **{name.name}** (as of {utils.display_time(name.time)} UTC).'
# Update timestamps and messages.
self._last_update_time = name.time
self._last_message = message
if self._last_alert_time == utils.MIN_TIME:
self.sync_last_alert_time()
return False, message
# Queries the user's current name and stores the result as a message. The
# _last_update_time is updated to the current time. The
# _last_alert_time is updated to the _last_update_time if this update raised
# an alert.
async def update(self) -> Tuple[bool, str]:
user_id = self._user_id
tag = self._tag
savefile = self._savefile
# Get current username.
member = None
for channel_id in self._channels:
channel = self._client.get_channel(channel_id)
member = await channel.guild.fetch_member(int(user_id))
print(f'Found member: {member}')
if member:
break
if not member:
print(f'Unable to find user: {user_id}')
return False, f'Unable to find user {user_id}'
if member.nick:
name_string = f'{member.nick} ({member.name}#{member.discriminator})'
else:
name_string = f'{member.name}#{member.discriminator}'
name = Name(time=datetime.now(timezone.utc),
user_id=user_id,
tag=tag,
name=name_string)
# Get previous username.
prev_name = _query_prev_name(savefile)
message = f'**{self.get_name()}** tracker: '
if not prev_name:
has_alert = True
message += f'Current name is **{name.name}**'
elif prev_name.name != name.name:
has_alert = True
message += f'The artist formerly known as **{prev_name.name}** is now known as **{name.name}**'
else:
has_alert = False
message += f'Current name is still **{name.name}**'
message += f' (as of {utils.display_time(name.time)} UTC).'
# Update timestamps and messages.
self._last_update_time = name.time
self._last_message = message
if has_alert:
self.sync_last_alert_time()
_write_name(name, savefile)
return has_alert, message