Compare commits

...

33 Commits
v2.0.1 ... main

Author SHA1 Message Date
d84d86e2dd TypeError: can't compare offset-naive and offset-aware datetimes
All checks were successful
Run tests / Run-Test (push) Successful in 50s
2024-07-29 09:28:18 +03:00
b3932f06f0 forgot config
All checks were successful
Run tests / Run-Test (push) Successful in 15s
2024-07-21 19:23:37 +03:00
a6f2e89297 try gitea actions
Some checks failed
Run tests / Run-Test (push) Failing after 15s
2024-07-21 19:18:27 +03:00
a053df3a8a test gitea actions
Some checks failed
Run tests / Run-Test (push) Failing after 45s
2024-07-21 19:12:06 +03:00
53a26221ef update README 2024-07-16 17:47:52 +03:00
e3b0e104ce test date 2024-07-16 17:05:22 +03:00
e415e92d49 typo in conf... 2024-07-16 16:10:03 +03:00
31a9d3d28c typo in conf... 2024-07-16 16:07:50 +03:00
ba6fce2b56 added some test for test test :D 2024-07-16 16:05:18 +03:00
9b1fc812d4 some fix, well fucked up gregorian calendar... 2024-07-16 16:04:36 +03:00
e696eaa375 peertube api url in config file 2024-07-14 19:04:04 +03:00
973e25adfa Added test calendar abstraction, thanks lina@mastodon.ml
https://mastodon.ml/@lina/112768409279788952
2024-07-12 12:17:05 +03:00
1ecaddfaff some pep8 fixes 2024-07-11 22:07:09 +03:00
7a78aa6244 removed parser at 00:00 and datetime inject directly 2024-07-11 12:15:58 +03:00
07b469ccfc not datetime in parser 2024-07-08 08:56:59 +03:00
ee9e42e092 fix positional args 2024-07-06 00:12:19 +03:00
3ad6b279d7 added display name watched movie (in forgot case) 2024-07-05 23:48:24 +03:00
7930d5d996 try automatically run thread if hour is 0 2024-07-04 14:59:47 +03:00
1558ab6a2d symlinked picture FMN new year and default picture 2024-07-04 12:50:48 +03:00
63f94ed574 added timeout for context fetch 2023-07-28 10:38:13 +03:00
c96056f9a4 ignore direct 2023-07-27 17:36:16 +03:00
e86d9829b0 do not mix IMDB and KP in one post 2023-07-27 17:22:06 +03:00
81c13c3c96 import cleanup and retries for context thread; pep8 2023-07-27 17:21:22 +03:00
6414cd863b Link on libremdb Leemoon Network frontend; Warning about unstable if using KP 2023-07-10 01:55:46 +03:00
176c25f200 Add new libremdb instance 2023-04-23 12:50:29 +03:00
8f07ed7f70 Notice if no avalaible movies in IMDB database 2023-03-01 00:41:58 +03:00
00f2dc33fb Back to original FMN picture 2023-01-21 16:16:36 +03:00
00b80493f7 watching list info 2023-01-15 22:20:37 +03:00
b58f39bfe1 Fix: forget states in states_stor class 2022-12-26 01:04:37 +03:00
0bdb95ee30 Criminally Cute mirror :) 2022-12-19 23:33:54 +03:00
885bfb5a53 New year picture FMN and default format webp (compact) 2022-12-17 19:23:19 +03:00
b166016df8 Add class for states
Lower read\write in states file
No attempt force upload attachment (server issues)
More logger catchers
2022-12-17 16:40:14 +03:00
3eebf7266c Added mirrors file 2022-12-01 03:32:31 +03:00
17 changed files with 264 additions and 91 deletions

View 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 }}."

5
MIRRORS Normal file
View File

@ -0,0 +1,5 @@
# Avalaible mirrors
https://inex.dev/localhost_frssoft/FMN_bot.git
https://git.macaw.me/localhost_frssoft/FMN_bot.git
https://code.criminallycute.fi/localhost_frssoft/FMN_bot.git
https://git.poridge.club/localhost_frssoft/FMN_bot.git

