Move time functions to a separate module

main
Bob Mottram 2025-05-28 12:38:45 +01:00
parent 6e324b2cc7
commit 5871155fe3
51 changed files with 444 additions and 428 deletions

View File

@ -14,7 +14,7 @@ import time
from posts import send_signed_json
from flags import has_group_type
from flags import url_permitted
from utils import get_status_number
from status import get_status_number
from utils import get_attributed_to
from utils import get_user_paths
from utils import text_in_file

View File

@ -12,6 +12,7 @@ __module_group__ = "ActivityPub"
import os
from flags import has_group_type
from flags import url_permitted
from status import get_status_number
from utils import text_in_file
from utils import get_user_paths
from utils import has_object_string_object
@ -20,7 +21,6 @@ from utils import remove_domain_port
from utils import remove_id_ending
from utils import has_users_path
from utils import get_full_domain
from utils import get_status_number
from utils import create_outbox_dir
from utils import get_nickname_from_actor
from utils import get_domain_from_actor

View File

@ -18,7 +18,7 @@ from utils import data_dir
from utils import has_users_path
from utils import text_in_file
from utils import remove_eol
from utils import date_utcnow
from timeFunctions import date_utcnow
def _hash_password(password: str) -> str:

View File

@ -16,12 +16,12 @@ from session import create_session
from flags import is_evil
from flags import is_quote_toot
from quote import get_quote_toot_url
from timeFunctions import date_utcnow
from timeFunctions import date_from_string_format
from utils import get_user_paths
from utils import contains_statuses
from utils import data_dir
from utils import string_contains
from utils import date_from_string_format
from utils import date_utcnow
from utils import remove_eol
from utils import has_object_string
from utils import has_object_string_object

View File

@ -16,11 +16,11 @@ from webapp_utils import html_footer
from webapp_utils import get_post_attachments_as_html
from webapp_utils import edit_text_area
from webapp_media import add_embedded_elements
from timeFunctions import date_from_string_format
from utils import replace_strings
from utils import data_dir
from utils import remove_link_tracking
from utils import get_url_from_post
from utils import date_from_string_format
from utils import get_attributed_to
from utils import remove_eol
from utils import text_in_file

View File

@ -26,9 +26,9 @@ from utils import load_json
from utils import save_json
from utils import get_file_case_insensitive
from utils import get_user_paths
from utils import date_utcnow
from utils import date_from_string_format
from utils import get_image_extensions
from timeFunctions import date_from_string_format
from timeFunctions import date_utcnow
from content import remove_script

View File

@ -9,9 +9,9 @@ __module_group__ = "RSS Feeds"
import os
import datetime
from timeFunctions import date_utcnow
from timeFunctions import date_epoch
from utils import data_dir
from utils import date_utcnow
from utils import date_epoch
from utils import replace_strings
MAX_TAG_LENGTH = 42

View File

@ -15,6 +15,7 @@ import email.parser
import urllib.parse
from shutil import copyfile
from dateutil.parser import parse
from timeFunctions import convert_published_to_local_timezone
from flags import is_pgp_encrypted
from flags import contains_pgp_public_key
from flags import is_float
@ -31,7 +32,6 @@ from utils import binary_is_image
from utils import get_content_from_post
from utils import get_full_domain
from utils import get_user_paths
from utils import convert_published_to_local_timezone
from utils import has_object_dict
from utils import valid_hash_tag
from utils import dangerous_svg

View File

@ -50,13 +50,13 @@ from categories import load_city_hashtags
from categories import update_hashtag_categories
from languages import load_default_post_languages
from searchable import load_searchable_by_default
from timeFunctions import load_account_timezones
from utils import set_accounts_data_dir
from utils import data_dir
from utils import check_bad_path
from utils import acct_handle_dir
from utils import load_reverse_timeline
from utils import load_min_images_for_accounts
from utils import load_account_timezones
from utils import load_translations_from_file
from utils import load_bold_reading
from utils import load_hide_follows

View File

@ -93,7 +93,7 @@ from flags import is_corporate
from flags import is_image_file
from flags import is_artist
from flags import is_blog_post
from utils import date_utcnow
from timeFunctions import date_utcnow
from utils import replace_strings
from utils import contains_invalid_chars
from utils import save_json

View File

@ -13,11 +13,11 @@ from webapp_conversation import html_conversation_view
from flags import is_public_post_from_url
from flags import is_public_post
from flags import is_premium_account
from flags import can_reply_to
from utils import get_instance_url
from utils import local_actor_url
from utils import locate_post
from utils import get_config_param
from utils import can_reply_to
from utils import get_nickname_from_actor
from utils import get_new_post_endpoints
from utils import acct_dir

View File

@ -15,7 +15,7 @@ from utils import get_nickname_from_actor
from utils import get_domain_from_actor
from utils import get_full_domain
from utils import local_actor_url
from utils import get_status_number
from status import get_status_number
from follow import unfollow_account
from follow import send_follow_request
from follow import remove_follower

View File

@ -16,12 +16,12 @@ from utils import data_dir
from utils import remove_id_ending
from utils import save_json
from utils import first_paragraph_from_string
from utils import date_from_string_format
from utils import load_json
from utils import locate_post
from utils import acct_dir
from utils import get_instance_url
from utils import get_nickname_from_actor
from timeFunctions import date_from_string_format
from httpheaders import redirect_headers
from posts import is_moderator
from content import extract_text_fields_in_post

View File

@ -20,13 +20,13 @@ from httpheaders import clear_login_details
from flags import is_artist
from flags import is_memorial_account
from flags import is_premium_account
from timeFunctions import get_account_timezone
from timeFunctions import set_account_timezone
from utils import data_dir
from utils import set_premium_account
from utils import save_json
from utils import save_reverse_timeline
from utils import set_minimize_all_images
from utils import set_account_timezone
from utils import get_account_timezone
from utils import set_memorials
from utils import get_memorials
from utils import license_link_from_name

View File

@ -31,8 +31,8 @@ from city import get_spoofed_city
from flags import is_image_file
from flags import is_float
from searchable import set_searchable_by
from utils import date_utcnow
from utils import date_from_string_format
from timeFunctions import date_utcnow
from timeFunctions import date_from_string_format
from utils import get_instance_url
from utils import save_json
from utils import remove_post_from_cache

View File

