Skip to content

Commit 7215cfa

Browse files
committed
Add user points system
1 parent a44b45b commit 7215cfa

File tree

5 files changed

+238
-46
lines changed

5 files changed

+238
-46
lines changed

.vscode/.ropeproject/config.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# The default ``config.py``
2+
# flake8: noqa
3+
4+
5+
def set_prefs(prefs):
6+
"""This function is called before opening the project"""
7+
8+
# Specify which files and folders to ignore in the project.
9+
# Changes to ignored resources are not added to the history and
10+
# VCSs. Also they are not returned in `Project.get_files()`.
11+
# Note that ``?`` and ``*`` match all characters but slashes.
12+
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
13+
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
14+
# '.svn': matches 'pkg/.svn' and all of its children
15+
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
16+
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
17+
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
18+
'.hg', '.svn', '_svn', '.git', '.tox']
19+
20+
# Specifies which files should be considered python files. It is
21+
# useful when you have scripts inside your project. Only files
22+
# ending with ``.py`` are considered to be python files by
23+
# default.
24+
# prefs['python_files'] = ['*.py']
25+
26+
# Custom source folders: By default rope searches the project
27+
# for finding source folders (folders that should be searched
28+
# for finding modules). You can add paths to that list. Note
29+
# that rope guesses project source folders correctly most of the
30+
# time; use this if you have any problems.
31+
# The folders should be relative to project root and use '/' for
32+
# separating folders regardless of the platform rope is running on.
33+
# 'src/my_source_folder' for instance.
34+
# prefs.add('source_folders', 'src')
35+
36+
# You can extend python path for looking up modules
37+
# prefs.add('python_path', '~/python/')
38+
39+
# Should rope save object information or not.
40+
prefs['save_objectdb'] = True
41+
prefs['compress_objectdb'] = False
42+
43+
# If `True`, rope analyzes each module when it is being saved.
44+
prefs['automatic_soa'] = True
45+
# The depth of calls to follow in static object analysis
46+
prefs['soa_followed_calls'] = 0
47+
48+
# If `False` when running modules or unit tests "dynamic object
49+
# analysis" is turned off. This makes them much faster.
50+
prefs['perform_doa'] = True
51+
52+
# Rope can check the validity of its object DB when running.
53+
prefs['validate_objectdb'] = True
54+
55+
# How many undos to hold?
56+
prefs['max_history_items'] = 32
57+
58+
# Shows whether to save history across sessions.
59+
prefs['save_history'] = True
60+
prefs['compress_history'] = False
61+
62+
# Set the number spaces used for indenting. According to
63+
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
64+
# unit-tests use 4 spaces it is more reliable, too.
65+
prefs['indent_size'] = 4
66+
67+
# Builtin and c-extension modules that are allowed to be imported
68+
# and inspected by rope.
69+
prefs['extension_modules'] = []
70+
71+
# Add all standard c-extensions to extension_modules list.
72+
prefs['import_dynload_stdmods'] = True
73+
74+
# If `True` modules with syntax errors are considered to be empty.
75+
# The default value is `False`; When `False` syntax errors raise
76+
# `rope.base.exceptions.ModuleSyntaxError` exception.
77+
prefs['ignore_syntax_errors'] = False
78+
79+
# If `True`, rope ignores unresolvable imports. Otherwise, they
80+
# appear in the importing namespace.
81+
prefs['ignore_bad_imports'] = False
82+
83+
# If `True`, rope will insert new module imports as
84+
# `from <package> import <module>` by default.
85+
prefs['prefer_module_from_imports'] = False
86+
87+
# If `True`, rope will transform a comma list of imports into
88+
# multiple separate import statements when organizing
89+
# imports.
90+
prefs['split_imports'] = False
91+
92+
# If `True`, rope will remove all top-level import statements and
93+
# reinsert them at the top of the module when making changes.
94+
prefs['pull_imports_to_top'] = True
95+
96+
# If `True`, rope will sort imports alphabetically by module name instead
97+
# of alphabetically by import statement, with from imports after normal
98+
# imports.
99+
prefs['sort_imports_alphabetically'] = False
100+
101+
# Location of implementation of
102+
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
103+
# case, you don't have to change this value, unless you're an rope expert.
104+
# Change this value to inject you own implementations of interfaces
105+
# listed in module rope.base.oi.type_hinting.providers.interfaces
106+
# For example, you can add you own providers for Django Models, or disable
107+
# the search type-hinting in a class hierarchy, etc.
108+
prefs['type_hinting_factory'] = (
109+
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
110+
111+
112+
def project_opened(project):
113+
"""This function is called after opening the project"""
114+
# Do whatever you like here!

.vscode/.ropeproject/objectdb

6 Bytes
Binary file not shown.
287 Bytes
Binary file not shown.

activity.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,27 @@ def __str__(self):
3636
return f"{self.username}{{UUID: {self.uuid}, Score: {self.score}, AllowModerator: {self.allowmoderator}, RankExempt: {self.rankexempt}}}"
3737

3838

39-
def changeScore(self, delta):
40-
self.score += delta
39+
def change_score(self, delta):
40+
if (self.score + delta > 0 and self.score + delta < 250):
41+
self.score += delta
42+
write_database()
4143
return self.score
4244

4345

44-
def addUser(uuid, username, score=0, allowmoderator=True, rankexempt=False):
46+
def change_all_scores(delta):
47+
for user in USERS.values():
48+
if (user.score + delta > 0 and user.score + delta < 250):
49+
user.score += delta
50+
51+
write_database()
52+
53+
54+
def add_user(uuid, username, score=0, allowmoderator=True, rankexempt=False):
4555
newUser = User(uuid, username, score, allowmoderator, rankexempt)
4656
USERS[uuid] = newUser
4757

4858

49-
def writeDatabase():
59+
def write_database():
5060
with open('data/users.csv', mode='w', encoding='utf-8') as dataFile:
5161
fieldnames = ['uuid', 'username', 'score', 'allowmoderator', 'rankexempt']
5262
writer = csv.DictWriter(dataFile, fieldnames=fieldnames, lineterminator = '\n')
@@ -61,23 +71,23 @@ def writeDatabase():
6171
'rankexempt': user.rankexempt})
6272

