mirror of
https://git.phreedom.club/localhost_frssoft/FMN_bot.git
synced 2025-04-05 16:26:30 +02:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
d84d86e2dd | |||
b3932f06f0 | |||
a6f2e89297 | |||
a053df3a8a | |||
53a26221ef | |||
e3b0e104ce | |||
e415e92d49 | |||
31a9d3d28c | |||
ba6fce2b56 | |||
9b1fc812d4 | |||
e696eaa375 | |||
973e25adfa | |||
1ecaddfaff | |||
7a78aa6244 | |||
07b469ccfc | |||
ee9e42e092 | |||
3ad6b279d7 | |||
7930d5d996 | |||
1558ab6a2d | |||
63f94ed574 | |||
c96056f9a4 | |||
e86d9829b0 | |||
81c13c3c96 | |||
6414cd863b | |||
176c25f200 | |||
8f07ed7f70 | |||
00f2dc33fb |
25
.gitea/workflows/gitea-actions.yaml
Normal file
25
.gitea/workflows/gitea-actions.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: Run tests
|
||||
run-name: ${{ gitea.actor }} is testing out test
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Run-Test:
|
||||
runs-on: ubuntu-act-latest
|
||||
steps:
|
||||
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||
- name: List files in the repository
|
||||
run: |
|
||||
ls ${{ gitea.workspace }}
|
||||
- run: python3 -m venv venv
|
||||
- run: . venv/bin/activate
|
||||
- run: pip install -r requirements.txt
|
||||
- run: pip install time_machine
|
||||
- run: cp config.py.example config.py
|
||||
- run: python test.py
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
13
README.md
13
README.md
|
@ -4,8 +4,10 @@
|
|||
Special for [XXIVProduction](https://xxivproduction.video/) and [drq@mastodon.ml](https://mastodon.ml/@drq).
|
||||
|
||||
## Первичная инициализация
|
||||
* Установка зависимостей python3
|
||||
* Установка зависимостей python3 и виртуального окружения
|
||||
```
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
|
@ -44,15 +46,16 @@ python3 fmn_bot.py
|
|||
```
|
||||
|
||||
## Использование
|
||||
Просто упомяните бота (упоминающий должен быть прописан в администраторах бота см. config.py), в воскресенье/понедельник, когда Fediverse Movie Night окончен. Бот инициализирует сборщика предложений и будет собирать фильмы, которые будут поступать в виде ссылок на imdb.com (также теперь есть поддержка фронтедов [libremdb](https://github.com/zyachel/libremdb)) и kinopoisk.ru в том треде, где его упомянули. Сбор будет завершен во вторник 16:00 (по-умолчанию) MSK+3. После этого всех кто не успел предложить фильм бот будет уведомлять, что сбор завершен и даст ссылку на голосовалку. По завершению голосовалки (суббота 16:00), будет вычисляться фильм-победитель на FMN, если у нескольких фильмов одинаковые голоса, то будет создан tie breaker. Победивший фильм будет записан сразу как "просмотренный", чтобы не добавлять его на следующий FMN повторно. Далее бот будет ждать очередного упоминания.
|
||||
Просто упомяните бота (упоминающий должен быть прописан в администраторах бота см. config.py), в воскресенье/понедельник, когда Fediverse Movie Night окончен (В новой версии, теперь встроена функция автоматического срабатывания в полночь, если упоминания не было). Бот инициализирует сборщика предложений и будет собирать фильмы, которые будут поступать в виде ссылок на imdb.com (также теперь есть поддержка фронтедов [libremdb](https://github.com/zyachel/libremdb)) и kinopoisk.ru в том треде, где его упомянули. Сбор будет завершен во вторник 16:00 (по-умолчанию) MSK+3. После этого всех кто не успел предложить фильм бот будет уведомлять, что сбор завершен и даст ссылку на голосовалку. По завершению голосовалки (суббота 16:00), будет вычисляться фильм-победитель на FMN, если у нескольких фильмов одинаковые голоса, то будет создан tie breaker. Победивший фильм будет записан сразу как "просмотренный", чтобы не добавлять его на следующий FMN повторно. Далее бот будет ждать очередного упоминания.
|
||||
|
||||
Note: Рекомендуется использовать ссылки на imdb.com, так как локальная база IMDB надёжнее, чем сетевой сторонний API Кинопоиска.
|
||||
Note2: Список доступных для приёма инстансов libremdb обновляется вручную и может не соотвествовать официальному.
|
||||
|
||||
## Список поддерживаемых инстансов libremdb:
|
||||
* https://libremdb.herokuapp.com
|
||||
* https://libremdb.pussthecat.org
|
||||
* https://libremdbeu.herokuapp.com
|
||||
* https://libremdb.leemoon.network/ (Спасибо [Саре](https://lamp.leemoon.network/@sarahquartz) в рамках self-host проекта [Leemoon Network 🍋](https://leemoon.network))
|
||||
* https://libremdb.herokuapp.com/
|
||||
* https://libremdb.pussthecat.org/
|
||||
* https://libremdbeu.herokuapp.com/
|
||||
* https://lmdb.tokhmi.xyz/
|
||||
* https://libremdb.esmailelbob.xyz/
|
||||
* http://libremdb.lqs5fjmajyp7rvp4qvyubwofzi6d4imua7vs237rkc4m5qogitqwrgyd.onion/
|
||||
|
|
|
@ -2,6 +2,9 @@ admins_bot = ('drq@mastodon.ml',) # Адреса админов бота, кот
|
|||
# Example: ('admin_user', 'another_admin_user2@example.example') or ('admin_user',)
|
||||
bot_acct = 'fmn' # Ник бота на инстансе
|
||||
instance = 'pleroma.dark-alexandr.net' # Инстанс, где будет запущен бот
|
||||
# Ссылка на live stream FMN (API)
|
||||
peertube_stream_url = 'https://xxivproduction.video/api/v1/videos/1FZeVVVzWBFShaxQVkYiXd'
|
||||
|
||||
|
||||
# Лимиты
|
||||
limit_movies_per_user = 2 # Ограничение количества фильмов на одного пользователя
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
from src import listener_context, listener_mention, imdb_datasets_worker
|
||||
from src import listener_context, listener_mention
|
||||
from config import logger_default_level
|
||||
from loguru import logger
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
logger.remove()
|
||||
logger.add(sink=sys.stderr, level=logger_default_level)
|
||||
logger.add(sink='suggestions.log', level='INFO', filter='src.listener_context')
|
||||
|
||||
listener_mention.run_scan_notif() # Слушаем упоминания в фоне
|
||||
listener_mention.run_scan_notif() # Слушаем упоминания в фоне
|
||||
time.sleep(1)
|
||||
|
||||
listener_context.scan_context_thread() # Слушаем тред на новые предложения фильмов
|
||||
listener_context.scan_context_thread() # Слушаем тред на новые предложения фильмов
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
BIN
src/FMN.webp
BIN
src/FMN.webp
Binary file not shown.
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 13 B |
1
src/FMN.webp
Symbolic link
1
src/FMN.webp
Symbolic link
|
@ -0,0 +1 @@
|
|||
FMN_prev.webp
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 13 B |
BIN
src/FMN_new_year.webp
Normal file
BIN
src/FMN_new_year.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
BIN
src/FMN_prev.webp
Normal file
BIN
src/FMN_prev.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
|
@ -1,6 +1,5 @@
|
|||
from config import instance
|
||||
import time
|
||||
import json
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
|
@ -35,7 +34,6 @@ def get_notifications():
|
|||
logger.info('Retrying get notificatios...')
|
||||
|
||||
|
||||
|
||||
def mark_as_read_notification(id_notification):
|
||||
success = 0
|
||||
while success == 0:
|
||||
|
@ -51,18 +49,22 @@ def mark_as_read_notification(id_notification):
|
|||
|
||||
|
||||
def get_status_context(status_id):
|
||||
retry = 0
|
||||
success = 0
|
||||
while success == 0:
|
||||
try:
|
||||
r = s.get(instance_point + f"/statuses/{status_id}/context")
|
||||
r = s.get(instance_point + f"/statuses/{status_id}/context", timeout=30)
|
||||
r.raise_for_status()
|
||||
success = 1
|
||||
return r.json()
|
||||
except:
|
||||
except Exception as E:
|
||||
logger.exception(f'Ошибка получения контекста треда {status_id}')
|
||||
time.sleep(30)
|
||||
logger.info('Повторный запрос треда...')
|
||||
|
||||
retry += 1
|
||||
if retry > 5:
|
||||
raise IOError(f'Фетчинг треда поломан! {E}')
|
||||
|
||||
|
||||
def get_status(status_id):
|
||||
success = 0
|
||||
|
@ -78,8 +80,7 @@ def get_status(status_id):
|
|||
logger.info(f'Retrying get status {status_id}')
|
||||
|
||||
|
||||
|
||||
def post_status(text, reply_to_status_id=None, poll_options=None, poll_expires=345600, attachments=None):
|
||||
def post_status(text, reply_to_status_id=None, poll_options=None, poll_expires=345600, attachments=None, visibility='unlisted'):
|
||||
poll = None
|
||||
if poll_options is not None:
|
||||
poll = {
|
||||
|
@ -90,7 +91,7 @@ def post_status(text, reply_to_status_id=None, poll_options=None, poll_expires=3
|
|||
params = {
|
||||
"status": text,
|
||||
"in_reply_to_id": reply_to_status_id,
|
||||
"visibility": "unlisted",
|
||||
"visibility": visibility,
|
||||
"content_type": "text/plain",
|
||||
"language": "ru",
|
||||
"poll": poll
|
||||
|
@ -136,4 +137,3 @@ def mute_user(acct_id=str, acct=str, duration=None):
|
|||
logger.exception(f'Ошибка глушения {acct}')
|
||||
time.sleep(5)
|
||||
logger.info(f'Повторное глушение {acct}...')
|
||||
|
||||
|
|
|
@ -113,4 +113,3 @@ def reset_poll():
|
|||
'''Сброс содержимого предложки-опроса'''
|
||||
c.execute("DELETE FROM poll")
|
||||
conn.commit()
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ def create_poll_movies(text=text_create_poll(), poll_expires=345600):
|
|||
attaches = [upload_attachment('src/FMN.webp')]
|
||||
except Exception as E:
|
||||
logger.error(f"attachements can't do upload: {E}")
|
||||
|
||||
|
||||
poll_status_id = post_status(text, None, formated_poll_options,
|
||||
poll_expires=poll_expires, attachments=attaches)
|
||||
logger.success('Голосовалка создана')
|
||||
|
@ -62,7 +62,7 @@ def get_winner_movie(poll_status_id=str):
|
|||
for option in poll['options']:
|
||||
votes_count = option['votes_count']
|
||||
votes_counters.append(votes_count)
|
||||
|
||||
|
||||
write_votes(votes_counters)
|
||||
voted_movies = read_votes()
|
||||
max_vote = voted_movies[0][4]
|
||||
|
@ -108,4 +108,3 @@ def create_tie_breaker(count_tie=1):
|
|||
else:
|
||||
poll_expires = 4*60*60
|
||||
tie_poll = create_poll_movies("TIE BREAKER!!!\n\nВыбираем из победителей!", poll_expires)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from loguru import logger
|
|||
|
||||
states_file = 'fmn_states.json'
|
||||
|
||||
|
||||
class states_stor:
|
||||
states = None
|
||||
|
||||
|
|
|
@ -3,13 +3,10 @@ from src.fedi_api import get_status_context, get_status, post_status, mute_user
|
|||
from src.kinopoisk_api import get_kinopoisk_movie_to_imdb
|
||||
from src.imdb_datasets_worker import get_title_by_id
|
||||
from src.fmn_database import add_movie_to_poll, get_already_watched, get_suggested_movies_count
|
||||
from src.fmn_states_db import states_stor, write_states
|
||||
from src.fmn_states_db import states_stor
|
||||
from src.fmn_poll import create_poll_movies, get_winner_movie
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
from dateutil.parser import parse as dateutilparse
|
||||
from dateutil.relativedelta import relativedelta, TU
|
||||
from collections import Counter
|
||||
from loguru import logger
|
||||
|
||||
|
@ -23,13 +20,13 @@ def parse_links(text=str):
|
|||
|
||||
|
||||
def parse_links_imdb(text=str):
|
||||
regex = r"imdb\.com/|libremdb\.pussthecat\.org/|libremdb\.esmailelbob\.xyz/|libremdb\.herokuapp\.com/|libremdbeu\.herokuapp\.com/|lmdb\.tokhmi\.xyz/|libremdb\.lqs5fjmajyp7rvp4qvyubwofzi6d4imua7vs237rkc4m5qogitqwrgyd\.onion/"
|
||||
regex = r"imdb\.com/|libremdb\.leemoon\.network/|libremdb\.pussthecat\.org/|libremdb\.esmailelbob\.xyz/|libremdb\.herokuapp\.com/|libremdbeu\.herokuapp\.com/|lmdb\.tokhmi\.xyz/|libremdb\.lqs5fjmajyp7rvp4qvyubwofzi6d4imua7vs237rkc4m5qogitqwrgyd\.onion/"
|
||||
if re.search(regex, text.lower(), flags=re.MULTILINE):
|
||||
imdb_ids = re.findall(r"tt(\d{1,})", text.lower())
|
||||
if imdb_ids != []:
|
||||
return imdb_ids[:limit_movies_per_user]
|
||||
|
||||
|
||||
|
||||
def scan_context_thread():
|
||||
fail_limit = Counter()
|
||||
while True:
|
||||
|
@ -58,7 +55,7 @@ def scan_context_thread():
|
|||
else:
|
||||
endings = int(stop_thread_scan) - time_now
|
||||
logger.debug(f'Осталось до закрытия сбора: {endings}')
|
||||
if reserve_time: # Reduce instance load
|
||||
if reserve_time: # Reduce instance load
|
||||
time.sleep(30)
|
||||
get_thread_time = time.time()
|
||||
descendants = get_status_context(status_id)['descendants']
|
||||
|
@ -72,6 +69,10 @@ def scan_context_thread():
|
|||
|
||||
for status in descendants:
|
||||
id_st = status['id']
|
||||
visibility = status['visibility']
|
||||
if visibility == 'direct':
|
||||
# Игнорируем личку
|
||||
continue
|
||||
in_reply_acct = status['in_reply_to_account_id']
|
||||
in_reply_id = status['in_reply_to_id']
|
||||
muted = status['muted']
|
||||
|
@ -79,14 +80,14 @@ def scan_context_thread():
|
|||
acct_id = status['account']['id']
|
||||
content = status['pleroma']['content']['text/plain']
|
||||
|
||||
if id_st in replyed: # Игнорировать уже отвеченное
|
||||
if id_st in replyed: # Игнорировать уже отвеченное
|
||||
continue
|
||||
if muted is True:
|
||||
continue
|
||||
if fail_limit[acct] >= max_fail_limit: # Игнорировать пользователя если он превысил fail limit
|
||||
if fail_limit[acct] >= max_fail_limit: # Игнорировать пользователя если он превысил fail limit
|
||||
mute_user(acct_id, acct, int(states.get('max_mute_time')) - time_now)
|
||||
logger.warning(f'{acct} игнорируется - превышение fail limit')
|
||||
break # Нужно обновить тред, чтобы muted на заглушенном стал True
|
||||
break # Нужно обновить тред, чтобы muted на заглушенном стал True
|
||||
|
||||
parsed_result = parse_links(content)
|
||||
parsed_result_imdb = parse_links_imdb(content)
|
||||
|
@ -99,23 +100,34 @@ def scan_context_thread():
|
|||
logger.info(f'{acct} был уведомлен о завершенной голосовалке')
|
||||
fail_limit[acct] += 1
|
||||
continue
|
||||
|
||||
|
||||
index_type = 1
|
||||
index_name = 2
|
||||
index_ru_name = 3
|
||||
index_year = 4
|
||||
|
||||
message_writer = []
|
||||
success = False
|
||||
if parsed_result and parsed_result_imdb:
|
||||
post_status('❌ Не смешивайте IMDB и кинопоиск в одном посте, пожалуйста.', id_st)
|
||||
fail_limit[acct] += 1
|
||||
continue
|
||||
if parsed_result is not None:
|
||||
print(parsed_result)
|
||||
suggested_movies = get_kinopoisk_movie_to_imdb(parsed_result)
|
||||
message_writer.append('⚠️ внимание при использовании Кинопоиска стабильность может быть понижена')
|
||||
if suggested_movies is None:
|
||||
post_status('❌ Не удалось выполнить запрос: возможно некорректный тип фильма, попробуйте использовать imdb.com', id_st)
|
||||
post_status('❌ Не удалось выполнить запрос: возможно некорректный тип фильма, попробуйте использовать https://libremdb.leemoon.network', id_st)
|
||||
fail_limit[acct] += 1
|
||||
continue
|
||||
elif parsed_result_imdb is not None:
|
||||
suggested_movies = get_title_by_id(parsed_result_imdb)
|
||||
|
||||
message_writer = []
|
||||
success = False
|
||||
|
||||
if suggested_movies is None:
|
||||
post_status('❌ Фильм(ы) не найден в базе данных IMDB, пожалуйста обратитесь к администратору, чтобы обновить базу. Примечание: IMDB выкладывает новые изменения не сразу.', id_st)
|
||||
fail_limit[acct] += 1
|
||||
continue
|
||||
|
||||
for movie in suggested_movies:
|
||||
logger.debug(str(movie))
|
||||
if movie[index_type] == "404":
|
||||
|
@ -132,7 +144,7 @@ def scan_context_thread():
|
|||
name_ru = movie[index_ru_name]
|
||||
year = movie[index_year]
|
||||
movie_string = f"{name_ru} / {name}, {year}"
|
||||
|
||||
|
||||
if name is None:
|
||||
movie_string = f"{name_ru}, {year}"
|
||||
if name_ru is None:
|
||||
|
@ -146,8 +158,8 @@ def scan_context_thread():
|
|||
logger.warning(f'Предложение {acct} было отклонено: количество уже предложенных фильмов превышает\равно {limit_all_movies_poll}')
|
||||
fail_limit[acct] += 1
|
||||
break
|
||||
|
||||
if get_already_watched(name, name_ru, year) == True:
|
||||
|
||||
if get_already_watched(name, name_ru, year) is True:
|
||||
message_writer.append(f"ℹ️ Этот фильм уже был на FMN: {movie_string}")
|
||||
logger.info(f'Попытка предложить уже просмотренный фильм: {acct} {name} {name_ru} {year}')
|
||||
fail_limit[acct] += 1
|
||||
|
@ -173,5 +185,3 @@ def scan_context_thread():
|
|||
post_status('\n'.join(message_writer) + message, id_st)
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
|
||||
|
|
|
@ -1,14 +1,25 @@
|
|||
from src.fedi_api import get_notifications, mark_as_read_notification, post_status, upload_attachment
|
||||
from src.fmn_states_db import write_states, states_stor
|
||||
from config import admins_bot, limit_movies_per_user, limit_all_movies_poll, hour_poll_posting, fmn_next_watching_hour
|
||||
from src.sheduler import check_stop_thread_scan
|
||||
from config import admins_bot, limit_movies_per_user, limit_all_movies_poll, hour_poll_posting, fmn_next_watching_hour, peertube_stream_url
|
||||
|
||||
import threading, time
|
||||
import threading
|
||||
import time
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from dateutil.parser import parse as dateutilparse
|
||||
from dateutil.relativedelta import relativedelta, TU, SU
|
||||
from loguru import logger
|
||||
|
||||
|
||||
@logger.catch
|
||||
def get_peertube_stream_name():
|
||||
try:
|
||||
return requests.get(peertube_stream_url).json()['name']
|
||||
except Exception as E:
|
||||
return f"[не удалось получить название {E}]"
|
||||
|
||||
|
||||
@logger.catch
|
||||
def get_control_mention():
|
||||
while True:
|
||||
|
@ -19,7 +30,8 @@ def get_control_mention():
|
|||
now_hour = time_now.hour
|
||||
if now_week not in (0, 6):
|
||||
continue
|
||||
if now_week == 6 and now_hour < fmn_next_watching_hour: # Предотвращение работы в холстую до начала сеанса
|
||||
if now_week == 6 and now_hour < fmn_next_watching_hour:
|
||||
# Предотвращение работы в холстую до начала сеанса
|
||||
continue
|
||||
post_exists = states.get('last_thread_id')
|
||||
if post_exists:
|
||||
|
@ -32,30 +44,81 @@ def get_control_mention():
|
|||
seen = i['pleroma']['is_seen']
|
||||
acct_mention = i['account']['acct']
|
||||
reply_to_id = i['status']['in_reply_to_id']
|
||||
if acct_mention in admins_bot and seen == False and reply_to_id == None and now_week in (0, 6):
|
||||
if acct_mention in admins_bot and seen is False and reply_to_id is None and now_week in (0, 6):
|
||||
logger.success(f'Найдено упоминание от {acct_mention}')
|
||||
st_id = i['status']['id']
|
||||
st_date = i['status']['created_at']
|
||||
thread_created_at = dateutilparse(st_date)
|
||||
|
||||
delta = relativedelta(hour=hour_poll_posting, minute=0, second=0, weekday=TU(1))
|
||||
delta = relativedelta(
|
||||
hour=hour_poll_posting, minute=0, second=0, weekday=TU(1))
|
||||
stop_thread_scan = thread_created_at + delta
|
||||
movies_accept_time = stop_thread_scan.strftime('%H:%M %d.%m.%Y по Москве')
|
||||
stop_thread_scan = time.mktime(time.struct_time(stop_thread_scan.timetuple()))
|
||||
|
||||
if now_week == 6: # Фикс стыков двух недель. Если вс, то расчитываем на следующую неделю
|
||||
# if check_stop_thread_scan(stop_thread_scan) == stop_thread_scan: # broken because pytz
|
||||
# logger.success("проверка на корректность даты пройдена")
|
||||
# else:
|
||||
# logger.warning("проверка на корректность даты не пройдена")
|
||||
# stop_thread_scan = check_stop_thread_scan(stop_thread_scan)
|
||||
movies_accept_time = stop_thread_scan.strftime(
|
||||
'%H:%M %d.%m.%Y по Москве')
|
||||
stop_thread_scan = time.mktime(
|
||||
time.struct_time(stop_thread_scan.timetuple()))
|
||||
|
||||
if now_week == 6: # Фикс стыков двух недель. Если вс, то расчитываем на следующую неделю
|
||||
next_week = 2
|
||||
else:
|
||||
next_week = 1
|
||||
next_movie_watching_delta = relativedelta(hour=fmn_next_watching_hour, minute=0, second=0, weekday=SU(next_week))
|
||||
next_movie_watching_delta = relativedelta(
|
||||
hour=fmn_next_watching_hour, minute=0, second=0, weekday=SU(next_week))
|
||||
next_movie_watching = time_now + next_movie_watching_delta
|
||||
max_mute_time = time.mktime(time.struct_time(next_movie_watching.timetuple())) # Глушение до следующего сеанса FMN.
|
||||
# Глушение до следующего сеанса FMN.
|
||||
max_mute_time = time.mktime(
|
||||
time.struct_time(next_movie_watching.timetuple()))
|
||||
next_movie_watching = next_movie_watching.strftime('%d.%m.%Y')
|
||||
post_status(start_collect_movies_text(movies_accept_time, next_movie_watching), st_id, attachments=[upload_attachment('src/FMN.webp')])
|
||||
post_status(start_collect_movies_text(movies_accept_time, next_movie_watching),
|
||||
st_id, attachments=[upload_attachment('src/FMN.webp')])
|
||||
|
||||
time.sleep(0.2)
|
||||
mark_as_read_notification(i['id'])
|
||||
|
||||
states_stor.states['max_mute_time'] = int(max_mute_time)
|
||||
states_stor.states['stop_thread_scan'] = int(stop_thread_scan)
|
||||
states_stor.states['last_thread_id'] = st_id
|
||||
write_states(states_stor.states)
|
||||
break
|
||||
if now_hour == 0:
|
||||
logger.warning('Автоматический триггер в полночи сработал')
|
||||
thread_created_at = time_now
|
||||
|
||||
delta = relativedelta(
|
||||
hour=hour_poll_posting, minute=0, second=0, weekday=TU(1))
|
||||
stop_thread_scan = thread_created_at + delta
|
||||
if check_stop_thread_scan(stop_thread_scan) == stop_thread_scan:
|
||||
logger.success("проверка на корректность даты пройдена")
|
||||
else:
|
||||
logger.warning("проверка на корректность даты не пройдена")
|
||||
# stop_thread_scan = check_stop_thread_scan(stop_thread_scan)
|
||||
movies_accept_time = stop_thread_scan.strftime(
|
||||
'%H:%M %d.%m.%Y по Москве')
|
||||
stop_thread_scan = time.mktime(
|
||||
time.struct_time(stop_thread_scan.timetuple()))
|
||||
|
||||
if now_week == 6: # Фикс стыков двух недель. Если вс, то расчитываем на следующую неделю
|
||||
next_week = 2
|
||||
else:
|
||||
next_week = 1
|
||||
next_movie_watching_delta = relativedelta(
|
||||
hour=fmn_next_watching_hour, minute=0, second=0, weekday=SU(next_week))
|
||||
next_movie_watching = time_now + next_movie_watching_delta
|
||||
# Глушение до следующего сеанса FMN.
|
||||
max_mute_time = time.mktime(
|
||||
time.struct_time(next_movie_watching.timetuple()))
|
||||
next_movie_watching = next_movie_watching.strftime('%d.%m.%Y')
|
||||
watched_movie_name = get_peertube_stream_name()
|
||||
st_id = post_status("Спасибо что посмотрели " + watched_movie_name + "\n\n" + start_collect_movies_text(movies_accept_time, next_movie_watching) +
|
||||
'\n\n@rf@mastodon.ml', reply_to_status_id=None, attachments=[upload_attachment('src/FMN.webp')], visibility="public")['id']
|
||||
|
||||
time.sleep(0.2)
|
||||
|
||||
states_stor.states['max_mute_time'] = int(max_mute_time)
|
||||
states_stor.states['stop_thread_scan'] = int(stop_thread_scan)
|
||||
states_stor.states['last_thread_id'] = st_id
|
||||
|
@ -70,7 +133,7 @@ def start_collect_movies_text(movies_accept_time=str, next_movie_watching=str):
|
|||
|
||||
Напоминаем правила:
|
||||
- Мы принимаем на просмотр полнометражные художественные фильмы;
|
||||
- Прием варианта осуществляется путем публикации ссылки на этот фильм на IMDB (libremdb) или Кинопоиске в этом треде;
|
||||
- Прием варианта осуществляется путем публикации ссылки на этот фильм на IMDB https://libremdb.leemoon.network/ или Кинопоиске в этом треде;
|
||||
- Нам не подходят: сериалы, короткометражные и документальные фильмы;
|
||||
- Максимальное количество вариантов, предложенных одним человеком не должно превышать {limit_movies_per_user};
|
||||
- Всего может быть собрано до {limit_all_movies_poll} фильмов;
|
||||
|
@ -83,7 +146,7 @@ def start_collect_movies_text(movies_accept_time=str, next_movie_watching=str):
|
|||
'''.replace('\t', '')
|
||||
return text
|
||||
|
||||
|
||||
def run_scan_notif():
|
||||
scan_notif = threading.Thread(target=get_control_mention, daemon=True)
|
||||
scan_notif.start()
|
||||
|
||||
|
|
23
src/sheduler.py
Normal file
23
src/sheduler.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import calendar
|
||||
import datetime
|
||||
from config import hour_poll_posting
|
||||
|
||||
|
||||
def check_stop_thread_scan(suggested_date):
|
||||
suggested_month = suggested_date.month
|
||||
suggested_year = suggested_date.year
|
||||
tuesdays = []
|
||||
for i in calendar.Calendar().itermonthdays4(suggested_year, suggested_month):
|
||||
if i[3] == 1 and datetime.datetime(year=i[0], month=i[1], day=i[2]) > suggested_date:
|
||||
tuesdays.append(datetime.datetime(
|
||||
year=i[0], month=i[1], day=i[2], hour=hour_poll_posting))
|
||||
if tuesdays == []:
|
||||
shift_for_next_week = suggested_date + datetime.timedelta(days=1)
|
||||
suggested_month = shift_for_next_week.month
|
||||
suggested_year = shift_for_next_week.year
|
||||
for i in calendar.Calendar().itermonthdays4(suggested_year, suggested_month):
|
||||
if i[3] == 1 and datetime.datetime(year=i[0], month=i[1], day=i[2]) > suggested_date:
|
||||
tuesdays.append(datetime.datetime(
|
||||
year=i[0], month=i[1], day=i[2], hour=hour_poll_posting))
|
||||
|
||||
return tuesdays[0] # near tuesday
|
30
test.py
Normal file
30
test.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import unittest
|
||||
import datetime
|
||||
import time_machine
|
||||
from src import sheduler
|
||||
|
||||
|
||||
class TestDate(unittest.TestCase):
|
||||
def test_date_tue(self):
|
||||
"""
|
||||
test near tue
|
||||
тестирует корректность расчета даты на след. вторник
|
||||
условия: расчет начат от воскресенья или понедельника (случайно)
|
||||
"""
|
||||
import calendar
|
||||
import random
|
||||
for month in range(1, 13):
|
||||
sundays = [i for i in calendar.Calendar().itermonthdays4(year=2024, month=month) if i[3] == 6]
|
||||
for sunday in sundays:
|
||||
fake_date = datetime.datetime(year=sunday[0], month=sunday[1], day=sunday[2], hour=random.randint(21, 23)) + datetime.timedelta(days=random.randint(0, 1))
|
||||
with time_machine.travel(fake_date):
|
||||
try:
|
||||
result = sheduler.check_stop_thread_scan(fake_date)
|
||||
print("current fake date: " + fake_date.strftime("%Y.%m.%d %H:%M") + f" near tue: {result}")
|
||||
except Exception as E:
|
||||
print("fail fake date: " + fake_date.strftime("%Y.%m.%d %H:%M") + f", err: {E}")
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user