@ -9,7 +9,6 @@ __module_group__ = "ActivityPub"
import os
from datetime import datetime, timezone
from utils import date_from_numbers
from utils import has_object_string
from utils import remove_domain_port
from utils import has_users_path
@ -21,9 +20,10 @@ from utils import locate_post
from utils import delete_post
from utils import remove_moderation_post_from_index
from utils import local_actor_url
from utils import date_utcnow
from utils import date_epoch
from utils import get_actor_from_post
from timeFunctions import date_epoch
from timeFunctions import date_from_numbers
from timeFunctions import date_utcnow
from session import post_json
from webfinger import webfinger_handle
from auth import create_basic_auth_header

View File

@ -9,9 +9,11 @@ __module_group__ = "Core"
import os
import re
from timeFunctions import date_utcnow
from timeFunctions import get_published_date
from timeFunctions import date_from_string_format
from timeFunctions import date_epoch
from utils import acct_dir
from utils import date_utcnow
from utils import date_epoch
from utils import data_dir
from utils import get_config_param
from utils import get_image_extensions
@ -23,7 +25,6 @@ from utils import has_object_dict
from utils import locate_post
from utils import load_json
from utils import has_object_string_type
from utils import date_from_string_format
from utils import get_reply_to
from utils import text_in_file
from utils import get_group_paths
@ -636,3 +637,46 @@ def is_corporate(server_name: str) -> bool:
'github' in server_lower:
return True
return False
def can_reply_to(base_dir: str, nickname: str, domain: str,
post_url: str, reply_interval_hours: int,
curr_date_str: str = None,
post_json_object: {} = None) -> bool:
"""Is replying to the given local post permitted?
This is a spam mitigation feature, so that spammers can't
add a lot of replies to old post which you don't notice.
"""
if '/statuses/' not in post_url:
return True
if not post_json_object:
post_filename = locate_post(base_dir, nickname, domain, post_url)
if not post_filename:
# the post is not stored locally
return True
post_json_object = load_json(post_filename)
if not post_json_object:
return False
published = get_published_date(post_json_object)
if not published:
return False
pub_date = date_from_string_format(published, ['%Y-%m-%dT%H:%M:%S%z'])
if not pub_date:
print('EX: can_reply_to unrecognized published date ' + str(published))
return False
if not curr_date_str:
curr_date = date_utcnow()
else:
curr_date = \
date_from_string_format(curr_date_str, ['%Y-%m-%dT%H:%M:%S%z'])
if not curr_date:
print('EX: can_reply_to unrecognized current date ' +
str(curr_date_str))
return False
hours_since_publication = \
int((curr_date - pub_date).total_seconds() / 3600)
if hours_since_publication < 0 or \
hours_since_publication >= reply_interval_hours:
return False
return True

View File

@ -21,10 +21,7 @@ from utils import valid_nickname
from utils import domain_permitted
from utils import get_domain_from_actor
from utils import get_nickname_from_actor
from utils import get_status_number
from utils import follow_person
from posts import send_signed_json
from posts import get_person_box
from utils import load_json
from utils import save_json
from utils import is_account_dir
@ -34,6 +31,9 @@ from utils import text_in_file
from utils import remove_eol
from utils import get_actor_from_post
from utils import data_dir
from status import get_status_number
from posts import send_signed_json
from posts import get_person_box
from acceptreject import create_accept
from acceptreject import create_reject
from webfinger import webfinger_handle

View File

@ -16,8 +16,6 @@ from flags import is_reminder
from flags import is_public_post
from utils import resembles_url
from utils import replace_strings
from utils import date_from_numbers
from utils import date_from_string_format
from utils import acct_handle_dir
from utils import load_json
from utils import save_json
@ -27,10 +25,12 @@ from utils import acct_dir
from utils import remove_html
from utils import get_display_name
from utils import delete_post
from utils import get_status_number
from utils import get_full_domain
from utils import text_in_file
from utils import remove_eol
from status import get_status_number
from timeFunctions import date_from_string_format
from timeFunctions import date_from_numbers
from filters import is_filtered
from context import get_individual_post_context
from session import get_method

View File