6373

64-
def readDatabase():
65-
with open('data/users.csv', mode='r') as dataFile:
74+
def read_database():
75+
with open('data/users.csv', mode='r', encoding='utf-8') as dataFile:
6676
dataFile = csv.DictReader(dataFile)
6777
for row in dataFile:
6878
user = User(int(row['uuid']), row['username'], int(row['score']), bool(row['allowmoderator']), (row['rankexempt']))
6979
USERS[int(row['uuid'])] = user
7080

7181

72-
def printDatabase():
82+
def print_database():
7383
for user in USERS.values():
7484
print(user)
7585

7686

7787
def main():
78-
readDatabase()
79-
printDatabase()
80-
writeDatabase()
88+
read_database()
89+
print_database()
90+
write_database()
8191

8292

8393
if __name__ == "__main__":

protobot.py

Lines changed: 104 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,18 @@
3030
#####################################
3131

3232

33-
# Load bot token from .env
34-
load_dotenv()
35-
TOKEN = os.getenv('DISCORD_TOKEN')
33+
#################################
34+
#
35+
POINTS_PER_MESSAGE = 2 #
36+
POINTS_PER_MINUTE_TALKING = 1 #
37+
POINT_DECAY_PER_HOUR = 1 #
38+
#
39+
#################################
40+
3641

3742
# Initialize bot object to use the COMMAND_PREFIX defined above
3843
bot = commands.Bot(command_prefix=COMMAND_PREFIX)
3944

40-
# Generate timestamp of startup
41-
timestamp = time.strftime('%Y%m%d-%H%M%S')
42-
43-
# Configure logging
44-
logging.basicConfig(
45-
level = LOG_LEVEL,
46-
format = '%(asctime)s: [%(levelname)s] - %(message)s',
47-
datefmt = '%Y-%m-%d %H:%M:%S',
48-
handlers = [
49-
logging.FileHandler(f"logs/{timestamp}.log", mode = "w"),
50-
logging.StreamHandler()
51-
]
52-
)
5345

5446
@bot.event
5547
async def on_connect():
@@ -75,10 +67,10 @@ async def on_ready():
7567
label = guild.name + " (" + str(guild.id) + ")"
7668
logging.info(label)
7769

78-
activity.readDatabase()
70+
activity.read_database()
7971

8072
for guild in bot.guilds:
81-
addAllUsersFromGuildToDatabase(guild)
73+
add_all_users_from_guild_to_database(guild)
8274

8375

8476
@bot.event
@@ -89,6 +81,7 @@ async def on_disconnect():
8981

9082
logging.warning('Lost connection to Discord.')
9183

84+
9285
@bot.event
9386
async def on_guild_join(guild):
9487
"""
@@ -119,10 +112,9 @@ async def on_member_join(member):
119112
else:
120113
welcome_message = f"Welcome to {member.guild.name}, {member.name}!"
121114

122-
await member.dm_channel.send(welcome_message)
123-
124-
115+
add_user_to_database(member.id, member.name)
125116

117+
await member.dm_channel.send(welcome_message)
126118

127119

128120
@bot.event
@@ -143,14 +135,17 @@ async def on_message(message):
143135
Allows the bot to respond to user messages rather than commands
144136
"""
145137