View File

@ -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/

View File

@ -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 # Ограничение количества фильмов на одного пользователя

View File

@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 KiB

1
src/FMN.webp Symbolic link
View File

@ -0,0 +1 @@
FMN_prev.webp

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -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,17 +49,21 @@ 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):
@ -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
@ -116,17 +117,9 @@ def upload_attachment(file_path):
params = {
"description": "Fediverse Movie Night\nВоскресенье, 21:00\nLIVE ON XXIV Production",
}
success = 0
while success == 0:
try:
r = s.post(instance_point + "/media", params, files=file)
r.raise_for_status()
success = 1
return r.json()['id']
except:
logger.exception(f'Error uploading {file_path} attachment')
time.sleep(5)
logger.info(f'Retrying upload {file_path}...')
r = s.post(instance_point + "/media", params, files=file, timeout=30)
r.raise_for_status()
return r.json()['id']
def mute_user(acct_id=str, acct=str, duration=None):
@ -144,4 +137,3 @@ def mute_user(acct_id=str, acct=str, duration=None):
logger.exception(f'Ошибка глушения {acct}')
time.sleep(5)
logger.info(f'Повторное глушение {acct}...')

View File

@ -113,4 +113,3 @@ def reset_poll():
'''Сброс содержимого предложки-опроса'''
c.execute("DELETE FROM poll")
conn.commit()

View File

@ -1,5 +1,5 @@
from src.fedi_api import get_status, post_status, upload_attachment
from src.fmn_states_db import read_states, write_states
from src.fmn_states_db import states_stor, write_states
from src.fmn_database import get_movies_for_poll, write_votes, read_votes, mark_as_watched_movie, get_already_watched, rewrite_db, reset_poll, get_count_all_watched_movies, force_commit
from collections import Counter
from loguru import logger
@ -16,7 +16,9 @@ def text_create_poll():
return text_poll
@logger.catch
def create_poll_movies(text=text_create_poll(), poll_expires=345600):
logger.debug('Creating poll')
formated_poll_options = []
raw_poll = get_movies_for_poll()
for i in raw_poll:
@ -25,6 +27,7 @@ def create_poll_movies(text=text_create_poll(), poll_expires=345600):
ru_name = i[2]
year = i[3]
poll_option_string = f"{ru_name} / {orig_name}, {year} ({acct})"
logger.debug(f"Adding option in poll: {poll_option_string}")
if ru_name is None:
poll_option_string = f"{orig_name}, {year} ({acct})"
if orig_name is None:
@ -33,19 +36,25 @@ def create_poll_movies(text=text_create_poll(), poll_expires=345600):
poll_option_string = poll_option_string[0:199] # Обрезка на 200 символов.
formated_poll_options.append(poll_option_string)
attaches = []
try:
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=[upload_attachment('src/FMN.png')])
poll_expires=poll_expires, attachments=attaches)
logger.success('Голосовалка создана')
states = read_states()
states['poll_expires_at'] = int(time.time()) + poll_expires
states['poll_status_id'] = poll_status_id['id']
write_states(states)
states_stor.states['poll_expires_at'] = int(time.time()) + poll_expires
states_stor.states['poll_status_id'] = poll_status_id['id']
write_states(states_stor.states)
return poll_status_id
@logger.catch
def get_winner_movie(poll_status_id=str):
'''Отмечаем победивший фильм на голосовании как просмотренный или постим tie breaker'''
states = read_states()
states = states_stor.states
votes_counters = []
status_with_poll = get_status(poll_status_id)
poll = status_with_poll['poll']
@ -83,19 +92,19 @@ def get_winner_movie(poll_status_id=str):
text_winned = f"{expired_poll_count} голосование завершилось! Победил вариант предложенный @{acct_suggested}:\n{win_variant}"
logger.success("Победил " + str(movie))
post_status(text_winned, attachments=[upload_attachment('src/FMN.png')])
post_status(text_winned, attachments=[upload_attachment('src/FMN.webp')])
states_stor.states = {}
write_states()
reset_poll()
@logger.catch
def create_tie_breaker(count_tie=1):
'''Создание tie breaker'''
if count_tie == 1:
states = read_states()
states['tie_breaker'] = 1
write_states(states)
states_stor.states['tie_breaker'] = 1
write_states(states_stor.states)
poll_expires = 8*60*60
else:
poll_expires = 4*60*60
tie_poll = create_poll_movies("TIE BREAKER!!!\n\nВыбираем из победителей!", poll_expires)

