-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhandlers.py
More file actions
364 lines (296 loc) · 15.1 KB
/
handlers.py
File metadata and controls
364 lines (296 loc) · 15.1 KB
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
import enum
import logging
import asyncio
from aiogram import types, Dispatcher, F, Router
from aiogram.types import FSInputFile
from datetime import datetime
from aiogram.fsm.context import FSMContext
from aiogram.filters import Command, CommandStart
from aiogram.types import (
KeyboardButton,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
InlineKeyboardMarkup,
InlineKeyboardButton
)
import os
from states import *
from database_manager import *
from parsers import *
router = Router()
class KeyBoards(enum.Enum):
MAIN_MENU = 0
CITIES_KEYBOARD = 1
EMPLOYMENT = 2
BACK_TO_MAIN = 3
LANGUAGES_KEYBOARD = 4
SALARY = 5
EXPERIENCE_KEYBOARD = 6
RESOURCE_KEYBOARD = 7
PRE_RUN_KEYBOARD = 8
def get_keyboards(keyboard_id):
def main_menu():
buttons = [KeyboardButton(text=t) for t in ["Parse Candidates", "Parsing History"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
def cities():
buttons = [KeyboardButton(text=t) for t in ["Dnipro", "Kyiv", "Odesa", "All Cities", "Remote", "Return to main menu"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
def employment():
buttons = [KeyboardButton(text=t) for t in ["Full time", "Part time", "Both", "Return to main menu"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
def back_to_main():
return ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="Return to main menu")]])
def languages():
buttons = [KeyboardButton(text=t) for t in ["German", "Ukrainian", "russian", "English", "French", "Slovakian", "Poland", "Continue", "Return to main menu"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
def salary():
buttons = [KeyboardButton(text=t) for t in ["Not specified", "Return to main menu"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
def experience():
buttons = [KeyboardButton(text=t) for t in ["No experience", "Less then 1 year", "1-3 years", "3-5 years", "5+ years", "Continue", "Return to main menu"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
def resource():
buttons = [KeyboardButton(text=t) for t in ["WorkUA", "RabotaUA", "Both", "Return to main menu"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
def pre_run():
buttons = [KeyboardButton(text=t) for t in ["Run Script", "Return to main menu"]]
return ReplyKeyboardMarkup(keyboard=[[button] for button in buttons], resize_keyboard=True)
if keyboard_id == KeyBoards.MAIN_MENU:
return main_menu()
elif keyboard_id == KeyBoards.CITIES_KEYBOARD:
return cities()
elif keyboard_id == KeyBoards.EMPLOYMENT:
return employment()
elif keyboard_id == KeyBoards.BACK_TO_MAIN:
return back_to_main()
elif keyboard_id == KeyBoards.LANGUAGES_KEYBOARD:
return languages()
elif keyboard_id == KeyBoards.SALARY:
return salary()
elif keyboard_id == KeyBoards.EXPERIENCE_KEYBOARD:
return experience()
elif keyboard_id == KeyBoards.RESOURCE_KEYBOARD:
return resource()
elif keyboard_id == KeyBoards.PRE_RUN_KEYBOARD:
return pre_run()
def create_dates_inline_keyboard(dates):
buttons = [
[InlineKeyboardButton(text=date, callback_data=f"date_{date}") for date in dates]
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
@router.message(F.text == "Parsing History")
async def check_history(msg: types.Message, state: FSMContext):
await msg.answer(text="Choose date to fetch history")
with DataBaseManager() as db:
dates = db.fetch_all_ids()
inline_kb = create_dates_inline_keyboard(dates)
await msg.answer("Select a date to view parsing history:", reply_markup=inline_kb)
@router.callback_query(F.data.startswith("date_"))
async def fetch_and_send_history(callback_query: types.CallbackQuery, state: FSMContext):
date_key = callback_query.data.split("_")[1]
user_id = callback_query.from_user.id
with DataBaseManager() as db:
document = db.fetch_data(date_key)
if not document:
await callback_query.message.answer(f"No data found for {date_key}")
return
filenames = save_parsing_history_to_excel(date_key)
for filename in filenames:
file = FSInputFile(filename)
await callback_query.message.answer_document(file)
os.remove(filename)
await callback_query.answer()
@router.message(ActionBlocker.parsing_inbound)
async def block_action(msg: types.Message, state: FSMContext):
await msg.answer(text="Wait until parsing will end")
@router.message(Command("start"))
async def cmd_start(msg: types.Message):
await msg.answer(
text="This bot written by https://github.com/Bersenrar as test task\nMany moments in this application"
"can be implemented better but in order to complete TEST task faster and just show my abbility"
"to create such application they weren't implemented as good as possible because it would take"
"long time(+-5 days)",
reply_markup=get_keyboards(KeyBoards.MAIN_MENU)
)
@router.message(F.text == "Return to main menu")
async def return_to_main(msg: types.Message, state: FSMContext):
await msg.answer(text="Back to main menu", reply_markup=get_keyboards(KeyBoards.MAIN_MENU))
await state.clear()
@router.message(F.text == "Parse Candidates")
async def start_form_filling(msg: types.Message, state: FSMContext):
await msg.answer(text="To parse candidate fill parameters")
await msg.answer(
text="Write key words for searching through whitespace for example: Python Developer",
reply_markup=get_keyboards(KeyBoards.BACK_TO_MAIN)
)
await state.set_state(ParsingForm.position_key_words)
@router.message(ParsingForm.position_key_words)
async def parse_key_words(msg: types.Message, state: FSMContext):
await state.update_data(position=msg.text.strip())
await msg.answer(
text="Choose city using keyboard or write by your own in English",
reply_markup=get_keyboards(KeyBoards.CITIES_KEYBOARD)
)
await state.set_state(ParsingForm.city)
@router.message(ParsingForm.city)
async def get_city(msg: types.Message, state: FSMContext):
await state.update_data(city=msg.text.strip().lower())
await msg.answer(
text="Now choose employment type",
reply_markup=get_keyboards(KeyBoards.EMPLOYMENT)
)
await state.set_state(ParsingForm.employment)
@router.message(ParsingForm.employment)
async def get_employment(msg: types.Message, state: FSMContext):
await state.update_data(employment=msg.text.strip())
await state.set_state(ParsingForm.languages)
await msg.answer(text="Select must have languages to skip this parameter press continue button", reply_markup=get_keyboards(KeyBoards.LANGUAGES_KEYBOARD))
@router.message(ParsingForm.languages)
async def language_selection(msg: types.Message, state: FSMContext):
user_data = await state.get_data()
selected_languages = user_data.get("selected_languages", [])
print(f"Current state: {await state.get_state()}")
if msg.text == "Continue":
await msg.answer("Languages selection completed.")
await state.update_data(selected_languages=selected_languages)
await msg.answer(text="Now input salary from search", reply_markup=get_keyboards(KeyBoards.SALARY))
await state.set_state(ParsingForm.salary_from)
elif msg.text in ["German", "Ukrainian", "russian", "English", "French", "Slovakian", "Poland"]:
if msg.text not in selected_languages:
selected_languages.append(msg.text)
await state.update_data(selected_languages=selected_languages)
await msg.answer(f"Added {msg.text}. You can select more or press 'Continue'.")
else:
await msg.answer(f"{msg.text} is already selected. You can select more or press 'Continue'.")
else:
await msg.answer("Please choose a valid language or press 'Continue'.")
if not msg.text == "Continue":
await msg.answer(
text="Select must-have languages or press 'Continue' when done:"
)
@router.message(ParsingForm.salary_from)
async def get_salary_to(msg: types.Message, state: FSMContext):
await state.update_data(salary_from=msg.text)
await msg.answer(text="Now specify salary to", reply_markup=get_keyboards(KeyBoards.SALARY))
await state.set_state(ParsingForm.salary_to)
@router.message(ParsingForm.salary_to)
async def get_salary_from(msg: types.Message, state: FSMContext):
await state.update_data(salary_from=msg.text)
await msg.answer(text="Select experience to skip this parameter press continue button", reply_markup=get_keyboards(KeyBoards.EXPERIENCE_KEYBOARD))
await state.set_state(ParsingForm.experience)
@router.message(ParsingForm.experience)
async def experience_selection(msg: types.Message, state: FSMContext):
user_data = await state.get_data()
selected_experience = user_data.get("selected_experience", [])
if msg.text == "Continue":
await msg.answer("Experience selection completed.")
await state.update_data(selected_experience=selected_experience)
await msg.answer(text="Now you need select resource for parsing.", reply_markup=get_keyboards(KeyBoards.RESOURCE_KEYBOARD))
await state.set_state(ParsingForm.parsing_resource)
elif msg.text in ["No experience", "Less then 1 year", "1-3 years", "3-5 years", "5+ years", "Not specified"]:
if msg.text not in selected_experience:
selected_experience.append(msg.text)
await state.update_data(selected_experience=selected_experience)
await msg.answer(f"Added {msg.text}. You can select more or press 'Continue'.")
else:
await msg.answer(f"{msg.text} is already selected. You can select more or press 'Continue'.")
else:
await msg.answer("Please choose a valid experience option or press 'Continue'.")
if not msg.text == "Continue":
await msg.answer(
text="Select experience or press 'Continue' when done:"
)
@router.message(ParsingForm.parsing_resource)
async def get_resource(msg: types.Message, state: FSMContext):
await state.update_data(resource=msg.text)
await msg.answer(text="All queries have been filled", reply_markup=get_keyboards(KeyBoards.PRE_RUN_KEYBOARD))
await state.set_state(Switchers.run_script)
@router.message(F.text == "Run Script")
@router.message(Switchers.run_script)
async def parse_data(msg: types.Message, state: FSMContext):
def prepare_data(d):
langs_table = {
"English": "eng", "Ukrainian": "ua", "russian": "ru", "Poland": "pol",
"German": "ger", "Slovakian": "slav", "French": "fre"
}
experience_table = {
"No experience": "no_experience", "Less then 1 year": "less_1_year",
"1-3 years": "1_3_years", "3-5 years": "3_5_years", "5+ years": "5_more_years"
}
employment_table = {"Full time": "full_time", "Part time": "part_time", "Both": "both"}
def prepare_work_ua():
query = {
"position": d.get("position", "").split(" "),
"city": d.get("city", "").lower(),
"employment": [employment_table.get(d.get("employment", ""), "both")],
"salary_from": d.get("salary_from") if d.get("salary_from", "").isdigit() else None,
"salary_to": d.get("salary_to") if d.get("salary_to", "").isdigit() else None,
"language": [langs_table.get(lang) for lang in d.get("selected_languages", [])],
"experience": [experience_table.get(exp) for exp in d.get("selected_experience", [])],
}
query = {k: v for k, v in query.items() if v}
return query
def prepare_rabota_ua():
query = {
"position": d.get("position", "").split(" "),
"city": d.get("city", "").capitalize(),
"employment": employment_table.get(d.get("employment", ""), "both"),
"salary_from": d.get("salary_from") if d.get("salary_from", "").isdigit() else None,
"salary_to": d.get("salary_to") if d.get("salary_to", "").isdigit() else None,
"language": [langs_table.get(lang) for lang in d.get("selected_languages", [])],
"experience": [experience_table.get(exp) for exp in d.get("selected_experience", [])],
}
query = {k: v for k, v in query.items() if v}
return query
resource = d.get("resource")
print(resource)
if resource == "Both":
return prepare_work_ua(), prepare_rabota_ua()
elif resource == "WorkUA":
return prepare_work_ua(), None
else:
return None, prepare_rabota_ua()
def parse_work_ua(q):
parser = WorkuaParser()
result = parser.run_script(q)
with MarksManager() as m:
for d in result:
d["mark"] = m.count_mark_workua(d)
result.sort(key=lambda x: x["mark"], reverse=True)
with DataBaseManager() as db:
db.append_data(
date_key=datetime.now().strftime("%d.%m.%Y"),
query=" ".join(q.get("position", "ALL")),
resource_name="WORK_UA",
data=result
)
async def parse_rabota_ua(q):
parser = RabotaUa()
result = await asyncio.to_thread(parser.run_script, q)
with MarksManager() as m:
for d in result:
d["mark"] = m.count_mark_rabotaua(d)
result.sort(key=lambda x: x["mark"], reverse=True)
with DataBaseManager() as db:
db.append_data(
date_key=datetime.now().strftime("%d.%m.%Y"),
query=" ".join(q.get("position", "ALL")),
resource_name="RABOTA_UA",
data=result
)
user_query = await state.get_data()
resource = user_query.get("resource")
await state.clear()
await state.set_state(ActionBlocker.parsing_inbound)
work_ua_query, rabota_ua_query = prepare_data(user_query)
print(work_ua_query, rabota_ua_query)
await msg.answer(text="Wait till parsing ends", reply_markup=ReplyKeyboardRemove())
if resource == "WorkUA":
parse_work_ua(work_ua_query)
elif resource == "RabotaUA":
await parse_rabota_ua(rabota_ua_query)
elif resource == "Both":
parse_work_ua(work_ua_query)
await parse_rabota_ua(rabota_ua_query)
await msg.answer(text="Parsing ended", reply_markup=get_keyboards(KeyBoards.MAIN_MENU))