Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
altlinux-packages-bot
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kirill Unitsaev
altlinux-packages-bot
Commits
d1ebed32
Verified
Commit
d1ebed32
authored
Apr 04, 2026
by
Kirill Unitsaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
mailing: unified schedule with per-type toggles
parent
e908122d
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
275 additions
and
199 deletions
+275
-199
profile.py
src/data/keyboards/profile.py
+37
-37
func.py
src/database/func.py
+51
-22
models.py
src/database/models.py
+16
-7
profile.py
src/handlers/profile.py
+77
-78
main.py
src/main.py
+46
-13
scheduler.py
src/modules/scheduler.py
+47
-41
task_events.py
src/services/task_events.py
+1
-1
No files found.
src/data/keyboards/profile.py
View file @
d1ebed32
...
...
@@ -11,7 +11,9 @@ profile_kb = (
profile_settings_kb
=
(
InlineKeyboard
()
.
add
(
InlineButton
(
"Рассылка"
,
callback_data
=
"profile/settings/mailing"
))
.
add
(
InlineButton
(
"Рассылки и уведомления"
,
callback_data
=
"profile/mailing"
))
.
row
()
.
add
(
InlineButton
(
"Отслеживаемые пакеты"
,
callback_data
=
"profile/settings/packages"
))
.
row
()
.
add
(
InlineButton
(
"Сменить сопровождающего"
,
callback_data
=
"profile/settings/maintainer"
))
.
row
()
...
...
@@ -31,47 +33,47 @@ def profile_settings_branch_kb():
return
kb
.
get_markup
()
def
settings_tasks_kb
(
watch_task
,
bugs_task
,
pkg_watch_task
,
task_events_enabled
):
SCHEDULED_TYPES
=
[
(
"watch"
,
"Устаревшие пакеты"
),
(
"pkg_watch"
,
"Отслеживаемые пакеты"
),
(
"bugs"
,
"Баги"
),
]
kb
=
InlineKeyboard
()
INSTANT_TYPES
=
[
(
"task_events"
,
"Таски girar"
),
]
label
=
"Устаревшие пакеты ✓"
if
watch_task
else
"Устаревшие пакеты"
action
=
"edit"
if
watch_task
else
"add"
kb
.
add
(
InlineButton
(
label
,
callback_data
=
f
"profile/settings/mailing/watch/{action}"
))
kb
.
row
()
label
=
"Открытые баги ✓"
if
bugs_task
else
"Открытые баги"
action
=
"edit"
if
bugs_task
else
"add"
kb
.
add
(
InlineButton
(
label
,
callback_data
=
f
"profile/settings/mailing/bugs/{action}"
))
kb
.
row
()
def
mailing_settings_kb
(
schedule
,
enabled
:
list
[
str
]):
kb
=
InlineKeyboard
()
label
=
"Устаревшие пакеты (по списку) ✓"
if
pkg_watch_task
else
"Устаревшие пакеты (по списку)"
action
=
"edit"
if
pkg_watch_task
else
"add"
kb
.
add
(
InlineButton
(
label
,
callback_data
=
f
"profile/settings/mailing/pkg_watch/{action}"
))
kb
.
add
(
InlineButton
(
"Дни недели"
,
callback_data
=
"profile/mailing/days"
))
kb
.
add
(
InlineButton
(
"Время"
,
callback_data
=
"profile/mailing/set-time"
))
kb
.
row
()
if
task_events_enabled
:
kb
.
add
(
InlineButton
(
"Таски girar: вкл"
,
callback_data
=
"profile/settings/mailing/task_events/off"
))
else
:
kb
.
add
(
InlineButton
(
"Таски girar: выкл"
,
callback_data
=
"profile/settings/mailing/task_events/on"
))
kb
.
row
()
for
type_id
,
label
in
SCHEDULED_TYPES
:
status
=
"вкл"
if
type_id
in
enabled
else
"выкл"
kb
.
add
(
InlineButton
(
f
"{label}: {status}"
,
callback_data
=
f
"profile/mailing/toggle/{type_id}"
,
))
kb
.
row
()
kb
.
add
(
InlineButton
(
"Пакеты для отслеживания"
,
callback_data
=
"profile/settings/packages"
))
kb
.
row
()
for
type_id
,
label
in
INSTANT_TYPES
:
status
=
"вкл"
if
type_id
in
enabled
else
"выкл"
kb
.
add
(
InlineButton
(
f
"{label}: {status}"
,
callback_data
=
f
"profile/mailing/toggle/{type_id}"
,
))
kb
.
row
()
kb
.
add
(
InlineButton
(
"Назад"
,
callback_data
=
"profile/settings"
))
return
kb
.
get_markup
()
def
user_packages_kb
(
pkgs
:
list
[
str
]):
return
packages_kb
(
"profile/pkg"
,
pkgs
,
back_callback
=
"profile/settings/mailing"
,
max_size
=
MAX_WATCH_LIST_SIZE
)
def
set_task_days_kb
(
task_type
,
days
,
selected_days
):
def
schedule_days_kb
(
days_list
,
selected_days
):
kb
=
InlineKeyboard
()
for
i
,
day_name
in
enumerate
(
days
):
for
i
,
day_name
in
enumerate
(
days
_list
):
emoji
=
"🟢"
if
i
in
selected_days
else
"🔴"
new_days
=
selected_days
.
copy
()
...
...
@@ -84,16 +86,14 @@ def set_task_days_kb(task_type, days, selected_days):
kb
.
add
(
InlineButton
(
f
"{emoji} {day_name}"
,
callback_data
=
f
"profile/
settings/mailing/{task_type}
/set-day/{new_days_str}"
callback_data
=
f
"profile/
mailing
/set-day/{new_days_str}"
))
if
i
==
3
:
kb
.
row
()
kb
.
row
()
kb
.
add
(
InlineButton
(
"Продолжить"
,
callback_data
=
f
"profile/settings/mailing/{task_type}/set-time"
))
kb
.
row
()
kb
.
add
(
InlineButton
(
"Назад"
,
callback_data
=
"profile/settings/mailing"
))
kb
.
add
(
InlineButton
(
"Сохранить"
,
callback_data
=
"profile/mailing"
))
return
kb
.
get_markup
()
def
user_packages_kb
(
pkgs
:
list
[
str
]):
return
packages_kb
(
"profile/pkg"
,
pkgs
,
back_callback
=
"profile/settings"
,
max_size
=
MAX_WATCH_LIST_SIZE
)
src/database/func.py
View file @
d1ebed32
import
json
from
datetime
import
time
as
dtime
from
.models
import
(
Maintainer
,
User
,
Package
,
ScheduledTask
,
Maintainer
,
User
,
Package
,
UserSchedule
,
UserMailing
,
WatchList
,
ChatSubscription
,
)
...
...
@@ -169,39 +172,64 @@ class PackageMethod:
return
package
class
Scheduler
Method
:
class
UserSchedule
Method
:
@classmethod
def
add
(
cls
,
user
:
User
,
task_type
:
str
,
days
:
str
,
time
:
str
):
task
=
ScheduledTask
(
user
=
user
,
task_type
=
task_type
,
days
=
days
,
send_time
=
time
)
task
.
save
()
def
get
(
cls
,
user
:
User
)
->
UserSchedule
|
None
:
return
UserSchedule
.
get_or_none
(
UserSchedule
.
user
==
user
)
@classmethod
def
get
(
cls
,
user
:
User
,
task_type
:
str
)
->
ScheduledTask
|
None
:
return
ScheduledTask
.
get_or_none
(
ScheduledTask
.
user
==
user
,
ScheduledTask
.
task_type
==
task_type
)
def
set
(
cls
,
user
:
User
,
days
:
str
,
send_time
):
schedule
,
created
=
UserSchedule
.
get_or_create
(
user
=
user
,
defaults
=
{
"days"
:
days
,
"send_time"
:
send_time
})
if
not
created
:
schedule
.
days
=
days
schedule
.
send_time
=
send_time
schedule
.
save
()
@classmethod
def
get_by_id
(
cls
,
task_id
:
int
):
return
ScheduledTask
.
get_or_none
(
ScheduledTask
.
id
==
task_id
)
def
update
(
cls
,
user
:
User
,
**
kwargs
):
UserSchedule
.
update
(
**
kwargs
)
.
where
(
UserSchedule
.
user
==
user
)
.
execute
(
)
@classmethod
def
update
(
cls
,
user
:
User
,
task_type
:
str
,
**
kwargs
):
ScheduledTask
.
update
(
**
kwargs
)
.
where
(
(
ScheduledTask
.
user
==
user
)
&
(
ScheduledTask
.
task_type
==
task_type
)
)
.
execute
()
def
ensure_exists
(
cls
,
user
:
User
):
UserSchedule
.
get_or_create
(
user
=
user
,
defaults
=
{
"days"
:
"0,1,2,3,4"
,
"send_time"
:
dtime
(
12
,
0
)})
@classmethod
def
all
(
cls
):
return
list
(
ScheduledTask
.
select
())
def
all_with_mailings
(
cls
):
return
list
(
UserSchedule
.
select
()
.
where
(
UserSchedule
.
days
!=
""
)
.
join
(
UserMailing
,
on
=
(
UserSchedule
.
user
==
UserMailing
.
user
))
.
switch
(
UserSchedule
)
.
distinct
()
)
class
UserMailingMethod
:
@classmethod
def
is_enabled
(
cls
,
user
:
User
,
mailing_type
:
str
)
->
bool
:
return
UserMailing
.
get_or_none
(
(
UserMailing
.
user
==
user
)
&
(
UserMailing
.
mailing_type
==
mailing_type
)
)
is
not
None
@classmethod
def
delete
(
cls
,
user
:
User
,
task_type
:
str
):
ScheduledTask
.
delete
()
.
where
(
(
ScheduledTask
.
user
==
user
)
&
(
ScheduledTask
.
task_type
==
task_type
)
def
enable
(
cls
,
user
:
User
,
mailing_type
:
str
):
UserMailing
.
get_or_create
(
user
=
user
,
mailing_type
=
mailing_type
)
@classmethod
def
disable
(
cls
,
user
:
User
,
mailing_type
:
str
):
UserMailing
.
delete
()
.
where
(
(
UserMailing
.
user
==
user
)
&
(
UserMailing
.
mailing_type
==
mailing_type
)
)
.
execute
()
@classmethod
def
get_enabled
(
cls
,
user
:
User
)
->
list
[
str
]:
return
[
row
.
mailing_type
for
row
in
UserMailing
.
select
()
.
where
(
UserMailing
.
user
==
user
)
]
class
WatchListMethod
:
@classmethod
...
...
@@ -271,6 +299,7 @@ class DB:
maintainer
=
MaintainerMethod
user
=
UserMethod
package
=
PackageMethod
scheduler
=
SchedulerMethod
schedule
=
UserScheduleMethod
mailing
=
UserMailingMethod
watch_list
=
WatchListMethod
chat_subscription
=
ChatSubscriptionMethod
src/database/models.py
View file @
d1ebed32
...
...
@@ -88,16 +88,25 @@ class ChatSubscription(BaseModel):
)
class
ScheduledTask
(
BaseModel
):
"""
Модель запланированной задачи
"""
class
UserSchedule
(
BaseModel
):
"""
Расписание рассылок пользователя
"""
user
=
ForeignKeyField
(
User
,
on_delete
=
"CASCADE"
,
to_field
=
"user_id"
)
task_type
=
CharField
()
days
=
CharField
()
user
=
ForeignKeyField
(
User
,
on_delete
=
"CASCADE"
,
to_field
=
"user_id"
,
unique
=
True
)
days
=
CharField
(
default
=
""
)
send_time
=
TimeField
()
class
Meta
:
table_name
=
"scheduled_tasks"
table_name
=
"user_schedules"
class
UserMailing
(
BaseModel
):
"""Включённые рассылки пользователя"""
user
=
ForeignKeyField
(
User
,
on_delete
=
"CASCADE"
,
to_field
=
"user_id"
)
mailing_type
=
CharField
()
class
Meta
:
table_name
=
"user_mailings"
indexes
=
(
((
'user'
,
'
task
_type'
),
True
),
((
'user'
,
'
mailing
_type'
),
True
),
)
src/handlers/profile.py
View file @
d1ebed32
...
...
@@ -18,22 +18,40 @@ dp = Dispatch()
wm
=
WaiterMachine
(
dp
)
DESCRIPTIONS
=
{
"watch"
:
"Новые версии пакетов текущего сопровождающего."
,
"bugs"
:
"Открытые баги текущего сопровождающего."
,
"pkg_watch"
:
"Новые версии отслеживаемых пакетов."
,
"task_events"
:
"Изменения статуса тасков текущего сопровождающего."
,
}
def
_mailing_text_and_markup
(
user
):
watch_task
=
DB
.
scheduler
.
get
(
user
,
'watch'
)
bugs_task
=
DB
.
scheduler
.
get
(
user
,
'bugs'
)
pkg_watch_task
=
DB
.
scheduler
.
get
(
user
,
'pkg_watch'
)
task_events
=
DB
.
scheduler
.
get
(
user
,
'task_events'
)
text
=
(
f
"{_bold('Уведомления:
\n\n
')}"
f
"{format_task(watch_task, 'Устаревшие пакеты')}"
f
"{format_task(bugs_task, 'Открытые баги')}"
f
"{format_task(pkg_watch_task, 'Устаревшие пакеты (по списку)')}"
f
"Таски girar: {'включено' if task_events else 'выключено'}
\n
"
)
markup
=
profile_keyboards
.
settings_tasks_kb
(
watch_task
,
bugs_task
,
pkg_watch_task
,
bool
(
task_events
)
)
schedule
=
DB
.
schedule
.
get
(
user
)
enabled
=
DB
.
mailing
.
get_enabled
(
user
)
text
=
_bold
(
"Рассылки:
\n\n
"
)
if
schedule
and
schedule
.
days
:
days
=
", "
.
join
(
DAYS
[
int
(
i
)]
for
i
in
schedule
.
days
.
split
(
","
))
time_str
=
f
"{schedule.send_time.hour}:{schedule.send_time.minute:02d}"
text
+=
f
"Расписание: {days} в {time_str}
\n\n
"
else
:
text
+=
"Расписание: не настроено
\n\n
"
for
type_id
,
label
in
profile_keyboards
.
SCHEDULED_TYPES
:
status
=
"вкл"
if
type_id
in
enabled
else
"выкл"
text
+=
_bold
(
f
"{label}: {status}
\n
"
)
text
+=
f
"{DESCRIPTIONS[type_id]}
\n\n
"
text
+=
_bold
(
"Уведомления:
\n\n
"
)
for
type_id
,
label
in
profile_keyboards
.
INSTANT_TYPES
:
status
=
"вкл"
if
type_id
in
enabled
else
"выкл"
text
+=
_bold
(
f
"{label}: {status}
\n
"
)
text
+=
f
"{DESCRIPTIONS[type_id]}
\n\n
"
markup
=
profile_keyboards
.
mailing_settings_kb
(
schedule
,
enabled
)
return
text
,
markup
...
...
@@ -148,10 +166,12 @@ async def branch_select_handler(cb: CallbackQuery, branch: str) -> None:
await
cb
.
answer
()
@dp.callback_query
(
PayloadMarkupRule
(
"profile/settings/mailing"
))
# --- Mailing ---
@dp.callback_query
(
PayloadEqRule
(
"profile/mailing"
))
async
def
mailing_handler
(
cb
:
CallbackQuery
)
->
None
:
user
=
DB
.
user
.
get
(
cb
.
from_user
.
id
)
if
user
is
None
:
if
not
user
:
return
text
,
markup
=
_mailing_text_and_markup
(
user
)
...
...
@@ -159,35 +179,46 @@ async def mailing_handler(cb: CallbackQuery) -> None:
await
cb
.
answer
()
@dp.callback_query
(
Payload
MarkupRule
(
"profile/settings/mailing/<task_type>/set-day/<days>
"
))
async
def
mailing_
set_days_handler
(
cb
:
CallbackQuery
,
task_type
:
str
,
days
:
str
)
:
@dp.callback_query
(
Payload
EqRule
(
"profile/mailing/days
"
))
async
def
mailing_
days_handler
(
cb
:
CallbackQuery
)
->
None
:
user
=
DB
.
user
.
get
(
cb
.
from_user
.
id
)
if
not
user
:
return
if
not
days
:
await
scheduler
.
remove_task
(
user
,
task_type
)
text
,
markup
=
_mailing_text_and_markup
(
user
)
await
cb
.
edit_text
(
text
,
reply_markup
=
markup
)
await
cb
.
answer
(
"Задача удалена"
)
schedule
=
DB
.
schedule
.
get
(
user
)
selected
=
list
(
map
(
int
,
schedule
.
days
.
split
(
","
)))
if
schedule
and
schedule
.
days
else
[]
await
cb
.
edit_text
(
"Выберите дни рассылки:"
,
reply_markup
=
profile_keyboards
.
schedule_days_kb
(
DAYS
,
selected
),
)
await
cb
.
answer
()
@dp.callback_query
(
PayloadMarkupRule
(
"profile/mailing/set-day/<days>"
))
async
def
mailing_set_day_handler
(
cb
:
CallbackQuery
,
days
:
str
)
->
None
:
user
=
DB
.
user
.
get
(
cb
.
from_user
.
id
)
if
not
user
:
return
task
=
scheduler
.
get_task
(
user
,
task_type
)
if
task
:
await
scheduler
.
update_task
(
user
,
task_type
,
days
=
days
)
else
:
await
scheduler
.
add_task
(
user
,
task_type
,
days
,
"12:00"
)
DB
.
schedule
.
ensure_exists
(
user
)
DB
.
schedule
.
update
(
user
,
days
=
days
)
await
edit_days_message
(
cb
,
task_type
)
selected
=
list
(
map
(
int
,
days
.
split
(
","
)))
if
days
else
[]
await
cb
.
edit_text
(
"Выберите дни рассылки:"
,
reply_markup
=
profile_keyboards
.
schedule_days_kb
(
DAYS
,
selected
),
)
await
cb
.
answer
()
@dp.callback_query
(
Payload
MarkupRule
(
"profile/settings/mailing/<task_type>
/set-time"
))
async
def
mailing_
time_handler
(
cb
:
CallbackQuery
,
task_type
:
str
)
:
@dp.callback_query
(
Payload
EqRule
(
"profile/mailing
/set-time"
))
async
def
mailing_
set_time_handler
(
cb
:
CallbackQuery
)
->
None
:
user
=
DB
.
user
.
get
(
cb
.
from_user
.
id
)
if
not
user
:
return
cancel
=
cancel_kb
(
"profile/
settings/
mailing"
)
cancel
=
cancel_kb
(
"profile/mailing"
)
await
cb
.
edit_text
(
"Введите время в формате ЧЧ:ММ (например, 09:30)"
,
reply_markup
=
cancel
)
try
:
...
...
@@ -204,39 +235,31 @@ async def mailing_time_handler(cb: CallbackQuery, task_type: str):
except
(
asyncio
.
CancelledError
,
LookupError
):
return
await
scheduler
.
update_task
(
user
,
task_type
,
send_time
=
time_str
)
DB
.
schedule
.
ensure_exists
(
user
)
await
scheduler
.
update_schedule
(
user
,
send_time
=
time_str
)
text
,
markup
=
_mailing_text_and_markup
(
user
)
await
cb
.
edit_text
(
text
,
reply_markup
=
markup
)
await
cb
.
answer
(
f
"Время: {time_str}"
)
@dp.callback_query
(
PayloadMarkupRule
(
"profile/
settings/mailing/task_events/on
"
))
async
def
task_events_on_handler
(
cb
:
CallbackQuery
)
:
@dp.callback_query
(
PayloadMarkupRule
(
"profile/
mailing/toggle/<mailing_type>
"
))
async
def
mailing_toggle_handler
(
cb
:
CallbackQuery
,
mailing_type
:
str
)
->
None
:
user
=
DB
.
user
.
get
(
cb
.
from_user
.
id
)
if
not
user
:
return
if
not
DB
.
scheduler
.
get
(
user
,
"task_events"
):
DB
.
scheduler
.
add
(
user
,
"task_events"
,
""
,
"00:00"
)
text
,
markup
=
_mailing_text_and_markup
(
user
)
await
cb
.
edit_text
(
text
,
reply_markup
=
markup
)
await
cb
.
answer
(
"Уведомления включены"
)
if
DB
.
mailing
.
is_enabled
(
user
,
mailing_type
):
await
scheduler
.
disable_mailing
(
user
,
mailing_type
)
else
:
await
scheduler
.
enable_mailing
(
user
,
mailing_type
)
@dp.callback_query
(
PayloadMarkupRule
(
"profile/settings/mailing/task_events/off"
))
async
def
task_events_off_handler
(
cb
:
CallbackQuery
):
user
=
DB
.
user
.
get
(
cb
.
from_user
.
id
)
if
not
user
:
return
DB
.
scheduler
.
delete
(
user
,
"task_events"
)
text
,
markup
=
_mailing_text_and_markup
(
user
)
await
cb
.
edit_text
(
text
,
reply_markup
=
markup
)
await
cb
.
answer
(
"Уведомления выключены"
)
await
cb
.
answer
()
@dp.callback_query
(
PayloadMarkupRule
(
"profile/settings/mailing/<task_type>/<action>"
))
async
def
mailing_action_handler
(
cb
:
CallbackQuery
,
task_type
:
str
,
action
:
str
):
await
edit_days_message
(
cb
,
task_type
)
# --- Packages ---
@dp.callback_query
(
PayloadEqRule
(
"profile/settings/packages"
))
async
def
user_packages_handler
(
cb
:
CallbackQuery
)
->
None
:
...
...
@@ -296,6 +319,8 @@ async def user_pkg_remove_handler(cb: CallbackQuery, package: str) -> None:
await
cb
.
answer
(
f
"{package} удалён"
)
# --- Menu ---
@dp.callback_query
(
PayloadEqRule
(
"command/menu"
))
async
def
menu_handler
(
cb
:
CallbackQuery
):
await
send_menu
(
cb
=
cb
)
...
...
@@ -304,29 +329,3 @@ async def menu_handler(cb: CallbackQuery):
@dp.message
(
Command
([
"menu"
,
"меню"
])
|
Text
([
"меню"
,
"menu"
]),
IsPrivate
())
async
def
menu_handler
(
m
:
Message
):
await
send_menu
(
m
=
m
)
def
format_task
(
task
,
title
):
if
not
task
:
return
f
"{title}: не настроено
\n
"
days
=
", "
.
join
(
DAYS
[
int
(
i
)]
for
i
in
task
.
days
.
split
(
","
))
time_str
=
f
"{task.send_time.hour}:{task.send_time.minute:02d}"
return
f
"{title}: {days} в {time_str}
\n
"
async
def
edit_days_message
(
cb
:
CallbackQuery
,
task_type
:
str
):
user
=
DB
.
user
.
get
(
cb
.
from_user
.
id
)
if
not
user
:
return
task
=
DB
.
scheduler
.
get
(
user
,
task_type
)
selected_days
=
list
(
map
(
int
,
task
.
days
.
split
(
","
))
)
if
task
and
task
.
days
else
[]
await
cb
.
edit_text
(
text
=
'Выберите дни и нажмите "Продолжить".'
,
reply_markup
=
profile_keyboards
.
set_task_days_kb
(
task_type
,
DAYS
,
selected_days
)
)
src/main.py
View file @
d1ebed32
...
...
@@ -9,9 +9,16 @@ from modules import scheduler
from
altrepo.api.errors
import
TooManyRequests
from
database.models
import
(
db
,
Maintainer
,
User
,
Package
,
ScheduledTask
,
WatchList
,
ChatSubscription
,
db
,
Maintainer
,
User
,
Package
,
UserSchedule
,
UserMailing
,
WatchList
,
ChatSubscription
,
)
from
database.func
import
DB
from
middlewares
import
UserMiddleware
from
services.test_api_version
import
test_api_version
...
...
@@ -25,12 +32,44 @@ bot.dispatch.load_from_dir("src/handlers")
bot
.
on
.
message
.
register_middleware
(
UserMiddleware
)
def
_migrate_scheduled_tasks
():
if
not
db
.
table_exists
(
"scheduled_tasks"
):
return
logger
.
info
(
"Migrating scheduled_tasks to new schema"
)
cursor
=
db
.
execute_sql
(
"SELECT user_id, task_type, days, send_time FROM scheduled_tasks"
)
for
user_id
,
task_type
,
days
,
send_time
in
cursor
.
fetchall
():
user
=
DB
.
user
.
get
(
user_id
)
if
not
user
:
continue
DB
.
mailing
.
enable
(
user
,
task_type
)
if
days
:
from
datetime
import
time
as
dtime
parts
=
send_time
.
split
(
":"
)
DB
.
schedule
.
set
(
user
,
days
,
dtime
(
int
(
parts
[
0
]),
int
(
parts
[
1
])))
db
.
execute_sql
(
"DROP TABLE scheduled_tasks"
)
logger
.
info
(
"Migration complete, old table dropped"
)
@bot.loop_wrapper.lifespan.on_startup
async
def
startup
():
db
.
create_tables
([
Maintainer
,
User
,
Package
,
ScheduledTask
,
WatchList
,
ChatSubscription
,
])
db
.
create_tables
(
[
Maintainer
,
User
,
Package
,
UserSchedule
,
UserMailing
,
WatchList
,
ChatSubscription
,
]
)
_migrate_scheduled_tasks
()
logger
.
info
(
"initializing ALTRepo"
)
await
altrepo
.
init
()
...
...
@@ -41,14 +80,8 @@ async def startup():
start_task_events_listener
()
logger
.
info
(
"initializing Scheduler"
)
scheduler
.
register_handler
(
"watch"
,
print
)
scheduler
.
register_handler
(
"bugs"
,
print
)
await
scheduler
.
init
()
await
bot
.
api
.
set_my_commands
(
commands
=
[
BotCommand
(
"watch"
,
"Отслеживание по пакетам"
),
...
...
src/modules/scheduler.py
View file @
d1ebed32
...
...
@@ -9,44 +9,52 @@ from services.bugs import bugs
from
services.pkg_watch
import
pkg_watch
MAILING_HANDLERS
=
{
"watch"
:
lambda
user
:
watch
(
user
),
"bugs"
:
lambda
user
:
bugs
(
user
),
"pkg_watch"
:
lambda
user
:
pkg_watch
(
DB
.
watch_list
.
get_list
(
user
.
user_id
),
user
.
user_id
),
}
class
CustomScheduler
:
def
__init__
(
self
):
self
.
scheduler
=
AsyncIOScheduler
()
self
.
handlers
=
{}
def
register_handler
(
self
,
task_type
:
str
,
func
):
self
.
handlers
[
task_type
]
=
func
async
def
init
(
self
):
self
.
scheduler
.
start
()
await
self
.
reload_tasks
()
async
def
add_task
(
self
,
user_id
:
int
,
task_type
:
str
,
days
:
str
,
time
:
str
):
h
,
m
=
map
(
int
,
time
.
split
(
":"
))
DB
.
scheduler
.
add
(
user_id
,
task_type
,
days
,
dtime
(
h
,
m
)
)
# --- User schedule ---
async
def
set_schedule
(
self
,
user
,
days
:
str
,
send_time
:
str
):
h
,
m
=
map
(
int
,
send_time
.
split
(
":"
))
DB
.
schedule
.
set
(
user
,
days
,
dtime
(
h
,
m
))
await
self
.
reload_tasks
()
async
def
update_
task
(
self
,
user_id
:
int
,
task_type
:
st
r
,
**
kwargs
):
DB
.
schedule
r
.
update
(
user_id
,
task_type
,
**
kwargs
)
async
def
update_
schedule
(
self
,
use
r
,
**
kwargs
):
DB
.
schedule
.
update
(
user
,
**
kwargs
)
await
self
.
reload_tasks
()
async
def
remove_task
(
self
,
user_id
:
int
,
task_type
:
str
):
DB
.
scheduler
.
delete
(
user_id
,
task_type
)
def
get_schedule
(
self
,
user
):
return
DB
.
schedule
.
get
(
user
)
# --- Mailing toggles ---
async
def
enable_mailing
(
self
,
user
,
mailing_type
:
str
):
DB
.
mailing
.
enable
(
user
,
mailing_type
)
DB
.
schedule
.
ensure_exists
(
user
)
await
self
.
reload_tasks
()
async
def
disable_mailing
(
self
,
user
,
mailing_type
:
str
):
DB
.
mailing
.
disable
(
user
,
mailing_type
)
await
self
.
reload_tasks
()
def
get_task
(
self
,
user_id
:
int
,
task_type
:
str
):
return
DB
.
scheduler
.
get
(
user_id
,
task_type
)
def
is_mailing_enabled
(
self
,
user
,
mailing_type
:
str
)
->
bool
:
return
DB
.
mailing
.
is_enabled
(
user
,
mailing_type
)
# --- Chat subscription
methods
---
# --- Chat subscription ---
async
def
add_chat_task
(
self
,
chat_id
:
int
,
task_type
:
str
,
days
:
str
,
time
:
str
):
h
,
m
=
map
(
int
,
time
.
split
(
":"
))
...
...
@@ -69,12 +77,12 @@ class CustomScheduler:
async
def
reload_tasks
(
self
):
self
.
scheduler
.
remove_all_jobs
()
for
task
in
DB
.
scheduler
.
all
():
if
not
task
.
days
:
for
schedule
in
DB
.
schedule
.
all_with_mailings
():
if
not
schedule
.
days
:
continue
hour
=
task
.
send_time
.
hour
minute
=
task
.
send_time
.
minute
for
day
in
task
.
days
.
split
(
","
):
hour
=
schedule
.
send_time
.
hour
minute
=
schedule
.
send_time
.
minute
for
day
in
schedule
.
days
.
split
(
","
):
self
.
scheduler
.
add_job
(
self
.
_job_runner
,
CronTrigger
(
...
...
@@ -82,11 +90,13 @@ class CustomScheduler:
hour
=
int
(
hour
),
minute
=
int
(
minute
)
),
args
=
[
task
.
id
],
id
=
f
"
{task.user.id}_{task.task_type
}_{day}"
args
=
[
schedule
.
user
.
user_
id
],
id
=
f
"
user_{schedule.user.user_id
}_{day}"
)
for
sub
in
DB
.
chat_subscription
.
all
():
if
not
sub
.
days
:
continue
hour
=
sub
.
send_time
.
hour
minute
=
sub
.
send_time
.
minute
for
day
in
sub
.
days
.
split
(
","
):
...
...
@@ -101,20 +111,16 @@ class CustomScheduler:
id
=
f
"chat_{sub.chat_id}_{sub.task_type}_{day}"
)
async
def
_job_runner
(
self
,
task_id
:
int
):
"""Вызывается планировщиком для пользовательских задач"""
task
=
DB
.
scheduler
.
get_by_id
(
task_id
)
if
not
task
:
async
def
_job_runner
(
self
,
user_id
:
int
):
user
=
DB
.
user
.
get
(
user_id
)
if
not
user
:
return
match
task
.
task_type
:
case
"watch"
:
await
watch
(
task
.
user
)
case
"bugs"
:
await
bugs
(
task
.
user
)
case
"pkg_watch"
:
packages
=
DB
.
watch_list
.
get_list
(
task
.
user
.
user_id
)
await
pkg_watch
(
packages
,
task
.
user
.
user_id
)
for
mailing_type
in
DB
.
mailing
.
get_enabled
(
user
):
handler
=
MAILING_HANDLERS
.
get
(
mailing_type
)
if
handler
:
await
handler
(
user
)
async
def
_chat_job_runner
(
self
,
sub_id
:
int
):
"""Вызывается планировщиком для чат-подписок"""
sub
=
DB
.
chat_subscription
.
get_by_id
(
sub_id
)
if
not
sub
:
return
...
...
src/services/task_events.py
View file @
d1ebed32
...
...
@@ -37,7 +37,7 @@ async def _listen():
users
=
DB
.
user
.
get_by_maintainer
(
data
.
owner
)
for
user
in
users
:
if
not
DB
.
scheduler
.
get
(
user
,
"task_events"
):
if
not
DB
.
mailing
.
is_enabled
(
user
,
"task_events"
):
continue
try
:
await
tg_api
.
send_message
(
chat_id
=
user
.
user_id
,
text
=
message
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment