-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathQuotes.py
221 lines (175 loc) · 7.84 KB
/
Quotes.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
# noinspection PyUnresolvedReferences
import json
import random
from datetime import datetime
from typing import Optional
import owncloud
class Quotes:
def __init__(self, oc: owncloud, quotes_path: str, demotivational_path: str, games_path: str):
self.oc = oc
self.quotes_path = quotes_path
self.game_path = games_path
self.demotivational_path = demotivational_path
self.quotes = []
self.game = {}
self.authors = {}
self.authors_for_game = {}
self.authors_weights_for_game = {}
self.demotivational = []
self.quotes_last_download = None
self.demotivational_last_download = None
def _download(self):
if self.quotes_last_download is not None and self._timestamp_now() - self.quotes_last_download < 60 * 60 * 48:
return self
self.quotes = json.loads(self.oc.get_file_contents(self.quotes_path).decode("utf-8"))
self.quotes_last_download = self._timestamp_now()
authors_count_for_game = {}
for quote in self.quotes:
if "author" in quote:
parts = quote["author"].split("/")
for author in parts:
author: str
author_not_normalized = author.strip()
author = self._normalize_author(author)
if author not in self.authors:
self.authors[author] = []
# dicts also keep insertion order from Python 3.7, which is important later
self.authors_for_game[author] = author_not_normalized
self.authors_weights_for_game[author] = 0
self.authors[author].append(quote)
if len(parts) == 1 and ("game" not in quote or quote["game"] != False):
self.authors_weights_for_game[author] += 1
loop_on_this = list(self.authors_weights_for_game.keys())
for author in loop_on_this:
if self.authors_weights_for_game[author] <= 5:
del self.authors_for_game[author]
del self.authors_weights_for_game[author]
print(f"There are {len(self.authors_for_game)} authors for THE GAME: {', '.join(self.authors_for_game.values())}")
return self
def _download_demotivational(self):
if self.demotivational_last_download is not None and self._timestamp_now() - self.demotivational_last_download < 60 * 60 * 48:
return self
self.demotivational = self.oc.get_file_contents(self.demotivational_path).decode("utf-8").split("\n")
self.demotivational_last_download = self._timestamp_now()
return self
def _download_game(self):
if len(self.game) <= 0:
try:
self.game = json.loads(self.oc.get_file_contents(self.game_path).decode("utf-8"))
except owncloud.owncloud.HTTPResponseError as e:
if e.status_code == 404:
self.oc.put_file_contents(self.game_path, json.dumps(self.game, indent=1).encode("utf-8"))
else:
raise e
return self
@staticmethod
def _timestamp_now() -> float:
return datetime.now().timestamp()
def get_random_quote(self, author: Optional[str] = None):
self._download()
if author is None:
q = self.quotes
else:
q = self.authors.get(self._normalize_author(author), {})
if len(q) <= 0:
return None, None, None, None
return self._format_quote(random.choice(q))
def get_game_stats(self, uid: str):
self._init_game(uid)
return self.game[uid]["right"], self.game[uid]["wrong"]
@staticmethod
def _random_choices_without_replacement(l: dict, n: int):
# Some incredibly efficient algorithms exist: https://stackoverflow.com/q/352670
# Every one of them requires some external library or doesn't work properly in this case for some reason
l = l.copy()
res = []
for _ in range(n):
# noinspection PyTypeChecker
this = random.choices(list(l.keys()), weights=l.values())
this = this[0]
res.append(this)
del l[this]
return res
def get_quote_for_game(self, uid: str):
self._download()
# Random quote from an allowed author
quote, author_printable, context, game = self.get_random_quote()
author_normalized = self._normalize_author(author_printable)
while author_normalized not in self.authors_for_game.keys() or not game:
quote, author_printable, context, game = self.get_random_quote()
author_normalized = self._normalize_author(author_printable)
# 3 other possibilites
answers = Quotes._random_choices_without_replacement(dict(filter(lambda el: el[0] != author_normalized, self.authors_weights_for_game.items())), 3)
# plus the right one
answers.append(author_normalized)
# Make them all printable
for i in range(0, 4):
answers[i] = self.authors_for_game[answers[i]]
# Shuffle
random.shuffle(answers)
self._init_game(uid)
self.game[uid]["current_author"] = author_printable
# self._save_game()
# since author_printable = '/', they're bound
# noinspection PyUnboundLocalVariable
return quote, context, answers
def _init_game(self, uid: str):
self._download_game()
if uid not in self.game:
self.game[uid] = {"current_author": None, "right": 0, "wrong": 0}
def answer_game(self, uid: str, answer: str):
self._init_game(uid)
if self.game[uid]["current_author"] is None:
return None
elif self._normalize_author(self.game[uid]["current_author"]).strip(" ") == answer:
self.game[uid]["current_author"] = None
self.game[uid]["right"] += 1
self._save_game()
return True
else:
right_author = self.game[uid]["current_author"]
self.game[uid]["current_author"] = None
self.game[uid]["wrong"] += 1
self._save_game()
return right_author
def get_demotivational_quote(self):
self._download_demotivational()
if len(self.demotivational) <= 0:
return None
return random.choice(self.demotivational)
@staticmethod
def _normalize_author(author):
author = "".join(filter(str.isalnum, author.strip().lower()))
return author
@staticmethod
def normalize_author_for_game(author):
return Quotes._normalize_author(author).strip(" ")
@staticmethod
def _format_quote(json_quote):
quote = json_quote["quote"] if "quote" in json_quote else None
author = json_quote["author"] if "author" in json_quote else None
context = json_quote["context"] if "context" in json_quote else None
game = True
if "game" in json_quote:
game = json_quote["game"]
if author and quote:
return quote, author, context, game
return None, None, None, None
def _get_quote_at(self, pos: int):
if pos < 0 or pos >= len(self.quotes):
return None
return self._format_quote(self.quotes[pos])
def delete_cache(self) -> int:
lines = len(self.quotes) + len(self.game)
self.quotes = []
self.authors = {}
self.game = {}
self.authors_for_game = {}
self.demotivational = []
self.quotes_last_download = None
self.demotivational_last_download = None
return lines
def _save_game(self):
if len(self.game) > 0:
# indent=0 to at least have some lines, instead of no newline at all
self.oc.put_file_contents(self.game_path, json.dumps(self.game, indent=0, separators=(",", ":")).encode("utf-8"))