146-
if message.author == bot.user:
138+
if message.author.bot:
147139
"""
148140
Tells the bot to ignore its own messages
149141
"""
150142

151-
return
143+
return
152144

153-
elif 'happy birthday' in message.content.lower():
145+
else:
146+
change_user_score(message.author.id, POINTS_PER_MESSAGE)
147+
148+
if 'happy birthday' in message.content.lower():
154149
"""
155150
Lets the bot say happy birthday whenever a user says it
156151
"""
@@ -184,38 +179,111 @@ async def on_message(message):
184179
await bot.process_commands(message)
185180

186181

187-
async def run_once_per_day():
182+
@bot.command(name="score", help="Displays your current server score.")
183+
async def check_user_score(ctx):
184+
uuid = ctx.message.author.id
185+
score = get_user_score(uuid)
186+
187+
# Take away the points the user gets for running the command
188+
change_user_score(uuid, -POINTS_PER_MESSAGE)
189+
190+
await ctx.message.channel.send(f"Score for <@{uuid}>: {score}")
191+
192+
193+
async def run_once_every_day():
188194
"""
189195
Runs a block of code every day sometime between 00:00 and 01:00 local time.
190196
"""
191197

192-
await bot.wait_until_ready()
193-
194198
if (int(time.strftime('%H', time.localtime())) < 1):
195199
# This code will run if it is the correct time
196200
logging.info("Running nightly operations.")
197201
else:
198202
logging.debug("Attempted to run daily event out of defined hours.")
199203

200-
# Check every hour
204+
205+
async def run_once_every_minute():
206+
"""
207+
Runs a block of code every minute
208+
"""
209+
210+
await asyncio.sleep(60)
211+
212+
# Give every user in a voice channel points
213+
for guild in bot.guilds:
214+
for channel in guild.voice_channels:
215+
for user in channel.members:
216+
change_user_score(user.id, POINTS_PER_MINUTE_TALKING)
217+
218+
219+
async def run_once_every_hour():
220+
"""
221+
Runs a block of code every hour
222+
"""
223+
201224
await asyncio.sleep(3600)
202225

226+
# Call the once-each-day function so it can do its check
227+
await run_once_every_day()
228+
229+
activity.change_all_scores(-POINT_DECAY_PER_HOUR)
230+
231+
232+
def change_user_score(uuid, delta):
233+
if (uuid in activity.USERS.keys()):
234+
activity.USERS[uuid].change_score(delta)
235+
else:
236+
logging.error(f"Attempted to change score of user {uuid} when user is not in database.")
237+
203238

204-
def addAllUsersFromGuildToDatabase(guild):
239+
def get_user_score(uuid):
240+
if (uuid in activity.USERS.keys()):
241+
return activity.USERS[uuid].score
242+
else:
243+
logging.error(f"Attempted to get score of user {uuid} when user is not in database.")
244+
245+
246+
def add_all_users_from_guild_to_database(guild):
205247
for user in guild.members:
206248
if (not user.bot):
207-
addUserToDatabase(user.id, user.name)
249+
add_user_to_database(user.id, user.name)
208250

209-
activity.writeDatabase()
251+
activity.write_database()
210252

211253

212-
def addUserToDatabase(uuid, name, score=0, allowmoderator=True, rankexempt=False):
254+
def add_user_to_database(uuid, name, score=0, allowmoderator=True, rankexempt=False):
213255
if (not uuid in activity.USERS.keys()):
214-
activity.addUser(uuid, name, score, allowmoderator, rankexempt)
256+
activity.add_user(uuid, name, score, allowmoderator, rankexempt)
215257
logging.info(f"Registered new user {name} ({uuid}) to database.")
216258

217-
activity.writeDatabase()
259+
activity.write_database()
260+
261+
262+
263+
264+
def main():
265+
# Load bot token from .env
266+
load_dotenv()
267+
TOKEN = os.getenv('DISCORD_TOKEN')
268+
269+
# Generate timestamp of startup
270+
timestamp = time.strftime('%Y%m%d-%H%M%S')
271+
272+
# Configure logging
273+
logging.basicConfig(
274+
level = LOG_LEVEL,
275+
format = '%(asctime)s: [%(levelname)s] - %(message)s',
276+
datefmt = '%Y-%m-%d %H:%M:%S',
277+
handlers = [
278+
logging.FileHandler(f"logs/{timestamp}.log", mode = "w"),
279+
logging.StreamHandler()
280+
]
281+
)
282+
283+
bot.loop.create_task(run_once_every_minute())
284+
bot.loop.create_task(run_once_every_hour())
218285

286+
bot.run(TOKEN)
219287

220-
bot.loop.create_task(run_once_per_day())
221-
bot.run(TOKEN)
288+
if __name__ == "__main__":
289+
main()

0 commit comments

Comments
 (0)