View File

@ -3,6 +3,11 @@ from loguru import logger
states_file = 'fmn_states.json'
class states_stor:
states = None
@logger.catch
def read_states():
try:
@ -16,10 +21,13 @@ def read_states():
@logger.catch
def write_states(states={}):
def write_states(new_states={}):
with open(states_file, 'wt') as f:
f.write(json.dumps(states, indent=4))
if states == {}:
f.write(json.dumps(new_states, indent=4))
if new_states == {}:
logger.info('states empty wrote')
return states
return new_states
if not states_stor.states:
states_stor.states = read_states()

View File

@ -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 read_states, 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,7 +20,7 @@ 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 != []:
@ -33,14 +30,14 @@ def parse_links_imdb(text=str):
def scan_context_thread():
fail_limit = Counter()
while True:
states = read_states()
states = states_stor.states
status_id = states.get('last_thread_id')
poll_created = states.get('poll_status_id')
stop_thread_scan = states.get('stop_thread_scan')
time_now = int(time.time())
reserve_time = False
while status_id is None or stop_thread_scan is None:
states = read_states()
states = states_stor.states
fail_limit = Counter()
status_id = states.get('last_thread_id')
stop_thread_scan = states.get('stop_thread_scan')
@ -51,14 +48,14 @@ def scan_context_thread():
logger.debug('Сбор завершён, сканирование треда на опоздавших')
if poll_created is None:
create_poll_movies()
poll_created = states.get('poll_status_id')
poll_created = states_stor.states.get('poll_status_id')
else:
if time_now >= int(states.get('poll_expires_at')):
get_winner_movie(poll_created)
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)
@ -104,18 +105,29 @@ def scan_context_thread():
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":
@ -147,7 +159,7 @@ def scan_context_thread():
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)

View File

@ -1,24 +1,37 @@
from src.fedi_api import get_notifications, mark_as_read_notification, post_status, upload_attachment
from src.fmn_states_db import write_states, read_states
from config import admins_bot, limit_movies_per_user, limit_all_movies_poll, hour_poll_posting, fmn_next_watching_hour
from src.fmn_states_db import write_states, states_stor
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:
states = read_states()
states = states_stor.states
time.sleep(30)
time_now = datetime.now()
now_week = time_now.weekday()
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:
@ -26,39 +39,90 @@ def get_control_mention():
logger.debug('Wait for from admin mention...')
notif = get_notifications()
for i in notif:
if i['type'] != "mention":
if i['type'] not in ("mention"):
continue
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 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: # Фикс стыков двух недель. Если вс, то расчитываем на следующую неделю
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.png')])
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['max_mute_time'] = int(max_mute_time)
states['stop_thread_scan'] = int(stop_thread_scan)
states['last_thread_id'] = st_id
write_states(states)
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
write_states(states_stor.states)
break
time.sleep(30)
@ -69,19 +133,20 @@ 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} фильмов;
- Заявки принимаются до крайнего срока, после чего будет объявлено голосование по собранным вариантам.
Крайний срок подачи заявки - {movies_accept_time}.
Рекомендуем посетить список, чтобы случайно не предложить уже просмотренный фильм: https://pub.phreedom.club/~localhost/fmn_watched.gmi
Желаем удачи.
'''.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
View 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
View 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()