@ -25,9 +25,9 @@ from utils import get_full_domain
from utils import get_sha_256
from utils import get_sha_512
from utils import local_actor_url
from utils import date_utcnow
from utils import date_epoch
from utils import date_from_string_format
from timeFunctions import date_epoch
from timeFunctions import date_from_string_format
from timeFunctions import date_utcnow
def message_content_digest(message_body_json_str: str,

View File

@ -14,6 +14,7 @@ import time
import random
from shutil import copyfile
from linked_data_sig import verify_json_signature
from flags import can_reply_to
from flags import is_system_account
from flags import is_blog_post
from flags import is_recent_post
@ -24,20 +25,19 @@ from flags import is_quote_toot
from flags import url_permitted
from quote import quote_toots_allowed
from mitm import save_mitm_servers
from timeFunctions import date_utcnow
from timeFunctions import date_epoch
from timeFunctions import get_account_timezone
from utils import harmless_markup
from utils import lines_in_file
from utils import date_epoch
from utils import date_utcnow
from utils import contains_statuses
from utils import get_actor_from_post_id
from utils import acct_handle_dir
from utils import text_in_file
from utils import get_media_descriptions_from_post
from utils import get_summary_from_post
from utils import get_account_timezone
from utils import domain_permitted
from utils import get_reply_interval_hours
from utils import can_reply_to
from utils import get_base_content_from_post
from utils import acct_dir
from utils import remove_domain_port
@ -49,7 +49,6 @@ from utils import has_users_path
from utils import get_full_domain
from utils import remove_id_ending
from utils import create_inbox_queue_dir
from utils import get_status_number
from utils import get_domain_from_actor
from utils import get_nickname_from_actor
from utils import locate_post
@ -63,6 +62,7 @@ from utils import get_actor_from_post
from utils import data_dir
from utils import is_dm
from utils import has_actor
from status import get_status_number
from httpsig import get_digest_algorithm_from_headers
from httpsig import verify_post_headers
from session import create_session

View File

@ -13,6 +13,7 @@ from flags import is_recent_post
from flags import is_quote_toot
from status import actor_status_expired
from quote import get_quote_toot_url
from timeFunctions import get_account_timezone
from utils import get_actor_from_post_id
from utils import contains_invalid_actor_url_chars
from utils import get_attributed_to
@ -30,7 +31,6 @@ from utils import has_users_path
from utils import has_object_string_type
from utils import get_config_param
from utils import acct_dir
from utils import get_account_timezone
from utils import is_dm
from utils import delete_cached_html
from utils import harmless_markup

View File

@ -9,6 +9,7 @@ __module_group__ = "Timeline"
import os
from flags import has_group_type
from timeFunctions import get_account_timezone
from utils import undo_announce_collection_entry
from utils import has_object_dict
from utils import remove_domain_port
@ -16,7 +17,6 @@ from utils import remove_id_ending
from utils import get_url_from_post
from utils import undo_reaction_collection_entry
from utils import remove_html
from utils import get_account_timezone
from utils import is_dm
from utils import get_cached_post_filename
from utils import load_json

View File

@ -21,7 +21,7 @@ from cryptography.hazmat.primitives.asymmetric import utils as hazutils
from pyjsonld import normalize
from context import has_valid_context
from utils import get_sha_256
from utils import date_utcnow
from timeFunctions import date_utcnow
def _options_hash(doc: {}) -> str:

View File

@ -19,9 +19,9 @@ from utils import save_json
from utils import locate_post
from utils import remove_html
from utils import has_object_dict
from utils import date_utcnow
from utils import date_epoch
from utils import date_from_string_format
from timeFunctions import date_epoch
from timeFunctions import date_from_string_format
from timeFunctions import date_utcnow
from session import get_resolved_url

View File

@ -15,8 +15,8 @@ import random
from random import randint
from hashlib import sha1
from auth import create_password
from utils import date_epoch
from utils import date_utcnow
from timeFunctions import date_utcnow
from timeFunctions import date_epoch
from utils import safe_system_string
from utils import get_base_content_from_post
from utils import get_full_domain

View File

@ -23,19 +23,19 @@ from newswire import get_dict_from_newswire
# from posts import send_signed_json
from posts import create_news_post
from posts import archive_posts_for_person
from utils import date_from_string_format
from utils import date_utcnow
from timeFunctions import date_from_string_format
from timeFunctions import date_utcnow
from utils import valid_hash_tag
from utils import get_base_content_from_post
from utils import remove_html
from utils import get_full_domain
from utils import load_json
from utils import save_json
from utils import get_status_number
from utils import dangerous_markup
from utils import local_actor_url
from utils import text_in_file
from utils import data_dir
from status import get_status_number
from session import create_session
from threads import begin_thread
from webapp_hashtagswarm import store_hash_tags

View File

@ -17,7 +17,7 @@ import errno
from datetime import timedelta
from datetime import timezone
from collections import OrderedDict
from utils import valid_post_date
from timeFunctions import valid_post_date
from categories import set_hashtag_category
from flags import is_suspended
from flags import is_local_network_address
@ -28,7 +28,6 @@ from utils import image_mime_types_dict
from utils import resembles_url
from utils import get_url_from_post
from utils import remove_zero_length_strings
from utils import date_from_string_format
from utils import acct_handle_dir
from utils import remove_eol
from utils import get_domain_from_actor
@ -48,6 +47,7 @@ from utils import acct_dir
from utils import local_actor_url
from utils import escape_text
from utils import unescaped_text
from timeFunctions import date_from_string_format
from blocking import is_blocked_domain
from blocking import is_blocked_hashtag
from filters import is_filtered

View File

@ -18,12 +18,12 @@ from posts import send_to_named_addresses_thread
from flags import is_featured_writer
from flags import is_quote_toot
from quote import quote_toots_allowed
from timeFunctions import get_account_timezone
from utils import data_dir
from utils import get_post_attachments
from utils import get_attributed_to
from utils import contains_invalid_actor_url_chars
from utils import get_attachment_property_value
from utils import get_account_timezone
from utils import has_object_string_type
from utils import get_base_content_from_post
from utils import has_object_dict

View File

@ -38,12 +38,12 @@ from roles import actor_roles_from_list
from roles import get_actor_roles_list
from media import process_meta_data
from flags import is_image_file
from timeFunctions import date_utcnow
from utils import get_person_icon
from utils import account_is_indexable
from utils import get_image_mime_type
from utils import get_instance_url
from utils import get_url_from_post
from utils import date_utcnow
from utils import get_memorials
from utils import is_account_dir
from utils import valid_hash_tag
@ -57,7 +57,6 @@ from utils import contains_invalid_actor_url_chars
from utils import replace_users_with_at
from utils import remove_eol
from utils import remove_domain_port
from utils import get_status_number
from utils import get_full_domain
from utils import valid_nickname
from utils import load_json
@ -77,6 +76,7 @@ from utils import text_in_file
from utils import contains_statuses
from utils import get_actor_from_post
from utils import data_dir
from status import get_status_number
from session import get_json_valid
from session import create_session
from session import get_json

2
pgp.py
View File

@ -18,10 +18,10 @@ from utils import get_occupation_skills
from utils import get_url_from_post
from utils import safe_system_string
from utils import get_full_domain
from utils import get_status_number
from utils import local_actor_url
from utils import replace_users_with_at
from utils import remove_html
from status import get_status_number
from webfinger import webfinger_handle
from posts import get_person_box
from auth import create_basic_auth_header

View File

@ -41,6 +41,10 @@ from flags import contains_private_key
from flags import has_group_type
from flags import is_premium_account
from flags import url_permitted
from timeFunctions import date_utcnow
from timeFunctions import date_from_string_format
from timeFunctions import date_epoch
from timeFunctions import valid_post_date
from utils import resembles_url
from utils import get_person_icon
from utils import remove_post_from_index
@ -50,9 +54,6 @@ from utils import get_actor_from_post_id
from utils import string_contains
from utils import get_post_attachments
from utils import get_url_from_post
from utils import date_from_string_format
from utils import date_epoch
from utils import date_utcnow
from utils import get_attributed_to
from utils import contains_statuses
from utils import contains_invalid_actor_url_chars
@ -76,10 +77,8 @@ from utils import reject_post_id
from utils import remove_invalid_chars
from utils import file_last_modified
from utils import has_users_path
from utils import valid_post_date
from utils import get_full_domain
from utils import get_followers_list
from utils import get_status_number
from utils import create_person_dir
from utils import get_nickname_from_actor
from utils import get_domain_from_actor
@ -90,7 +89,6 @@ from utils import load_json
from utils import save_json
from utils import get_config_param
from utils import locate_news_votes
from utils import locate_news_arrival
from utils import votes_on_newswire_item
from utils import remove_html
from utils import dangerous_markup
@ -99,6 +97,7 @@ from utils import local_actor_url
from utils import get_reply_to
from utils import get_actor_from_post
from utils import data_dir
from status import get_status_number
from media import get_music_metadata
from media import attach_media
from media import replace_you_tube
@ -4840,6 +4839,39 @@ def remove_post_interactions(post_json_object: {}, force: bool) -> bool:
return True
def _locate_news_arrival(base_dir: str, domain: str,
post_url: str) -> str:
"""Returns the arrival time for a news post
within the news user account
"""
post_url1 = post_url.strip()
post_url = remove_eol(post_url1)
# if this post in the shared inbox?
post_url = remove_id_ending(post_url.strip()).replace('/', '#')
if post_url.endswith('.json'):
post_url = post_url + '.arrived'
else:
post_url = post_url + '.json.arrived'
account_dir = data_dir(base_dir) + '/news@' + domain + '/'
post_filename = account_dir + 'outbox/' + post_url
if os.path.isfile(post_filename):
try:
with open(post_filename, 'r', encoding='utf-8') as fp_arrival:
arrival = fp_arrival.read()
if arrival:
arrival_date = \
date_from_string_format(arrival,
["%Y-%m-%dT%H:%M:%S%z"])
return arrival_date
except OSError:
print('EX: _locate_news_arrival unable to read ' + post_filename)
return None
def _passed_newswire_voting(newswire_votes_threshold: int,
base_dir: str, domain: str,
post_filename: str,
@ -4853,7 +4885,7 @@ def _passed_newswire_voting(newswire_votes_threshold: int,
# note that the presence of an arrival file also indicates
# that this post is moderated
arrival_date = \
locate_news_arrival(base_dir, domain, post_filename)
_locate_news_arrival(base_dir, domain, post_filename)
if not arrival_date:
return True
# how long has elapsed since this post arrived?

View File

@ -20,8 +20,8 @@ from utils import load_json
from utils import save_json
from utils import remove_html
from utils import get_image_extensions
from utils import date_epoch
from utils import date_from_string_format
from timeFunctions import date_epoch
from timeFunctions import date_from_string_format
def get_book_link_from_content(content: str) -> str:

View File

@ -11,11 +11,11 @@ import os
from utils import data_dir
from utils import load_json
from utils import save_json
from utils import get_status_number
from utils import remove_domain_port
from utils import acct_dir
from utils import text_in_file
from utils import get_config_param
from status import get_status_number
def _clear_role_status(base_dir: str, role: str) -> None:

View File

@ -10,16 +10,16 @@ __module_group__ = "Calendar"
import os
import time
from utils import data_dir
from utils import date_from_string_format
from utils import date_epoch
from timeFunctions import date_from_string_format
from timeFunctions import date_epoch
from utils import acct_handle_dir
from utils import has_object_dict
from utils import get_status_number
from utils import load_json
from utils import is_account_dir
from utils import acct_dir
from utils import remove_eol
from utils import date_utcnow
from status import get_status_number
from timeFunctions import date_utcnow
from outbox import post_message_to_outbox
from session import create_session
from threads import begin_thread

View File

@ -24,18 +24,18 @@ from session import post_image
from session import create_session
from session import get_json_valid
from flags import is_float
from timeFunctions import date_utcnow
from timeFunctions import date_string_to_seconds
from timeFunctions import date_seconds_to_string
from utils import replace_strings
from utils import data_dir
from utils import resembles_url
from utils import date_utcnow
from utils import dangerous_markup
from utils import remove_html
from utils import get_media_extensions
from utils import acct_handle_dir
from utils import remove_eol
from utils import has_object_string_type
from utils import date_string_to_seconds
from utils import date_seconds_to_string
from utils import get_config_param
from utils import get_full_domain
from utils import valid_nickname

View File

@ -9,8 +9,9 @@ __module_group__ = "Core"
__accounts_data_path__ = None
__accounts_data_path_tests__ = False
from utils import date_utcnow
from utils import date_from_string_format
from timeFunctions import date_utcnow
from timeFunctions import date_from_string_format
from timeFunctions import date_epoch
from utils import remove_html
from unicodetext import standardize_text
@ -66,3 +67,29 @@ def actor_status_expired(actor_status_json: {}) -> bool:
if status_end_time < curr_time:
return True
return False
def get_status_number(published_str: str = None) -> (str, str):
"""Returns the status number and published date
"""
if not published_str:
curr_time = date_utcnow()
else:
curr_time = \
date_from_string_format(published_str, ['%Y-%m-%dT%H:%M:%S%z'])
days_since_epoch = (curr_time - date_epoch()).days
# status is the number of seconds since epoch
status_number = \
str(((days_since_epoch * 24 * 60 * 60) +
(curr_time.hour * 60 * 60) +
(curr_time.minute * 60) +
curr_time.second) * 1000 +
int(curr_time.microsecond / 1000))
# See https://github.com/tootsuite/mastodon/blob/
# 995f8b389a66ab76ec92d9a240de376f1fc13a38/lib/mastodon/snowflake.rb
# use the leftover microseconds as the sequence number
sequence_id = curr_time.microsecond % 1000
# shift by 16bits "sequence data"
status_number = str((int(status_number) << 16) + sequence_id)
published = curr_time.strftime("%Y-%m-%dT%H:%M:%SZ")
return status_number, published

View File

@ -59,6 +59,7 @@ from follow import send_follow_request_via_server
from follow import send_unfollow_request_via_server
from siteactive import site_is_active
from siteactive import is_online
from flags import can_reply_to
from flags import contains_pgp_public_key
from flags import is_group_actor
from flags import is_group_account
@ -72,27 +73,26 @@ from utils import data_dir
from utils import data_dir_testing
from utils import remove_link_tracking
from utils import get_url_from_post
from utils import date_from_string_format
from utils import date_utcnow
from utils import remove_markup_tag
from utils import remove_style_within_html
from utils import html_tag_has_closing
from unicodetext import remove_inverted_text
from unicodetext import remove_square_capitals
from unicodetext import standardize_text
from timeFunctions import date_from_string_format
from timeFunctions import date_utcnow
from timeFunctions import convert_published_to_local_timezone
from timeFunctions import date_string_to_seconds
from timeFunctions import date_seconds_to_string
from utils import remove_eol
from utils import text_in_file
from utils import convert_published_to_local_timezone
from utils import convert_to_snake_case
from utils import get_sha_256
from utils import dangerous_svg
from utils import can_reply_to
from utils import get_actor_languages_list
from utils import get_category_types
from utils import get_supported_languages
from utils import set_config_param
from utils import date_string_to_seconds
from utils import date_seconds_to_string
from utils import valid_password
from utils import user_agent_domain
from utils import camel_case_split
@ -107,12 +107,12 @@ from utils import get_domain_from_actor
from utils import copytree
from utils import load_json
from utils import save_json
from utils import get_status_number
from utils import valid_hash_tag
from utils import get_followers_of_person
from utils import remove_html
from utils import dangerous_markup
from utils import acct_dir
from status import get_status_number
from pgp import extract_pgp_public_key
from pgp import pgp_public_key_upload
from follow import add_follower_of_person

View File

@ -11,7 +11,7 @@ import threading
import sys
import time
from socket import error as SocketError
from utils import date_utcnow
from timeFunctions import date_utcnow
class thread_with_trace(threading.Thread):

242
timeFunctions.py 100644
View File

@ -0,0 +1,242 @@
__filename__ = "timeFunctions.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.6.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
import os
import time
import datetime
from dateutil.tz import tz
from utils import acct_dir
from utils import data_dir
from utils import has_object_dict
def convert_published_to_local_timezone(published, timezone: str) -> str:
"""Converts a post published time into local time
"""
to_zone = None
from_zone = tz.gettz('UTC')
if timezone:
try:
to_zone = tz.gettz(timezone)
except BaseException:
pass
if not timezone or not to_zone:
return published
utc = published.replace(tzinfo=from_zone)
local_time = utc.astimezone(to_zone)
return local_time
def _utc_mktime(utc_tuple):
"""Returns number of seconds elapsed since epoch
Note that no timezone are taken into consideration.
utc tuple must be: (year, month, day, hour, minute, second)
"""
if len(utc_tuple) == 6:
utc_tuple += (0, 0, 0)
return time.mktime(utc_tuple) - time.mktime((1970, 1, 1, 0, 0, 0, 0, 0, 0))
def _datetime_to_timestamp(dtime):
"""Converts a datetime object to UTC timestamp"""
return int(_utc_mktime(dtime.timetuple()))
def date_utcnow():
"""returns the time now
"""
return datetime.datetime.now(datetime.timezone.utc)
def date_from_numbers(year: int, month: int, day: int,
hour: int, mins: int):
"""returns an offset-aware datetime
"""
return datetime.datetime(year, month, day, hour, mins, 0,
tzinfo=datetime.timezone.utc)
def date_from_string_format(date_str: str, formats: []):
"""returns an offset-aware datetime from a string date
"""
if not formats:
formats = ("%a, %d %b %Y %H:%M:%S %Z",
"%a, %d %b %Y %H:%M:%S %z",
"%Y-%m-%dT%H:%M:%S%z")
dtime = None
for date_format in formats:
try:
dtime = \
datetime.datetime.strptime(date_str, date_format)
except BaseException:
continue
break
if not dtime:
return None
if not dtime.tzinfo:
dtime = dtime.replace(tzinfo=datetime.timezone.utc)
return dtime
def date_epoch():
"""returns an offset-aware version of epoch
"""
return date_from_numbers(1970, 1, 1, 0, 0)
def date_string_to_seconds(date_str: str) -> int:
"""Converts a date string (eg "published") into seconds since epoch
"""
expiry_time = \
date_from_string_format(date_str, ['%Y-%m-%dT%H:%M:%S%z'])
if not expiry_time:
print('EX: date_string_to_seconds unable to parse date ' +
str(date_str))
return None
return _datetime_to_timestamp(expiry_time)
def date_seconds_to_string(date_sec: int) -> str:
"""Converts a date in seconds since epoch to a string
"""
this_date = \
datetime.datetime.fromtimestamp(date_sec, datetime.timezone.utc)
if not this_date.tzinfo:
this_date = this_date.replace(tzinfo=datetime.timezone.utc)
this_date_tz = this_date.astimezone(datetime.timezone.utc)
return this_date_tz.strftime("%Y-%m-%dT%H:%M:%SZ")
def get_account_timezone(base_dir: str, nickname: str, domain: str) -> str:
"""Returns the timezone for the given account
"""
tz_filename = \
acct_dir(base_dir, nickname, domain) + '/timezone.txt'
if not os.path.isfile(tz_filename):
return None
timezone = None
try:
with open(tz_filename, 'r', encoding='utf-8') as fp_timezone:
timezone = fp_timezone.read().strip()
except OSError:
print('EX: get_account_timezone unable to read ' + tz_filename)
return timezone
def set_account_timezone(base_dir: str, nickname: str, domain: str,
timezone: str) -> None:
"""Sets the timezone for the given account
"""
tz_filename = \
acct_dir(base_dir, nickname, domain) + '/timezone.txt'
timezone = timezone.strip()
try:
with open(tz_filename, 'w+', encoding='utf-8') as fp_timezone:
fp_timezone.write(timezone)
except OSError:
print('EX: set_account_timezone unable to write ' +
tz_filename)
def load_account_timezones(base_dir: str) -> {}:
"""Returns a dictionary containing the preferred timezone for each account
"""
account_timezone = {}
dir_str = data_dir(base_dir)
for _, dirs, _ in os.walk(dir_str):
for acct in dirs:
if '@' not in acct:
continue
if acct.startswith('inbox@') or acct.startswith('Actor@'):
continue
acct_directory = os.path.join(dir_str, acct)
tz_filename = acct_directory + '/timezone.txt'
if not os.path.isfile(tz_filename):
continue
timezone = None
try:
with open(tz_filename, 'r', encoding='utf-8') as fp_timezone:
timezone = fp_timezone.read().strip()
except OSError:
print('EX: load_account_timezones unable to read ' +
tz_filename)
if timezone:
nickname = acct.split('@')[0]
account_timezone[nickname] = timezone
break
return account_timezone
def week_day_of_month_start(month_number: int, year: int) -> int:
"""Gets the day number of the first day of the month
1=sun, 7=sat
"""
first_day_of_month = date_from_numbers(year, month_number, 1, 0, 0)
return int(first_day_of_month.strftime("%w")) + 1
def valid_post_date(published: str, max_age_days: int, debug: bool) -> bool:
"""Returns true if the published date is recent and is not in the future
"""
baseline_time = date_epoch()
days_diff = date_utcnow() - baseline_time
now_days_since_epoch = days_diff.days
post_time_object = \
date_from_string_format(published, ["%Y-%m-%dT%H:%M:%S%z"])
if not post_time_object:
if debug:
print('EX: valid_post_date invalid published date ' +
str(published))
return False
days_diff = post_time_object - baseline_time
post_days_since_epoch = days_diff.days
if post_days_since_epoch > now_days_since_epoch:
if debug:
print("Inbox post has a published date in the future!")
return False
if now_days_since_epoch - post_days_since_epoch >= max_age_days:
if debug:
print("Inbox post is not recent enough")
return False
return True
def time_days_ago(datestr: str) -> int:
"""returns the number of days ago for the given date
"""
date1 = \
date_from_string_format(datestr,
["%Y-%m-%dT%H:%M:%S%z"])
if not date1:
return 0
date_diff = date_utcnow() - date1
return date_diff.days
def get_published_date(post_json_object: {}) -> str:
"""Returns the published date on the given post
"""
published = None
if post_json_object.get('published'):
published = post_json_object['published']
elif has_object_dict(post_json_object):
if post_json_object['object'].get('published'):
published = post_json_object['object']['published']
if not published:
return None
if not isinstance(published, str):
return None
return published

329
utils.py
View File

@ -18,7 +18,6 @@ import json
import locale
from pprint import pprint
import idna
from dateutil.tz import tz
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from followingCalendar import add_person_to_calendar
@ -65,64 +64,6 @@ def remove_zero_length_strings(text: str) -> str:
return text.replace('', '')
def _utc_mktime(utc_tuple):
"""Returns number of seconds elapsed since epoch
Note that no timezone are taken into consideration.
utc tuple must be: (year, month, day, hour, minute, second)
"""
if len(utc_tuple) == 6:
utc_tuple += (0, 0, 0)
return time.mktime(utc_tuple) - time.mktime((1970, 1, 1, 0, 0, 0, 0, 0, 0))
def _datetime_to_timestamp(dtime):
"""Converts a datetime object to UTC timestamp"""
return int(_utc_mktime(dtime.timetuple()))
def date_utcnow():
"""returns the time now
"""
return datetime.datetime.now(datetime.timezone.utc)
def date_from_numbers(year: int, month: int, day: int,
hour: int, mins: int):
"""returns an offset-aware datetime
"""
return datetime.datetime(year, month, day, hour, mins, 0,
tzinfo=datetime.timezone.utc)
def date_from_string_format(date_str: str, formats: []):
"""returns an offset-aware datetime from a string date
"""
if not formats:
formats = ("%a, %d %b %Y %H:%M:%S %Z",
"%a, %d %b %Y %H:%M:%S %z",
"%Y-%m-%dT%H:%M:%S%z")
dtime = None
for date_format in formats:
try:
dtime = \
datetime.datetime.strptime(date_str, date_format)
except BaseException:
continue
break
if not dtime:
return None
if not dtime.tzinfo:
dtime = dtime.replace(tzinfo=datetime.timezone.utc)
return dtime
def date_epoch():
"""returns an offset-aware version of epoch
"""
return date_from_numbers(1970, 1, 1, 0, 0)
def get_url_from_post(url_field) -> str:
"""Returns a url from a post object
"""
@ -658,37 +599,6 @@ def has_users_path(path_str: str) -> bool:
return False
def valid_post_date(published: str, max_age_days: int, debug: bool) -> bool:
"""Returns true if the published date is recent and is not in the future
"""
baseline_time = date_epoch()
days_diff = date_utcnow() - baseline_time
now_days_since_epoch = days_diff.days
post_time_object = \
date_from_string_format(published, ["%Y-%m-%dT%H:%M:%S%z"])
if not post_time_object:
if debug:
print('EX: valid_post_date invalid published date ' +
str(published))
return False
days_diff = post_time_object - baseline_time
post_days_since_epoch = days_diff.days
if post_days_since_epoch > now_days_since_epoch:
if debug:
print("Inbox post has a published date in the future!")
return False
if now_days_since_epoch - post_days_since_epoch >= max_age_days:
if debug:
print("Inbox post is not recent enough")
return False
return True
def get_full_domain(domain: str, port: int) -> str:
"""Returns the full domain name, including port number
"""
@ -1147,32 +1057,6 @@ def load_json_onionify(filename: str, domain: str, onion_domain: str,
return json_object
def get_status_number(published_str: str = None) -> (str, str):
"""Returns the status number and published date
"""
if not published_str:
curr_time = date_utcnow()
else:
curr_time = \
date_from_string_format(published_str, ['%Y-%m-%dT%H:%M:%S%z'])
days_since_epoch = (curr_time - date_epoch()).days
# status is the number of seconds since epoch
status_number = \
str(((days_since_epoch * 24 * 60 * 60) +
(curr_time.hour * 60 * 60) +
(curr_time.minute * 60) +
curr_time.second) * 1000 +
int(curr_time.microsecond / 1000))
# See https://github.com/tootsuite/mastodon/blob/
# 995f8b389a66ab76ec92d9a240de376f1fc13a38/lib/mastodon/snowflake.rb
# use the leftover microseconds as the sequence number
sequence_id = curr_time.microsecond % 1000
# shift by 16bits "sequence data"
status_number = str((int(status_number) << 16) + sequence_id)
published = curr_time.strftime("%Y-%m-%dT%H:%M:%SZ")
return status_number, published
def evil_incarnate() -> []:
"""Hardcoded blocked domains
"""
@ -1866,39 +1750,6 @@ def locate_news_votes(base_dir: str, domain: str,
return None
def locate_news_arrival(base_dir: str, domain: str,
post_url: str) -> str:
"""Returns the arrival time for a news post
within the news user account
"""
post_url1 = post_url.strip()
post_url = remove_eol(post_url1)
# if this post in the shared inbox?
post_url = remove_id_ending(post_url.strip()).replace('/', '#')
if post_url.endswith('.json'):
post_url = post_url + '.arrived'
else:
post_url = post_url + '.json.arrived'
account_dir = data_dir(base_dir) + '/news@' + domain + '/'
post_filename = account_dir + 'outbox/' + post_url
if os.path.isfile(post_filename):
try:
with open(post_filename, 'r', encoding='utf-8') as fp_arrival:
arrival = fp_arrival.read()
if arrival:
arrival_date = \
date_from_string_format(arrival,
["%Y-%m-%dT%H:%M:%S%z"])
return arrival_date
except OSError:
print('EX: locate_news_arrival unable to read ' + post_filename)
return None
def locate_post(base_dir: str, nickname: str, domain: str,
post_url: str, replies: bool = False) -> str:
"""Returns the filename for the given status post url
@ -1937,22 +1788,6 @@ def locate_post(base_dir: str, nickname: str, domain: str,
return None
def _get_published_date(post_json_object: {}) -> str:
"""Returns the published date on the given post
"""
published = None
if post_json_object.get('published'):
published = post_json_object['published']
elif has_object_dict(post_json_object):
if post_json_object['object'].get('published'):
published = post_json_object['object']['published']
if not published:
return None
if not isinstance(published, str):
return None
return published
def get_reply_interval_hours(base_dir: str, nickname: str, domain: str,
default_reply_interval_hrs: int) -> int:
"""Returns the reply interval for the given account.
@ -1994,49 +1829,6 @@ def set_reply_interval_hours(base_dir: str, nickname: str, domain: str,
return False
def can_reply_to(base_dir: str, nickname: str, domain: str,
post_url: str, reply_interval_hours: int,
curr_date_str: str = None,
post_json_object: {} = None) -> bool:
"""Is replying to the given local post permitted?
This is a spam mitigation feature, so that spammers can't
add a lot of replies to old post which you don't notice.
"""
if '/statuses/' not in post_url:
return True
if not post_json_object:
post_filename = locate_post(base_dir, nickname, domain, post_url)
if not post_filename:
# the post is not stored locally
return True
post_json_object = load_json(post_filename)
if not post_json_object:
return False
published = _get_published_date(post_json_object)
if not published:
return False
pub_date = date_from_string_format(published, ['%Y-%m-%dT%H:%M:%S%z'])
if not pub_date:
print('EX: can_reply_to unrecognized published date ' + str(published))
return False
if not curr_date_str:
curr_date = date_utcnow()
else:
curr_date = \
date_from_string_format(curr_date_str, ['%Y-%m-%dT%H:%M:%S%z'])
if not curr_date:
print('EX: can_reply_to unrecognized current date ' +
str(curr_date_str))
return False
hours_since_publication = \
int((curr_date - pub_date).total_seconds() / 3600)
if hours_since_publication < 0 or \
hours_since_publication >= reply_interval_hours:
return False
return True
def _remove_attachment(base_dir: str, http_prefix: str,
nickname: str, domain: str, post_json: {}) -> None:
"""Removes media files for an attachment
@ -3106,14 +2898,6 @@ def update_announce_collection(recent_posts_cache: {},
save_json(post_json_object, post_filename)
def week_day_of_month_start(month_number: int, year: int) -> int:
"""Gets the day number of the first day of the month
1=sun, 7=sat
"""
first_day_of_month = date_from_numbers(year, month_number, 1, 0, 0)
return int(first_day_of_month.strftime("%w")) + 1
def media_file_mime_type(filename: str) -> str:
"""Given a media filename return its mime type
"""
@ -3149,18 +2933,6 @@ def media_file_mime_type(filename: str) -> str:
return extensions[file_ext]
def time_days_ago(datestr: str) -> int:
"""returns the number of days ago for the given date
"""
date1 = \
date_from_string_format(datestr,
["%Y-%m-%dT%H:%M:%S%z"])
if not date1:
return 0
date_diff = date_utcnow() - date1
return date_diff.days
def camel_case_split(text: str) -> str:
""" Splits CamelCase into "Camel Case"
"""
@ -3526,29 +3298,6 @@ def valid_password(password: str, debug: bool) -> bool:
return True
def date_string_to_seconds(date_str: str) -> int:
"""Converts a date string (eg "published") into seconds since epoch
"""
expiry_time = \
date_from_string_format(date_str, ['%Y-%m-%dT%H:%M:%S%z'])
if not expiry_time:
print('EX: date_string_to_seconds unable to parse date ' +
str(date_str))
return None
return _datetime_to_timestamp(expiry_time)
def date_seconds_to_string(date_sec: int) -> str:
"""Converts a date in seconds since epoch to a string
"""
this_date = \
datetime.datetime.fromtimestamp(date_sec, datetime.timezone.utc)
if not this_date.tzinfo:
this_date = this_date.replace(tzinfo=datetime.timezone.utc)
this_date_tz = this_date.astimezone(datetime.timezone.utc)
return this_date_tz.strftime("%Y-%m-%dT%H:%M:%SZ")
def get_currencies() -> {}:
"""Returns a dictionary of currencies
"""
@ -3796,53 +3545,6 @@ def valid_hash_tag(hashtag: str) -> bool:
return False
def convert_published_to_local_timezone(published, timezone: str) -> str:
"""Converts a post published time into local time
"""
to_zone = None
from_zone = tz.gettz('UTC')
if timezone:
try:
to_zone = tz.gettz(timezone)
except BaseException:
pass
if not timezone or not to_zone:
return published
utc = published.replace(tzinfo=from_zone)
local_time = utc.astimezone(to_zone)
return local_time
def load_account_timezones(base_dir: str) -> {}:
"""Returns a dictionary containing the preferred timezone for each account
"""
account_timezone = {}
dir_str = data_dir(base_dir)
for _, dirs, _ in os.walk(dir_str):
for acct in dirs:
if '@' not in acct:
continue
if acct.startswith('inbox@') or acct.startswith('Actor@'):
continue
acct_directory = os.path.join(dir_str, acct)
tz_filename = acct_directory + '/timezone.txt'
if not os.path.isfile(tz_filename):
continue
timezone = None
try:
with open(tz_filename, 'r', encoding='utf-8') as fp_timezone:
timezone = fp_timezone.read().strip()
except OSError:
print('EX: load_account_timezones unable to read ' +
tz_filename)
if timezone:
nickname = acct.split('@')[0]
account_timezone[nickname] = timezone
break
return account_timezone
def load_bold_reading(base_dir: str) -> {}:
"""Returns a dictionary containing the bold reading status for each account
"""
@ -3902,37 +3604,6 @@ def load_hide_recent_posts(base_dir: str) -> {}:
return hide_recent_posts
def get_account_timezone(base_dir: str, nickname: str, domain: str) -> str:
"""Returns the timezone for the given account
"""
tz_filename = \
acct_dir(base_dir, nickname, domain) + '/timezone.txt'
if not os.path.isfile(tz_filename):
return None
timezone = None
try:
with open(tz_filename, 'r', encoding='utf-8') as fp_timezone:
timezone = fp_timezone.read().strip()
except OSError:
print('EX: get_account_timezone unable to read ' + tz_filename)
return timezone
def set_account_timezone(base_dir: str, nickname: str, domain: str,
timezone: str) -> None:
"""Sets the timezone for the given account
"""
tz_filename = \
acct_dir(base_dir, nickname, domain) + '/timezone.txt'
timezone = timezone.strip()
try:
with open(tz_filename, 'w+', encoding='utf-8') as fp_timezone:
fp_timezone.write(timezone)
except OSError:
print('EX: set_account_timezone unable to write ' +
tz_filename)
def _is_onion_request(calling_domain: str, referer_domain: str,
domain: str, onion_domain: str) -> bool:
"""Do the given domains indicate that this is a request

View File

@ -18,14 +18,14 @@ from utils import get_nickname_from_actor
from utils import get_domain_from_actor
from utils import locate_post
from utils import load_json
from utils import week_day_of_month_start
from utils import get_alt_path
from utils import remove_domain_port
from utils import acct_dir
from utils import local_actor_url
from utils import replace_users_with_at
from utils import language_right_to_left
from utils import date_from_string_format
from timeFunctions import week_day_of_month_start
from timeFunctions import date_from_string_format
from happening import get_todays_events
from happening import get_calendar_events
from happening import get_todays_events_icalendar

View File

@ -24,7 +24,7 @@ from utils import get_nickname_from_actor
from utils import get_config_param
from utils import remove_domain_port
from utils import acct_dir
from utils import date_from_string_format
from timeFunctions import date_from_string_format
from posts import is_moderator
from newswire import get_newswire_favicon_url
from webapp_utils import get_right_image_file

View File

@ -18,7 +18,7 @@ from utils import load_json
from utils import get_config_param
from utils import get_alt_path
from utils import acct_dir
from utils import get_account_timezone
from timeFunctions import get_account_timezone
from blocking import sending_is_blocked2
from webapp_utils import set_custom_background
from webapp_utils import html_header_with_external_style

View File

@ -26,10 +26,10 @@ from utils import get_config_param
from utils import acct_dir
from utils import get_currencies
from utils import get_category_types
from utils import get_account_timezone
from utils import get_supported_languages
from utils import get_attributed_to
from utils import get_full_domain
from timeFunctions import get_account_timezone
from blocking import sending_is_blocked2
from webapp_utils import open_content_warning
from webapp_utils import edit_check_box

View File

@ -11,7 +11,7 @@ import os
from flags import is_system_account
from utils import get_domain_from_actor
from utils import get_config_param
from utils import get_account_timezone
from timeFunctions import get_account_timezone
from person import person_box_json
from webapp_utils import html_header_with_external_style
from webapp_utils import html_footer

View File

@ -10,20 +10,20 @@ __module_group__ = "Web Interface"
import os
from datetime import datetime, timezone
from flags import is_public_post
from timeFunctions import date_utcnow
from timeFunctions import date_from_string_format
from timeFunctions import date_epoch
from utils import valid_hash_tag
from utils import remove_id_ending
from utils import resembles_url
from utils import has_object_dict
from utils import local_actor_url
from utils import date_from_string_format
from utils import file_last_modified
from utils import acct_dir
from utils import data_dir
from utils import get_nickname_from_actor
from utils import get_config_param
from utils import escape_text
from utils import date_utcnow
from utils import date_epoch
from utils import string_contains
from delete import remove_old_hashtags
from maps import get_category_from_post

View File

@ -10,12 +10,12 @@ __module_group__ = "ActivityPub"
import os
from utils import locate_post
from utils import get_config_param
from utils import get_account_timezone
from utils import get_display_name
from utils import get_nickname_from_actor
from utils import has_object_dict
from utils import load_json
from utils import get_actor_from_post
from timeFunctions import get_account_timezone
from person import get_person_avatar_url
from webapp_utils import html_header_with_external_style
from webapp_utils import html_footer

View File

@ -45,7 +45,6 @@ from utils import contains_statuses
from utils import data_dir
from utils import get_post_attachments
from utils import get_url_from_post
from utils import date_from_string_format
from utils import remove_markup_tag
from utils import ap_proxy_type
from utils import remove_style_within_html
@ -54,7 +53,8 @@ from utils import dont_speak_hashtags
from utils import remove_eol
from utils import disallow_announce
from utils import disallow_reply
from utils import convert_published_to_local_timezone
from timeFunctions import date_from_string_format
from timeFunctions import convert_published_to_local_timezone
from utils import remove_hash_from_post_id
from utils import remove_html
from utils import get_actor_languages_list

View File

@ -24,7 +24,6 @@ from unicodetext import standardize_text
from utils import get_person_icon
from utils import replace_strings
from utils import data_dir
from utils import time_days_ago
from utils import get_attributed_to
from utils import get_url_from_post
from utils import get_memorials
@ -47,10 +46,11 @@ from utils import acct_dir
from utils import get_supported_languages
from utils import local_actor_url
from utils import get_reply_interval_hours
from utils import get_account_timezone
from utils import remove_eol
from utils import get_actor_from_post
from utils import resembles_url
from timeFunctions import time_days_ago
from timeFunctions import get_account_timezone
from languages import get_actor_languages
from skills import get_skills
from theme import get_themes_list

View File

@ -13,11 +13,11 @@ import urllib.parse
from flags import is_editor
from flags import is_public_post
from searchable import search_box_posts
from timeFunctions import date_from_string_format
from utils import get_person_icon
from utils import data_dir
from utils import get_post_attachments
from utils import get_url_from_post
from utils import date_from_string_format
from utils import get_attributed_to
from utils import get_actor_from_post_id
from utils import remove_html