From ed742cd1f1b9b6ffcb98041ae80224c3aa65c3cf Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 22 Aug 2023 18:13:35 +0100 Subject: [PATCH] Optionally add shared items to actor attachments --- daemon.py | 44 +++++++++ shares.py | 251 ++++++++++++++++++++++++++++++++++++++++++++++++ webapp_utils.py | 186 ++--------------------------------- 3 files changed, 302 insertions(+), 179 deletions(-) diff --git a/daemon.py b/daemon.py index bc67f7515..2651207c4 100644 --- a/daemon.py +++ b/daemon.py @@ -268,6 +268,7 @@ from webapp_welcome import html_welcome_screen from webapp_welcome import is_welcome_screen_complete from webapp_welcome_profile import html_welcome_profile from webapp_welcome_final import html_welcome_final +from shares import add_shares_to_actor from shares import merge_shared_item_tokens from shares import run_federated_shares_daemon from shares import run_federated_shares_watchdog @@ -5268,6 +5269,24 @@ class PubServer(BaseHTTPRequestHandler): remove_shared_item(base_dir, share_nickname, share_domain, item_id, 'shares') + # remove shared items from the actor attachments + # https://codeberg.org/fediverse/fep/ + # src/branch/main/fep/0837/fep-0837.md + actor = \ + self._get_instance_url(calling_domain) + \ + '/users/' + share_nickname + person_cache = self.server.person_cache + actor_json = get_person_from_cache(base_dir, + actor, person_cache) + if add_shares_to_actor(base_dir, + share_nickname, share_domain, + actor_json, + self.server.max_shares_on_profile): + store_person_in_cache(base_dir, actor, + actor_json, person_cache, False) + actor_filename = acct_dir(base_dir, share_nickname, + share_domain) + '.json' + save_json(actor_json, actor_filename) if calling_domain.endswith('.onion') and onion_domain: origin_path_str = 'http://' + onion_domain + users_path @@ -22415,6 +22434,9 @@ class PubServer(BaseHTTPRequestHandler): print('Adding wanted item') shares_file_type = 'wanted' on_profile = False + if fields.get('onProfile'): + if fields['onProfile'] == 'on': + on_profile = True add_share(self.server.base_dir, self.server.http_prefix, nickname, @@ -22433,6 +22455,26 @@ class PubServer(BaseHTTPRequestHandler): self.server.low_bandwidth, self.server.content_license_url, on_profile) + if post_type == 'newshare': + # add onProfile items to the actor attachments + # https://codeberg.org/fediverse/fep/src/branch/main/fep/0837/fep-0837.md + actor = \ + self._get_instance_url(calling_domain) + \ + '/users/' + nickname + person_cache = self.server.person_cache + actor_json = get_person_from_cache(self.server.base_dir, + actor, person_cache) + if add_shares_to_actor(self.server.base_dir, + nickname, self.server.domain, + actor_json, + self.server.max_shares_on_profile): + store_person_in_cache(self.server.base_dir, actor, + actor_json, person_cache, False) + actor_filename = acct_dir(self.server.base_dir, + nickname, + self.server.domain) + '.json' + save_json(actor_json, actor_filename) + if filename: if os.path.isfile(filename): try: @@ -23667,6 +23709,8 @@ def run_daemon(max_hashtags: int, httpd.starting_daemon = True + httpd.max_shares_on_profile = 8 + # load a list of nicknames for accounts blocking military instances httpd.block_military = load_blocked_military(base_dir) diff --git a/shares.py b/shares.py index 1d289832c..d96c8f5de 100644 --- a/shares.py +++ b/shares.py @@ -23,6 +23,8 @@ from session import post_json from session import post_image from session import create_session from session import get_json_valid +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 @@ -1944,3 +1946,252 @@ def share_category_icon(category: str) -> str: if category_icons.get(category): return category_icons[category] return '' + + +def _currency_to_wikidata(currency_type: str) -> str: + """Converts a currency type, such as USD, into a wikidata reference + """ + currencies = { + "GBP": "https://www.wikidata.org/wiki/Q25224", + "EUR": "https://www.wikidata.org/wiki/Q4916", + "CAD": "https://www.wikidata.org/wiki/Q1104069", + "USD": "https://www.wikidata.org/wiki/Q4917", + "AUD": "https://www.wikidata.org/wiki/Q259502", + "PKR": "https://www.wikidata.org/wiki/Q188289", + "PEN": "https://www.wikidata.org/wiki/Q204656", + "PAB": "https://www.wikidata.org/wiki/Q210472", + "PHP": "https://www.wikidata.org/wiki/Q17193", + "RWF": "https://www.wikidata.org/wiki/Q4741", + "NZD": "https://www.wikidata.org/wiki/Q1472704", + "MXN": "https://www.wikidata.org/wiki/Q4730", + "JMD": "https://www.wikidata.org/wiki/Q209792", + "ISK": "https://www.wikidata.org/wiki/Q131473", + "EGP": "https://www.wikidata.org/wiki/Q199462", + "CNY": "https://www.wikidata.org/wiki/Q39099", + "AFN": "https://www.wikidata.org/wiki/Q199471", + "AWG": "https://www.wikidata.org/wiki/Q232270", + "AZN": "https://www.wikidata.org/wiki/Q483725", + "BYN": "https://www.wikidata.org/wiki/Q21531507", + "BZD": "https://www.wikidata.org/wiki/Q275112", + "BOB": "https://www.wikidata.org/wiki/Q200737", + "BAM": "https://www.wikidata.org/wiki/Q179620", + "BWP": "https://www.wikidata.org/wiki/Q186794", + "BGN": "https://www.wikidata.org/wiki/Q172540", + "BRL": "https://www.wikidata.org/wiki/Q173117", + "KHR": "https://www.wikidata.org/wiki/Q204737", + "UYU": "https://www.wikidata.org/wiki/Q209272", + "DOP": "https://www.wikidata.org/wiki/Q242922", + "CRC": "https://www.wikidata.org/wiki/Q242915", + "HRK": "https://www.wikidata.org/wiki/Q595634", + "CUP": "https://www.wikidata.org/wiki/Q201505", + "CZK": "https://www.wikidata.org/wiki/Q131016", + "NOK": "https://www.wikidata.org/wiki/Q132643", + "GHS": "https://www.wikidata.org/wiki/Q183530", + "GTQ": "https://www.wikidata.org/wiki/Q207396", + "HNL": "https://www.wikidata.org/wiki/Q4719", + "HUF": "https://www.wikidata.org/wiki/Q47190", + "IDR": "https://www.wikidata.org/wiki/Q41588", + "INR": "https://www.wikidata.org/wiki/Q80524", + "IRR": "https://www.wikidata.org/wiki/Q188608", + "ILS": "https://www.wikidata.org/wiki/Q131309", + "JPY": "https://www.wikidata.org/wiki/Q8146", + "KRW": "https://www.wikidata.org/wiki/Q202040", + "LAK": "https://www.wikidata.org/wiki/Q200055", + "MKD": "https://www.wikidata.org/wiki/Q177875", + "MYR": "https://www.wikidata.org/wiki/Q163712", + "MUR": "https://www.wikidata.org/wiki/Q212967", + "MNT": "https://www.wikidata.org/wiki/Q183435", + "MZN": "https://www.wikidata.org/wiki/Q200753", + "NIO": "https://www.wikidata.org/wiki/Q207312", + "NGN": "https://www.wikidata.org/wiki/Q203567", + "PYG": "https://www.wikidata.org/wiki/Q207514", + "PLN": "https://www.wikidata.org/wiki/Q123213", + "RON": "https://www.wikidata.org/wiki/Q131645", + "RUB": "https://www.wikidata.org/wiki/Q41044", + "RSD": "https://www.wikidata.org/wiki/Q172524", + "SOS": "https://www.wikidata.org/wiki/Q4603", + "ZAR": "https://www.wikidata.org/wiki/Q181907", + "CHF": "https://www.wikidata.org/wiki/Q25344", + "TWD": "https://www.wikidata.org/wiki/Q208526", + "THB": "https://www.wikidata.org/wiki/Q177882", + "TTD": "https://www.wikidata.org/wiki/Q242890", + "UAH": "https://www.wikidata.org/wiki/Q81893", + "VES": "https://www.wikidata.org/wiki/Q56349362", + "VEB": "https://www.wikidata.org/wiki/Q56349362", + "VND": "https://www.wikidata.org/wiki/Q192090" + } + currency_type = currency_type.upper() + for curr, curr_url in currencies.items(): + if curr in currency_type: + return curr_url + return "https://www.wikidata.org/wiki/Q25224" + + +def _vf_share_id(share_id: str) -> str: + """returns the share id + """ + share_id = share_id.replace('___', '://') + return share_id.replace('--', '/') + + +def vf_proposal_from_share(shared_item: {}, + share_type: str, + publishes_direction: str, + reciprocal_direction: str) -> {}: + """Returns a ValueFlows proposal from a shared item + """ + if not shared_item.get('shareId'): + return {} + om2_link = \ + "http://www.ontology-of-units-of-measure.org/resource/om-2/" + share_id = _vf_share_id(shared_item['shareId']) + offer_item = { + "@context": [ + "https://www.w3.org/ns/activitystreams", + { + "om2": om2_link, + "vf": "https://w3id.org/valueflows/", + "Proposal": "vf:Proposal", + "Intent": "vf:Intent", + "receiver": "vf:receiver", + "provider": "vf:provider", + "action": "vf:action", + "unitBased": "vf:unitBased", + "publishes": "vf:publishes", + "reciprocal": "vf:reciprocal", + "resourceClassifiedAs": "vf:resourceClassifiedAs", + "resourceConformsTo": "vf:resourceConformsTo", + "resourceQuantity": "vf:resourceQuantity", + "hasUnit": "om2:hasUnit", + "hasNumericalValue": "om2:hasNumericalValue" + } + ], + "type": share_type, + "id": share_id, + "attributedTo": shared_item['actor'], + "name": shared_item['displayName'], + "content": shared_item['summary'], + "published": shared_item['published'], + "publishes": { + "type": "Intent", + "id": share_id + '#primary', + "action": "transfer", + "resourceQuantity": { + "hasUnit": "one", + "hasNumericalValue": str(shared_item['itemQty']) + }, + publishes_direction: shared_item['actor'] + }, + "attachment": [], + "unitBased": False, + "to": "https://www.w3.org/ns/activitystreams#Public" + + } + if shared_item.get('dfcId'): + offer_item['publishes']['resourceConformsTo'] = \ + shared_item['dfcId'] + if shared_item['category']: + offer_item['attachment'].append({ + "type": "PropertyValue", + "name": "category", + "value": shared_item['category'] + }) + if shared_item['location']: + # pixelfed style representation of location + offer_item['location'] = { + "type": "Place", + "name": shared_item['location'].title() + } + if shared_item['imageUrl']: + if '://' in shared_item['imageUrl']: + file_extension = None + accepted_types = get_media_extensions() + for mtype in accepted_types: + if shared_item['imageUrl'].endswith('.' + mtype): + if mtype == 'jpg': + mtype = 'jpeg' + if mtype == 'mp3': + mtype = 'mpeg' + file_extension = mtype + if file_extension: + media_type = 'image/' + file_extension + shared_item_url = remove_html(shared_item['imageUrl']) + offer_item['attachment'].append({ + 'mediaType': media_type, + 'name': shared_item['displayName'], + 'type': 'Document', + 'url': shared_item_url + }) + if shared_item['itemPrice'] and shared_item['itemCurrency']: + currency_url = _currency_to_wikidata(shared_item['itemCurrency']) + offer_item['publishes']['reciprocal'] = { + "type": "Intent", + "id": share_id + '#reciprocal', + "action": "transfer", + "resourceConformsTo": currency_url, + "resourceQuantity": { + "hasUnit": "one", + "hasNumericalValue": str(shared_item['itemPrice']) + }, + reciprocal_direction: shared_item['actor'] + } + return offer_item + + +def add_shares_to_actor(base_dir: str, + nickname: str, domain: str, + actor_json: {}, + max_shares_on_profile: int) -> bool: + """Adds shared items to the given actor + """ + if 'attachment' not in actor_json: + actor_json['attachment'] = [] + changed = False + + # remove any existing ValueFlows items from attachment list + new_attachment = [] + for attach_item in actor_json['attachment']: + is_proposal = False + if 'rel' not in attach_item: + if isinstance(attach_item['rel'], list): + if len(attach_item['rel']) == 2: + if attach_item['rel'][0] == 'payment' and \ + attach_item['rel'][1].endswith('/valueflows/Proposal'): + changed = True + is_proposal = True + if not is_proposal: + new_attachment.append(attach_item) + actor_json['attachment'] = new_attachment + + # do shared items exist for this account? + shares_filename = \ + acct_dir(base_dir, nickname, domain) + '/shares.json' + if not os.path.isfile(shares_filename): + return changed + shares_json = load_json(shares_filename) + if not shares_json: + return changed + + # add ValueFlows items to the attachment list + media_type = \ + "application/ld+json; profile=" + \ + "\"https://www.w3.org/ns/activitystreams\"" + ctr = 0 + for share_id, shared_item in shares_json.items(): + if ctr >= max_shares_on_profile: + break + if not shared_item.get('onProfile'): + continue + if not shared_item.get('shareId'): + continue + share_id = _vf_share_id(shared_item['shareId']) + actor_json['attachment'].append({ + "type": "Link", + "name": shared_item['displayName'], + "mediaType": media_type, + "href": share_id, + "rel": ["payment", "https://w3id.org/valueflows/Proposal"] + }) + changed = True + ctr += 1 + return changed diff --git a/webapp_utils.py b/webapp_utils.py index 809f57bbf..98d48b1bb 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -13,7 +13,6 @@ from collections import OrderedDict from session import get_json from session import get_json_valid from utils import local_network_host -from utils import get_media_extensions from utils import dangerous_markup from utils import acct_handle_dir from utils import remove_id_ending @@ -43,6 +42,7 @@ from person import get_person_avatar_url from posts import is_moderator from blocking import is_blocked from blocking import allowed_announce +from shares import vf_proposal_from_share def minimizing_attached_images(base_dir: str, nickname: str, domain: str, @@ -561,85 +561,6 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int, return result_json, last_page -def _currency_to_wikidata(currency_type: str) -> str: - """Converts a currency type, such as USD, into a wikidata reference - """ - currencies = { - "GBP": "https://www.wikidata.org/wiki/Q25224", - "EUR": "https://www.wikidata.org/wiki/Q4916", - "CAD": "https://www.wikidata.org/wiki/Q1104069", - "USD": "https://www.wikidata.org/wiki/Q4917", - "AUD": "https://www.wikidata.org/wiki/Q259502", - "PKR": "https://www.wikidata.org/wiki/Q188289", - "PEN": "https://www.wikidata.org/wiki/Q204656", - "PAB": "https://www.wikidata.org/wiki/Q210472", - "PHP": "https://www.wikidata.org/wiki/Q17193", - "RWF": "https://www.wikidata.org/wiki/Q4741", - "NZD": "https://www.wikidata.org/wiki/Q1472704", - "MXN": "https://www.wikidata.org/wiki/Q4730", - "JMD": "https://www.wikidata.org/wiki/Q209792", - "ISK": "https://www.wikidata.org/wiki/Q131473", - "EGP": "https://www.wikidata.org/wiki/Q199462", - "CNY": "https://www.wikidata.org/wiki/Q39099", - "AFN": "https://www.wikidata.org/wiki/Q199471", - "AWG": "https://www.wikidata.org/wiki/Q232270", - "AZN": "https://www.wikidata.org/wiki/Q483725", - "BYN": "https://www.wikidata.org/wiki/Q21531507", - "BZD": "https://www.wikidata.org/wiki/Q275112", - "BOB": "https://www.wikidata.org/wiki/Q200737", - "BAM": "https://www.wikidata.org/wiki/Q179620", - "BWP": "https://www.wikidata.org/wiki/Q186794", - "BGN": "https://www.wikidata.org/wiki/Q172540", - "BRL": "https://www.wikidata.org/wiki/Q173117", - "KHR": "https://www.wikidata.org/wiki/Q204737", - "UYU": "https://www.wikidata.org/wiki/Q209272", - "DOP": "https://www.wikidata.org/wiki/Q242922", - "CRC": "https://www.wikidata.org/wiki/Q242915", - "HRK": "https://www.wikidata.org/wiki/Q595634", - "CUP": "https://www.wikidata.org/wiki/Q201505", - "CZK": "https://www.wikidata.org/wiki/Q131016", - "NOK": "https://www.wikidata.org/wiki/Q132643", - "GHS": "https://www.wikidata.org/wiki/Q183530", - "GTQ": "https://www.wikidata.org/wiki/Q207396", - "HNL": "https://www.wikidata.org/wiki/Q4719", - "HUF": "https://www.wikidata.org/wiki/Q47190", - "IDR": "https://www.wikidata.org/wiki/Q41588", - "INR": "https://www.wikidata.org/wiki/Q80524", - "IRR": "https://www.wikidata.org/wiki/Q188608", - "ILS": "https://www.wikidata.org/wiki/Q131309", - "JPY": "https://www.wikidata.org/wiki/Q8146", - "KRW": "https://www.wikidata.org/wiki/Q202040", - "LAK": "https://www.wikidata.org/wiki/Q200055", - "MKD": "https://www.wikidata.org/wiki/Q177875", - "MYR": "https://www.wikidata.org/wiki/Q163712", - "MUR": "https://www.wikidata.org/wiki/Q212967", - "MNT": "https://www.wikidata.org/wiki/Q183435", - "MZN": "https://www.wikidata.org/wiki/Q200753", - "NIO": "https://www.wikidata.org/wiki/Q207312", - "NGN": "https://www.wikidata.org/wiki/Q203567", - "PYG": "https://www.wikidata.org/wiki/Q207514", - "PLN": "https://www.wikidata.org/wiki/Q123213", - "RON": "https://www.wikidata.org/wiki/Q131645", - "RUB": "https://www.wikidata.org/wiki/Q41044", - "RSD": "https://www.wikidata.org/wiki/Q172524", - "SOS": "https://www.wikidata.org/wiki/Q4603", - "ZAR": "https://www.wikidata.org/wiki/Q181907", - "CHF": "https://www.wikidata.org/wiki/Q25344", - "TWD": "https://www.wikidata.org/wiki/Q208526", - "THB": "https://www.wikidata.org/wiki/Q177882", - "TTD": "https://www.wikidata.org/wiki/Q242890", - "UAH": "https://www.wikidata.org/wiki/Q81893", - "VES": "https://www.wikidata.org/wiki/Q56349362", - "VEB": "https://www.wikidata.org/wiki/Q56349362", - "VND": "https://www.wikidata.org/wiki/Q192090" - } - currency_type = currency_type.upper() - for curr, curr_url in currencies.items(): - if curr in currency_type: - return curr_url - return "https://www.wikidata.org/wiki/Q25224" - - def get_shares_collection(actor: str, page_number: int, items_per_page: int, base_dir: str, domain: str, nickname: str, max_shares_per_account: int, @@ -666,106 +587,13 @@ def get_shares_collection(actor: str, page_number: int, items_per_page: int, reciprocal_direction = "provider" collection_name = nickname + "'s Wanted Items" - om2_link = \ - "http://www.ontology-of-units-of-measure.org/resource/om-2/" - for _, shared_item in shares_json.items(): - if not shared_item.get('shareId'): - continue - share_id = shared_item['shareId'].replace('___', '://') - share_id = share_id.replace('--', '/') - offer_item = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - { - "om2": om2_link, - "vf": "https://w3id.org/valueflows/", - "Proposal": "vf:Proposal", - "Intent": "vf:Intent", - "receiver": "vf:receiver", - "provider": "vf:provider", - "action": "vf:action", - "unitBased": "vf:unitBased", - "publishes": "vf:publishes", - "reciprocal": "vf:reciprocal", - "resourceClassifiedAs": "vf:resourceClassifiedAs", - "resourceConformsTo": "vf:resourceConformsTo", - "resourceQuantity": "vf:resourceQuantity", - "hasUnit": "om2:hasUnit", - "hasNumericalValue": "om2:hasNumericalValue" - } - ], - "type": share_type, - "id": share_id, - "attributedTo": shared_item['actor'], - "name": shared_item['displayName'], - "content": shared_item['summary'], - "published": shared_item['published'], - "publishes": { - "type": "Intent", - "id": share_id + '#primary', - "action": "transfer", - "resourceQuantity": { - "hasUnit": "one", - "hasNumericalValue": str(shared_item['itemQty']) - }, - publishes_direction: shared_item['actor'] - }, - "attachment": [], - "unitBased": False, - "to": "https://www.w3.org/ns/activitystreams#Public" - - } - if shared_item.get('dfcId'): - offer_item['publishes']['resourceConformsTo'] = \ - shared_item['dfcId'] - if shared_item['category']: - offer_item['attachment'].append({ - "type": "PropertyValue", - "name": "category", - "value": shared_item['category'] - }) - if shared_item['location']: - # pixelfed style representation of location - offer_item['location'] = { - "type": "Place", - "name": shared_item['location'].title() - } - if shared_item['imageUrl']: - if '://' in shared_item['imageUrl']: - file_extension = None - accepted_types = get_media_extensions() - for mtype in accepted_types: - if shared_item['imageUrl'].endswith('.' + mtype): - if mtype == 'jpg': - mtype = 'jpeg' - if mtype == 'mp3': - mtype = 'mpeg' - file_extension = mtype - if file_extension: - media_type = 'image/' + file_extension - shared_item_url = remove_html(shared_item['imageUrl']) - offer_item['attachment'].append({ - 'mediaType': media_type, - 'name': shared_item['displayName'], - 'type': 'Document', - 'url': shared_item_url - }) - if shared_item['itemPrice'] and shared_item['itemCurrency']: - currency_url = _currency_to_wikidata(shared_item['itemCurrency']) - offer_item['publishes']['reciprocal'] = { - "type": "Intent", - "id": share_id + '#reciprocal', - "action": "transfer", - "resourceConformsTo": currency_url, - "resourceQuantity": { - "hasUnit": "one", - "hasNumericalValue": str(shared_item['itemPrice']) - }, - reciprocal_direction: shared_item['actor'] - } - - shares_collection.append(offer_item) + offer_item = vf_proposal_from_share(shared_item, + share_type, + publishes_direction, + reciprocal_direction) + if offer_item: + shares_collection.append(offer_item) result_json = { "@context": [