2020-04-04 10:52:30 +00:00
|
|
|
__filename__ = "schedule.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2024-01-21 19:01:20 +00:00
|
|
|
__version__ = "1.5.0"
|
2020-04-04 10:52:30 +00:00
|
|
|
__maintainer__ = "Bob Mottram"
|
2021-09-10 16:14:50 +00:00
|
|
|
__email__ = "bob@libreserver.org"
|
2020-04-04 10:52:30 +00:00
|
|
|
__status__ = "Production"
|
2021-06-15 15:08:12 +00:00
|
|
|
__module_group__ = "Calendar"
|
2020-01-12 20:13:44 +00:00
|
|
|
|
|
|
|
import os
|
2020-01-12 20:16:33 +00:00
|
|
|
import time
|
2024-05-12 12:35:26 +00:00
|
|
|
from utils import data_dir
|
2023-11-20 22:27:58 +00:00
|
|
|
from utils import date_from_string_format
|
|
|
|
from utils import date_epoch
|
2022-12-18 15:29:54 +00:00
|
|
|
from utils import acct_handle_dir
|
2021-12-26 10:57:03 +00:00
|
|
|
from utils import has_object_dict
|
2021-12-27 17:42:35 +00:00
|
|
|
from utils import get_status_number
|
2021-12-26 15:13:34 +00:00
|
|
|
from utils import load_json
|
2021-12-26 18:46:43 +00:00
|
|
|
from utils import is_account_dir
|
2021-12-26 12:02:29 +00:00
|
|
|
from utils import acct_dir
|
2022-06-21 11:58:50 +00:00
|
|
|
from utils import remove_eol
|
2023-11-20 22:27:58 +00:00
|
|
|
from utils import date_utcnow
|
2021-12-29 21:55:09 +00:00
|
|
|
from outbox import post_message_to_outbox
|
2022-03-11 13:27:54 +00:00
|
|
|
from session import create_session
|
2022-07-28 09:59:18 +00:00
|
|
|
from threads import begin_thread
|
2023-09-15 21:04:31 +00:00
|
|
|
from siteactive import save_unavailable_sites
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _update_post_schedule(base_dir: str, handle: str, httpd,
|
2021-12-31 10:29:28 +00:00
|
|
|
max_scheduled_posts: int) -> None:
|
2022-09-21 20:17:21 +00:00
|
|
|
"""Checks if posts are due to be delivered and if so moves them to
|
|
|
|
the outbox
|
2020-01-12 20:13:44 +00:00
|
|
|
"""
|
2021-12-31 10:29:28 +00:00
|
|
|
schedule_index_filename = \
|
2022-12-18 15:29:54 +00:00
|
|
|
acct_handle_dir(base_dir, handle) + '/schedule.index'
|
2021-12-31 10:29:28 +00:00
|
|
|
if not os.path.isfile(schedule_index_filename):
|
2020-01-12 20:13:44 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
# get the current time as an int
|
2023-11-20 22:27:58 +00:00
|
|
|
curr_time = date_utcnow()
|
|
|
|
days_since_epoch = (curr_time - date_epoch()).days
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2022-12-18 15:29:54 +00:00
|
|
|
schedule_dir = acct_handle_dir(base_dir, handle) + '/scheduled/'
|
2021-12-31 10:29:28 +00:00
|
|
|
index_lines = []
|
|
|
|
delete_schedule_post = False
|
2020-04-04 10:52:30 +00:00
|
|
|
nickname = handle.split('@')[0]
|
2024-07-19 10:59:00 +00:00
|
|
|
shared_items_federated_domains = httpd.shared_items_federated_domains
|
|
|
|
shared_item_federation_tokens = httpd.shared_item_federation_tokens
|
|
|
|
try:
|
|
|
|
with open(schedule_index_filename, 'r', encoding='utf-8') as fp_sched:
|
|
|
|
for line in fp_sched:
|
|
|
|
if ' ' not in line:
|
2020-01-13 11:16:35 +00:00
|
|
|
continue
|
2024-07-19 10:59:00 +00:00
|
|
|
date_str = line.split(' ')[0]
|
|
|
|
if 'T' not in date_str:
|
|
|
|
continue
|
|
|
|
post_id1 = line.split(' ', 1)[1]
|
|
|
|
post_id = remove_eol(post_id1)
|
|
|
|
post_filename = schedule_dir + post_id + '.json'
|
|
|
|
if delete_schedule_post:
|
|
|
|
# delete extraneous scheduled posts
|
|
|
|
if os.path.isfile(post_filename):
|
|
|
|
try:
|
|
|
|
os.remove(post_filename)
|
|
|
|
except OSError:
|
|
|
|
print('EX: ' +
|
|
|
|
'_update_post_schedule unable to delete ' +
|
|
|
|
str(post_filename))
|
|
|
|
continue
|
|
|
|
# create the new index file
|
|
|
|
index_lines.append(line)
|
|
|
|
# convert string date to int
|
|
|
|
post_time = \
|
|
|
|
date_from_string_format(date_str, ["%Y-%m-%dT%H:%M:%S%z"])
|
|
|
|
post_time = post_time.replace(tzinfo=None)
|
|
|
|
post_days_since_epoch = \
|
|
|
|
(post_time - date_epoch()).days
|
|
|
|
if days_since_epoch < post_days_since_epoch:
|
|
|
|
continue
|
|
|
|
if days_since_epoch == post_days_since_epoch:
|
|
|
|
if curr_time.time().hour < post_time.time().hour:
|
|
|
|
continue
|
|
|
|
if curr_time.time().minute < post_time.time().minute:
|
|
|
|
continue
|
|
|
|
if not os.path.isfile(post_filename):
|
|
|
|
print('WARN: schedule missing post_filename=' +
|
|
|
|
post_filename)
|
|
|
|
index_lines.remove(line)
|
|
|
|
continue
|
|
|
|
# load post
|
|
|
|
post_json_object = load_json(post_filename)
|
|
|
|
if not post_json_object:
|
|
|
|
print('WARN: schedule json not loaded')
|
|
|
|
index_lines.remove(line)
|
2020-01-13 11:16:35 +00:00
|
|
|
continue
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
# set the published time
|
|
|
|
# If this is not recent then http checks on the receiving side
|
|
|
|
# will reject it
|
|
|
|
_, published = get_status_number()
|
|
|
|
if post_json_object.get('published'):
|
2021-12-25 22:09:19 +00:00
|
|
|
post_json_object['published'] = published
|
2024-07-19 10:59:00 +00:00
|
|
|
if has_object_dict(post_json_object):
|
|
|
|
if post_json_object['object'].get('published'):
|
|
|
|
post_json_object['published'] = published
|
2020-01-13 11:36:42 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
print('Sending scheduled post ' + post_id)
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
if nickname:
|
|
|
|
httpd.post_to_nickname = nickname
|
2022-03-11 13:27:54 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
# create session if needed
|
|
|
|
curr_session = httpd.session
|
|
|
|
curr_proxy_type = httpd.proxy_type
|
|
|
|
if not curr_session:
|
|
|
|
curr_session = create_session(httpd.proxy_type)
|
|
|
|
httpd.session = curr_session
|
|
|
|
if not curr_session:
|
|
|
|
continue
|
2022-03-11 13:27:54 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
if not post_message_to_outbox(curr_session,
|
|
|
|
httpd.translate,
|
|
|
|
post_json_object, nickname,
|
|
|
|
httpd, base_dir,
|
|
|
|
httpd.http_prefix,
|
|
|
|
httpd.domain,
|
|
|
|
httpd.domain_full,
|
|
|
|
httpd.onion_domain,
|
|
|
|
httpd.i2p_domain,
|
|
|
|
httpd.port,
|
|
|
|
httpd.recent_posts_cache,
|
|
|
|
httpd.followers_threads,
|
|
|
|
httpd.federation_list,
|
|
|
|
httpd.send_threads,
|
|
|
|
httpd.postLog,
|
|
|
|
httpd.cached_webfingers,
|
|
|
|
httpd.person_cache,
|
|
|
|
httpd.allow_deletion,
|
|
|
|
curr_proxy_type,
|
|
|
|
httpd.project_version,
|
|
|
|
httpd.debug,
|
|
|
|
httpd.yt_replace_domain,
|
|
|
|
httpd.twitter_replacement_domain,
|
|
|
|
httpd.show_published_date_only,
|
|
|
|
httpd.allow_local_network_access,
|
|
|
|
httpd.city,
|
|
|
|
httpd.system_language,
|
|
|
|
shared_items_federated_domains,
|
|
|
|
shared_item_federation_tokens,
|
|
|
|
httpd.low_bandwidth,
|
|
|
|
httpd.signing_priv_key_pem,
|
|
|
|
httpd.peertube_instances,
|
|
|
|
httpd.theme_name,
|
|
|
|
httpd.max_like_count,
|
|
|
|
httpd.max_recent_posts,
|
|
|
|
httpd.cw_lists,
|
|
|
|
httpd.lists_enabled,
|
|
|
|
httpd.content_license_url,
|
|
|
|
httpd.dogwhistles,
|
|
|
|
httpd.min_images_for_accounts,
|
|
|
|
httpd.buy_sites,
|
|
|
|
httpd.sites_unavailable,
|
|
|
|
httpd.max_recent_books,
|
|
|
|
httpd.books_cache,
|
|
|
|
httpd.max_cached_readers,
|
|
|
|
httpd.auto_cw_cache,
|
|
|
|
httpd.block_federated):
|
|
|
|
index_lines.remove(line)
|
|
|
|
try:
|
|
|
|
os.remove(post_filename)
|
|
|
|
except OSError:
|
|
|
|
print('EX: _update_post_schedule unable to delete ' +
|
|
|
|
str(post_filename))
|
|
|
|
continue
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
# move to the outbox
|
|
|
|
outbox_post_filename = \
|
|
|
|
post_filename.replace('/scheduled/', '/outbox/')
|
|
|
|
os.rename(post_filename, outbox_post_filename)
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
print('Scheduled post sent ' + post_id)
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2024-07-19 10:59:00 +00:00
|
|
|
index_lines.remove(line)
|
|
|
|
if len(index_lines) > max_scheduled_posts:
|
|
|
|
delete_schedule_post = True
|
|
|
|
except OSError as exc:
|
|
|
|
print('EX: _update_post_schedule unable to read ' +
|
|
|
|
schedule_index_filename + ' ' + str(exc))
|
2020-01-12 20:13:44 +00:00
|
|
|
|
|
|
|
# write the new schedule index file
|
2021-12-31 10:29:28 +00:00
|
|
|
schedule_index_file = \
|
2022-12-18 15:29:54 +00:00
|
|
|
acct_handle_dir(base_dir, handle) + '/schedule.index'
|
2024-02-01 13:30:59 +00:00
|
|
|
try:
|
|
|
|
with open(schedule_index_file, 'w+',
|
2024-07-14 13:01:46 +00:00
|
|
|
encoding='utf-8') as fp_schedule:
|
2024-02-01 13:30:59 +00:00
|
|
|
for line in index_lines:
|
2024-07-14 13:01:46 +00:00
|
|
|
fp_schedule.write(line)
|
2024-02-01 13:30:59 +00:00
|
|
|
except OSError:
|
|
|
|
print('EX: _update_post_schedule unable to write ' +
|
|
|
|
schedule_index_file)
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
|
2021-12-31 10:29:28 +00:00
|
|
|
def run_post_schedule(base_dir: str, httpd, max_scheduled_posts: int):
|
2020-01-12 20:13:44 +00:00
|
|
|
"""Dispatches scheduled posts
|
|
|
|
"""
|
|
|
|
while True:
|
|
|
|
time.sleep(60)
|
|
|
|
# for each account
|
2024-05-12 12:35:26 +00:00
|
|
|
dir_str = data_dir(base_dir)
|
|
|
|
for _, dirs, _ in os.walk(dir_str):
|
2020-01-12 20:13:44 +00:00
|
|
|
for account in dirs:
|
|
|
|
if '@' not in account:
|
|
|
|
continue
|
2021-12-26 18:46:43 +00:00
|
|
|
if not is_account_dir(account):
|
2021-01-09 21:47:52 +00:00
|
|
|
continue
|
2020-01-12 20:13:44 +00:00
|
|
|
# scheduled posts index for this account
|
2021-12-31 10:29:28 +00:00
|
|
|
schedule_index_filename = \
|
2024-05-12 12:35:26 +00:00
|
|
|
dir_str + '/' + account + '/schedule.index'
|
2021-12-31 10:29:28 +00:00
|
|
|
if not os.path.isfile(schedule_index_filename):
|
2020-01-12 20:13:44 +00:00
|
|
|
continue
|
2021-12-29 21:55:09 +00:00
|
|
|
_update_post_schedule(base_dir, account,
|
2021-12-31 10:29:28 +00:00
|
|
|
httpd, max_scheduled_posts)
|
2021-06-05 12:57:24 +00:00
|
|
|
break
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def run_post_schedule_watchdog(project_version: str, httpd) -> None:
|
2020-01-12 20:13:44 +00:00
|
|
|
"""This tries to keep the scheduled post thread running even if it dies
|
|
|
|
"""
|
2022-06-14 10:51:40 +00:00
|
|
|
print('THREAD: Starting scheduled post watchdog ' + project_version)
|
2021-12-31 10:29:28 +00:00
|
|
|
post_schedule_original = \
|
2021-12-29 21:55:09 +00:00
|
|
|
httpd.thrPostSchedule.clone(run_post_schedule)
|
2022-07-28 09:59:18 +00:00
|
|
|
begin_thread(httpd.thrPostSchedule, 'run_post_schedule_watchdog')
|
2023-09-15 21:04:31 +00:00
|
|
|
curr_sites_unavailable = httpd.sites_unavailable.copy()
|
2020-01-12 20:13:44 +00:00
|
|
|
while True:
|
2020-03-22 21:16:02 +00:00
|
|
|
time.sleep(20)
|
2023-09-15 21:04:31 +00:00
|
|
|
|
|
|
|
# save the list of unavailable sites
|
2023-09-15 21:10:14 +00:00
|
|
|
if str(curr_sites_unavailable) != str(httpd.sites_unavailable):
|
2023-09-15 21:04:31 +00:00
|
|
|
save_unavailable_sites(httpd.base_dir, httpd.sites_unavailable)
|
|
|
|
curr_sites_unavailable = httpd.sites_unavailable.copy()
|
|
|
|
|
2021-06-05 12:43:57 +00:00
|
|
|
if httpd.thrPostSchedule.is_alive():
|
|
|
|
continue
|
|
|
|
httpd.thrPostSchedule.kill()
|
2022-03-13 11:01:07 +00:00
|
|
|
print('THREAD: restarting scheduled post watchdog')
|
2021-06-05 12:43:57 +00:00
|
|
|
httpd.thrPostSchedule = \
|
2021-12-31 10:29:28 +00:00
|
|
|
post_schedule_original.clone(run_post_schedule)
|
2022-07-28 09:59:18 +00:00
|
|
|
begin_thread(httpd.thrPostSchedule, 'run_post_schedule_watchdog')
|
2021-06-05 12:43:57 +00:00
|
|
|
print('Restarting scheduled posts...')
|
2020-01-14 10:23:17 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def remove_scheduled_posts(base_dir: str, nickname: str, domain: str) -> None:
|
2020-01-14 10:23:17 +00:00
|
|
|
"""Removes any scheduled posts
|
|
|
|
"""
|
|
|
|
# remove the index
|
2021-12-31 10:29:28 +00:00
|
|
|
schedule_index_filename = \
|
2021-12-26 12:02:29 +00:00
|
|
|
acct_dir(base_dir, nickname, domain) + '/schedule.index'
|
2021-12-31 10:29:28 +00:00
|
|
|
if os.path.isfile(schedule_index_filename):
|
2021-09-05 10:17:43 +00:00
|
|
|
try:
|
2021-12-31 10:29:28 +00:00
|
|
|
os.remove(schedule_index_filename)
|
2021-11-25 18:42:38 +00:00
|
|
|
except OSError:
|
2021-12-29 21:55:09 +00:00
|
|
|
print('EX: remove_scheduled_posts unable to delete ' +
|
2021-12-31 10:29:28 +00:00
|
|
|
schedule_index_filename)
|
2020-01-14 10:23:17 +00:00
|
|
|
# remove the scheduled posts
|
2021-12-31 10:29:28 +00:00
|
|
|
scheduled_dir = acct_dir(base_dir, nickname, domain) + '/scheduled'
|
|
|
|
if not os.path.isdir(scheduled_dir):
|
2020-01-14 10:23:17 +00:00
|
|
|
return
|
2021-12-31 10:29:28 +00:00
|
|
|
for scheduled_post_filename in os.listdir(scheduled_dir):
|
|
|
|
file_path = os.path.join(scheduled_dir, scheduled_post_filename)
|
2024-07-19 10:59:57 +00:00
|
|
|
if not os.path.isfile(file_path):
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
os.remove(file_path)
|
|
|
|
except OSError:
|
|
|
|
print('EX: remove_scheduled_posts unable to delete ' +
|
|
|
|
file_path)
|