-
Notifications
You must be signed in to change notification settings - Fork 2
/
config.py
183 lines (169 loc) · 6.81 KB
/
config.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
"""
Config - data storage for merely bots
Extends configparser with backups, defaults, saving and reloading
"""
from configparser import ConfigParser
import os, glob, shutil, time
from packaging import version
class Config(ConfigParser):
"""loads the config file automatically and ensures it's in a valid state"""
def __init__(self, path='config', quiet=False):
"""
Custom init for merelybot configparser
will always return a valid config object, even if the filesystem is broken
"""
super().__init__()
self.path = path
self.file = os.path.join(self.path, "config.ini")
self.template = os.path.join(self.path, "config.factory.ini")
self.reference:ConfigParser | None = None
self.master:ConfigParser | None = None
master_file = os.path.join('config', "config.factory.ini")
if path != 'config' and os.path.exists(master_file):
with open(master_file, 'r', encoding='utf-8') as f:
self.master = ConfigParser()
self.master.read_file(f)
self.last_backup = 0
self.migrate:bool | version.Version = False
self.quiet = quiet
self.load()
def load(self):
""" Verify file exists and load it """
rebuild = False
if not os.path.exists(self.path):
if not self.quiet:
print(
f"WARN: {self.path} missing - creating folder and generating bare-minimum defaults.",
f"\nYou should consider writing or including a '{self.template}'"
)
rebuild = True
os.makedirs(self.path)
if not os.path.exists(self.file):
if os.path.exists(self.template):
if not self.quiet:
print(f"WARN: {self.file} missing - reverting to template config")
shutil.copy(self.template, self.file)
else:
if not self.quiet:
print(
f"WARN: {self.template} missing - resorting to bare-minimum defaults.",
f"\nYou should consider writing or including a '{self.template}'"
)
rebuild = True
if not rebuild:
with open(self.file, 'r', encoding='utf-8') as f:
ini = f.read()
if ini.endswith('\n\n'):
self.read_string(ini)
else:
raise AssertionError(
f"FATAL: {self.file} may have been in the process of saving during the last run!",
"\nCheck your config file! There may be data loss."
)
if os.path.exists(self.template):
with open(self.template, 'r', encoding='utf-8') as f:
self.reference = ConfigParser()
self.reference.read_file(f)
# Ensure required sections exist and provide sane defaults
# Generates config in case it's missing, but also updates old configs
# Main section
if 'main' not in self.sections():
self.add_section('main')
if 'token' not in self['main']:
self['main']['token'] = ''
if 'botname' not in self['main']:
self['main']['botname'] = 'merely framework bot'
if 'themecolor' not in self['main']:
self['main']['themecolor'] = '0x0'
if 'voteurl' not in self['main']:
self['main']['voteurl'] = ''
if 'tos_url' not in self['main']:
self['main']['tos_url'] = ''
if 'beta' not in self['main']:
self['main']['beta'] = 'False'
if 'ver' not in self['main']:
self['main']['ver'] = '0'
elif (
self.reference and
version.parse(self.reference['main']['ver']) > version.parse(self['main']['ver'])
):
self.migrate = version.parse(self['main']['ver'])
self['main']['ver'] = self.reference['main']['ver']
if 'creator' not in self['main']:
self['main']['creator'] = ''
# Intents section
if 'intents' not in self.sections():
self.add_section('intents')
if 'guilds' not in self['intents']:
self['intents']['guilds'] = 'False'
if 'members' not in self['intents']:
self['intents']['members'] = 'False'
if 'moderation' not in self['intents']:
self['intents']['moderation'] = 'False'
if 'emojis' not in self['intents']:
self['intents']['emojis'] = 'False'
if 'integrations' not in self['intents']:
self['intents']['integrations'] = 'False'
if 'webhooks' not in self['intents']:
self['intents']['webhooks'] = 'False'
if 'invites' not in self['intents']:
self['intents']['invites'] = 'False'
if 'voice_states' not in self['intents']:
self['intents']['voice_states'] = 'False'
if 'presences' not in self['intents']:
self['intents']['presences'] = 'False'
if 'message_content' not in self['intents']:
self['intents']['message_content'] = 'False'
if 'bans' not in self['intents']:
self['intents']['bans'] = 'False'
if 'scheduled_events' not in self['intents']:
self['intents']['scheduled_events'] = 'False'
if 'auto_moderation' not in self['intents']:
self['intents']['auto_moderation'] = 'False'
if 'messages' not in self['intents']:
self['intents']['messages'] = 'none'
if 'reactions' not in self['intents']:
self['intents']['reactions'] = 'none'
if 'typing' not in self['intents']:
self['intents']['typing'] = 'none'
if 'polls' not in self['intents']:
self['intents']['polls'] = 'none'
# Language section (babel)
if 'language' not in self.sections():
self.add_section('language')
if 'default' not in self['language']:
self['language']['default'] = 'en'
if 'prefix' not in self['language']:
self['language']['prefix'] = ''
if 'contribute_url' not in self['language']:
self['language']['contribute_url'] = ''
# Extensions section
if 'extensions' not in self.sections():
self.add_section('extensions')
self.save()
def save(self):
""" copy existing config to backups, save new config """
# create a backup of the config (max 1 per hour)
if self.last_backup < time.time() - (60*60):
if not os.path.exists(os.path.join(self.path, 'config_history')):
os.makedirs(os.path.join(self.path, 'config_history'))
if os.path.isfile(self.file):
oldfiles = glob.glob(
os.path.join(self.path, 'config_history', 'config-'+time.strftime("%d-%m-%y ")+'*.ini')
)
shutil.copy(self.file, os.path.join(
self.path,
'config_history',
'config-'+time.strftime("%d-%m-%y %H:%M.%S")+'.ini'
))
for oldfile in oldfiles:
# delete older configs from the day so only one is stored a day
os.remove(oldfile)
self.last_backup = time.time()
with open(self.file, 'w', encoding='utf-8') as f:
ConfigParser.write(self, f)
def reload(self):
""" reset config and load it again """
for section in self.sections():
self.remove_section(section)
self.load()