2020-04-04 10:52:30 +00:00
|
|
|
__filename__ = "schedule.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2021-01-26 10:07:42 +00:00
|
|
|
__version__ = "1.2.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
|
2020-01-12 20:13:44 +00:00
|
|
|
import datetime
|
2021-06-22 15:45:59 +00:00
|
|
|
from utils import hasObjectDict
|
2020-01-13 11:36:42 +00:00
|
|
|
from utils import getStatusNumber
|
2020-01-12 21:47:36 +00:00
|
|
|
from utils import loadJson
|
2021-07-04 17:55:29 +00:00
|
|
|
from utils import isAccountDir
|
2021-07-13 21:59:53 +00:00
|
|
|
from utils import acctDir
|
2020-01-13 10:45:02 +00:00
|
|
|
from outbox import postMessageToOutbox
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
def _updatePostSchedule(base_dir: str, handle: str, httpd,
|
2020-12-22 18:06:23 +00:00
|
|
|
maxScheduledPosts: int) -> None:
|
2020-01-12 20:13:44 +00:00
|
|
|
"""Checks if posts are due to be delivered and if so moves them to the outbox
|
|
|
|
"""
|
2021-12-25 16:17:53 +00:00
|
|
|
scheduleIndexFilename = \
|
|
|
|
base_dir + '/accounts/' + handle + '/schedule.index'
|
2020-01-12 20:13:44 +00:00
|
|
|
if not os.path.isfile(scheduleIndexFilename):
|
|
|
|
return
|
|
|
|
|
|
|
|
# get the current time as an int
|
2020-04-04 10:52:30 +00:00
|
|
|
currTime = datetime.datetime.utcnow()
|
|
|
|
daysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
scheduleDir = base_dir + '/accounts/' + handle + '/scheduled/'
|
2020-04-04 10:52:30 +00:00
|
|
|
indexLines = []
|
|
|
|
deleteSchedulePost = False
|
|
|
|
nickname = handle.split('@')[0]
|
2020-01-12 20:13:44 +00:00
|
|
|
with open(scheduleIndexFilename, 'r') as fp:
|
|
|
|
for line in fp:
|
|
|
|
if ' ' not in line:
|
|
|
|
continue
|
2020-04-04 10:52:30 +00:00
|
|
|
dateStr = line.split(' ')[0]
|
2020-01-12 20:13:44 +00:00
|
|
|
if 'T' not in dateStr:
|
|
|
|
continue
|
2020-05-22 11:32:38 +00:00
|
|
|
postId = line.split(' ', 1)[1].replace('\n', '').replace('\r', '')
|
2020-04-04 10:52:30 +00:00
|
|
|
postFilename = scheduleDir + postId + '.json'
|
2020-01-12 20:13:44 +00:00
|
|
|
if deleteSchedulePost:
|
|
|
|
# delete extraneous scheduled posts
|
|
|
|
if os.path.isfile(postFilename):
|
2021-09-05 10:17:43 +00:00
|
|
|
try:
|
|
|
|
os.remove(postFilename)
|
2021-11-25 18:42:38 +00:00
|
|
|
except OSError:
|
2021-10-29 18:48:15 +00:00
|
|
|
print('EX: _updatePostSchedule unable to delete ' +
|
|
|
|
str(postFilename))
|
2020-01-12 20:13:44 +00:00
|
|
|
continue
|
|
|
|
# create the new index file
|
|
|
|
indexLines.append(line)
|
|
|
|
# convert string date to int
|
2020-04-04 10:52:30 +00:00
|
|
|
postTime = \
|
|
|
|
datetime.datetime.strptime(dateStr, "%Y-%m-%dT%H:%M:%S%z")
|
|
|
|
postTime = postTime.replace(tzinfo=None)
|
|
|
|
postDaysSinceEpoch = \
|
|
|
|
(postTime - datetime.datetime(1970, 1, 1)).days
|
2020-01-12 20:13:44 +00:00
|
|
|
if daysSinceEpoch < postDaysSinceEpoch:
|
|
|
|
continue
|
2020-01-13 11:16:35 +00:00
|
|
|
if daysSinceEpoch == postDaysSinceEpoch:
|
|
|
|
if currTime.time().hour < postTime.time().hour:
|
|
|
|
continue
|
|
|
|
if currTime.time().minute < postTime.time().minute:
|
|
|
|
continue
|
2020-01-12 20:13:44 +00:00
|
|
|
if not os.path.isfile(postFilename):
|
2020-04-04 10:52:30 +00:00
|
|
|
print('WARN: schedule missing postFilename=' + postFilename)
|
2020-01-12 20:13:44 +00:00
|
|
|
indexLines.remove(line)
|
|
|
|
continue
|
|
|
|
# load post
|
2021-12-25 22:09:19 +00:00
|
|
|
post_json_object = loadJson(postFilename)
|
|
|
|
if not post_json_object:
|
2020-01-13 11:13:30 +00:00
|
|
|
print('WARN: schedule json not loaded')
|
2020-01-12 20:13:44 +00:00
|
|
|
indexLines.remove(line)
|
|
|
|
continue
|
|
|
|
|
2020-01-13 11:36:42 +00:00
|
|
|
# set the published time
|
|
|
|
# If this is not recent then http checks on the receiving side
|
|
|
|
# will reject it
|
2020-04-04 10:52:30 +00:00
|
|
|
statusNumber, published = getStatusNumber()
|
2021-12-25 22:09:19 +00:00
|
|
|
if post_json_object.get('published'):
|
|
|
|
post_json_object['published'] = published
|
|
|
|
if hasObjectDict(post_json_object):
|
|
|
|
if post_json_object['object'].get('published'):
|
|
|
|
post_json_object['published'] = published
|
2020-01-13 11:36:42 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
print('Sending scheduled post ' + postId)
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2020-01-13 12:27:27 +00:00
|
|
|
if nickname:
|
2020-04-04 10:52:30 +00:00
|
|
|
httpd.postToNickname = nickname
|
2021-03-06 21:32:27 +00:00
|
|
|
if not postMessageToOutbox(httpd.session,
|
|
|
|
httpd.translate,
|
2021-12-25 22:09:19 +00:00
|
|
|
post_json_object, nickname,
|
2021-12-25 16:17:53 +00:00
|
|
|
httpd, base_dir,
|
2021-12-25 17:09:22 +00:00
|
|
|
httpd.http_prefix,
|
2020-04-04 10:52:30 +00:00
|
|
|
httpd.domain,
|
|
|
|
httpd.domainFull,
|
2021-12-25 20:43:43 +00:00
|
|
|
httpd.onion_domain,
|
2021-12-25 20:50:24 +00:00
|
|
|
httpd.i2p_domain,
|
2020-04-04 10:52:30 +00:00
|
|
|
httpd.port,
|
|
|
|
httpd.recentPostsCache,
|
2021-12-25 22:48:08 +00:00
|
|
|
httpd.followers_threads,
|
2020-04-04 10:52:30 +00:00
|
|
|
httpd.federationList,
|
2021-12-25 21:37:41 +00:00
|
|
|
httpd.send_threads,
|
2020-04-04 10:52:30 +00:00
|
|
|
httpd.postLog,
|
2021-12-25 22:28:18 +00:00
|
|
|
httpd.cached_webfingers,
|
2021-12-25 22:17:49 +00:00
|
|
|
httpd.person_cache,
|
2021-12-25 21:29:53 +00:00
|
|
|
httpd.allow_deletion,
|
2021-12-25 21:09:22 +00:00
|
|
|
httpd.proxy_type,
|
2021-12-25 20:34:38 +00:00
|
|
|
httpd.project_version,
|
2020-08-02 09:51:20 +00:00
|
|
|
httpd.debug,
|
2021-12-25 17:15:52 +00:00
|
|
|
httpd.yt_replace_domain,
|
2021-12-25 20:55:47 +00:00
|
|
|
httpd.twitter_replacement_domain,
|
2021-12-25 20:06:27 +00:00
|
|
|
httpd.show_published_date_only,
|
2021-12-25 18:54:50 +00:00
|
|
|
httpd.allow_local_network_access,
|
2021-12-25 23:03:28 +00:00
|
|
|
httpd.city, httpd.system_language,
|
2021-12-25 18:05:01 +00:00
|
|
|
httpd.shared_items_federated_domains,
|
2021-08-13 20:18:36 +00:00
|
|
|
httpd.sharedItemFederationTokens,
|
2021-12-25 18:20:56 +00:00
|
|
|
httpd.low_bandwidth,
|
2021-12-25 23:03:28 +00:00
|
|
|
httpd.signing_priv_key_pem,
|
2021-09-03 11:30:23 +00:00
|
|
|
httpd.peertubeInstances,
|
|
|
|
httpd.themeName,
|
2021-12-25 18:23:12 +00:00
|
|
|
httpd.max_like_count,
|
2021-12-25 20:28:06 +00:00
|
|
|
httpd.max_recent_posts,
|
2021-10-21 19:00:25 +00:00
|
|
|
httpd.CWlists,
|
2021-12-25 18:12:13 +00:00
|
|
|
httpd.lists_enabled,
|
2021-12-25 17:13:38 +00:00
|
|
|
httpd.content_license_url):
|
2020-01-12 20:13:44 +00:00
|
|
|
indexLines.remove(line)
|
2021-09-05 10:17:43 +00:00
|
|
|
try:
|
|
|
|
os.remove(postFilename)
|
2021-11-25 18:42:38 +00:00
|
|
|
except OSError:
|
2021-10-29 18:48:15 +00:00
|
|
|
print('EX: _updatePostSchedule unable to delete ' +
|
|
|
|
str(postFilename))
|
2020-01-12 20:13:44 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
# move to the outbox
|
2021-07-04 17:55:29 +00:00
|
|
|
outboxPostFilename = postFilename.replace('/scheduled/',
|
|
|
|
'/outbox/')
|
2020-04-04 10:52:30 +00:00
|
|
|
os.rename(postFilename, outboxPostFilename)
|
2020-01-12 20:13:44 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
print('Scheduled post sent ' + postId)
|
2020-01-12 20:13:44 +00:00
|
|
|
|
|
|
|
indexLines.remove(line)
|
2020-04-04 10:52:30 +00:00
|
|
|
if len(indexLines) > maxScheduledPosts:
|
|
|
|
deleteSchedulePost = True
|
2020-01-12 20:13:44 +00:00
|
|
|
|
|
|
|
# write the new schedule index file
|
2020-04-04 10:52:30 +00:00
|
|
|
scheduleIndexFile = \
|
2021-12-25 16:17:53 +00:00
|
|
|
base_dir + '/accounts/' + handle + '/schedule.index'
|
2021-06-22 12:27:10 +00:00
|
|
|
with open(scheduleIndexFile, 'w+') as scheduleFile:
|
2020-01-12 20:13:44 +00:00
|
|
|
for line in indexLines:
|
|
|
|
scheduleFile.write(line)
|
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
def runPostSchedule(base_dir: str, httpd, maxScheduledPosts: int):
|
2020-01-12 20:13:44 +00:00
|
|
|
"""Dispatches scheduled posts
|
|
|
|
"""
|
|
|
|
while True:
|
|
|
|
time.sleep(60)
|
|
|
|
# for each account
|
2021-12-25 16:17:53 +00:00
|
|
|
for subdir, dirs, files in os.walk(base_dir + '/accounts'):
|
2020-01-12 20:13:44 +00:00
|
|
|
for account in dirs:
|
|
|
|
if '@' not in account:
|
|
|
|
continue
|
2021-07-04 17:55:29 +00:00
|
|
|
if not isAccountDir(account):
|
2021-01-09 21:47:52 +00:00
|
|
|
continue
|
2020-01-12 20:13:44 +00:00
|
|
|
# scheduled posts index for this account
|
2020-04-04 10:52:30 +00:00
|
|
|
scheduleIndexFilename = \
|
2021-12-25 16:17:53 +00:00
|
|
|
base_dir + '/accounts/' + account + '/schedule.index'
|
2020-01-12 21:15:42 +00:00
|
|
|
if not os.path.isfile(scheduleIndexFilename):
|
2020-01-12 20:13:44 +00:00
|
|
|
continue
|
2021-12-25 16:17:53 +00:00
|
|
|
_updatePostSchedule(base_dir, account,
|
|
|
|
httpd, maxScheduledPosts)
|
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-25 20:34:38 +00:00
|
|
|
def runPostScheduleWatchdog(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
|
|
|
|
"""
|
|
|
|
print('Starting scheduled post watchdog')
|
2020-04-04 10:52:30 +00:00
|
|
|
postScheduleOriginal = \
|
2020-01-12 20:13:44 +00:00
|
|
|
httpd.thrPostSchedule.clone(runPostSchedule)
|
|
|
|
httpd.thrPostSchedule.start()
|
|
|
|
while True:
|
2020-03-22 21:16:02 +00:00
|
|
|
time.sleep(20)
|
2021-06-05 12:43:57 +00:00
|
|
|
if httpd.thrPostSchedule.is_alive():
|
|
|
|
continue
|
|
|
|
httpd.thrPostSchedule.kill()
|
|
|
|
httpd.thrPostSchedule = \
|
|
|
|
postScheduleOriginal.clone(runPostSchedule)
|
|
|
|
httpd.thrPostSchedule.start()
|
|
|
|
print('Restarting scheduled posts...')
|
2020-01-14 10:23:17 +00:00
|
|
|
|
2020-04-04 10:52:30 +00:00
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
def removeScheduledPosts(base_dir: str, nickname: str, domain: str) -> None:
|
2020-01-14 10:23:17 +00:00
|
|
|
"""Removes any scheduled posts
|
|
|
|
"""
|
|
|
|
# remove the index
|
2020-04-04 10:52:30 +00:00
|
|
|
scheduleIndexFilename = \
|
2021-12-25 16:17:53 +00:00
|
|
|
acctDir(base_dir, nickname, domain) + '/schedule.index'
|
2020-01-14 10:23:17 +00:00
|
|
|
if os.path.isfile(scheduleIndexFilename):
|
2021-09-05 10:17:43 +00:00
|
|
|
try:
|
|
|
|
os.remove(scheduleIndexFilename)
|
2021-11-25 18:42:38 +00:00
|
|
|
except OSError:
|
2021-10-29 18:48:15 +00:00
|
|
|
print('EX: removeScheduledPosts unable to delete ' +
|
|
|
|
scheduleIndexFilename)
|
2020-01-14 10:23:17 +00:00
|
|
|
# remove the scheduled posts
|
2021-12-25 16:17:53 +00:00
|
|
|
scheduledDir = acctDir(base_dir, nickname, domain) + '/scheduled'
|
2020-01-14 10:23:17 +00:00
|
|
|
if not os.path.isdir(scheduledDir):
|
|
|
|
return
|
|
|
|
for scheduledPostFilename in os.listdir(scheduledDir):
|
2020-04-04 10:52:30 +00:00
|
|
|
filePath = os.path.join(scheduledDir, scheduledPostFilename)
|
2021-10-29 18:48:15 +00:00
|
|
|
if os.path.isfile(filePath):
|
|
|
|
try:
|
|
|
|
os.remove(filePath)
|
2021-11-25 18:42:38 +00:00
|
|
|
except OSError:
|
2021-10-29 18:48:15 +00:00
|
|
|
print('EX: removeScheduledPosts unable to delete ' +
|
|
|
|
filePath)
|