Anti-virality
is a common design approach in the fediverse, and Epicyon also follows
@@ -429,7 +434,7 @@ preferable, so that it matches your typical pattern of daily posting
activity without giving away your real location.
Verifying your website or
blog
-
It is possible to indicate that a website of blog belongs to you by
+
It is possible to indicate that a website or blog belongs to you by
linking it to your profile screen. Within the head html section
of your website or blog index page include a line similar to:
Selecting the location header will open the last known
geolocation, so if your current location is near this makes it quicker
to find.
+
Scientific references
+
It is possible to have references to scientific papers linked
+automatically, such that they are readable with one click/press.
+Supported references are arXiv and Digital
+object identifier (DOI). For example:
+
This is a reference to a paper: arxiv:2203.15752
The Timeline
Layout
@@ -902,6 +914,18 @@ you to change colors and values for user interface components.
Theme designer screen
+
Buying and selling
+
When creating a new post you have the option of specifying a buy
+link This is a link to a web page where you can buy some particular
+item. When someone receives the post if they have a compatible instance
+then a small shopping cart icon will appear at the bottom of the post
+along with the other icons. Clicking or pressing the shopping cart will
+then take you to the buying site. It’s a predictable and machine
+parsable way indicating that something is for sale, separate from the
+post content.
+
To avoid spam, it is possible for the shopping icon to only appear if
+it links to one of an allowed list of seller domains. In this way you
+can be confident that you are only navigating to approved sites.
Sharing economy
This is intended to add Freecycle
diff --git a/manual/manual.md b/manual/manual.md
index 2a37d6a89..ac6b594ec 100644
--- a/manual/manual.md
+++ b/manual/manual.md
@@ -15,10 +15,11 @@
14. [Media timeline](#media-timeline)
15. [Moderation](#moderation)
16. [Themes](#themes)
-17. [Sharing economy](#sharing-economy)
-18. [Search](#search)
-19. [Browsing in a command shell](#browsing-in-a-command-shell)
-20. [Building fediverse communities](#building-fediverse-communities)
+17. [Buying and selling](#buying-and-selling)
+18. [Sharing economy](#sharing-economy)
+19. [Search](#search)
+20. [Browsing in a command shell](#browsing-in-a-command-shell)
+21. [Building fediverse communities](#building-fediverse-communities)
# Introduction
*"Every new beginning comes from some other beginning’s end."*
@@ -655,6 +656,11 @@ If you have the *artist* role then from the top of the left column of the main t

+# Buying and selling
+When creating a new post you have the option of specifying a *buy link* This is a link to a web page where you can buy some particular item. When someone receives the post if they have a compatible instance then a small shopping cart icon will appear at the bottom of the post along with the other icons. Clicking or pressing the shopping cart will then take you to the buying site. It's a predictable and machine parsable way indicating that something is for sale, separate from the post content.
+
+To avoid spam, it is possible for the shopping icon to only appear if it links to one of an allowed list of seller domains. In this way you can be confident that you are only navigating to approved sites.
+
# Sharing economy
This is intended to add [Freecycle](https://en.wikipedia.org/wiki/The_Freecycle_Network) type functionality within a social network context, leveraging your social connections on the instance, or between participating instances, to facilitate sharing and reduce wasteful consumerism.
diff --git a/newsdaemon.py b/newsdaemon.py
index dedcddfba..f8c7b633d 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -636,6 +636,7 @@ def _convert_rss_to_activitypub(base_dir: str, http_prefix: str,
city = 'London, England'
conversation_id = None
languages_understood = [system_language]
+ buy_url = ''
blog = create_news_post(base_dir,
domain, port, http_prefix,
rss_description,
@@ -645,7 +646,8 @@ def _convert_rss_to_activitypub(base_dir: str, http_prefix: str,
rss_title, system_language,
conversation_id, low_bandwidth,
content_license_url,
- languages_understood, translate)
+ languages_understood, translate,
+ buy_url)
if not blog:
continue
diff --git a/outbox.py b/outbox.py
index 8870096ea..a5b56ed36 100644
--- a/outbox.py
+++ b/outbox.py
@@ -238,7 +238,8 @@ def post_message_to_outbox(session, translate: {},
lists_enabled: str,
content_license_url: str,
dogwhistles: {},
- min_images_for_accounts: []) -> bool:
+ min_images_for_accounts: [],
+ buy_sites: {}) -> bool:
"""post is received by the outbox
Client to server message post
https://www.w3.org/TR/activitypub/#client-to-server-outbox-delivery
@@ -593,7 +594,8 @@ def post_message_to_outbox(session, translate: {},
cw_lists, lists_enabled,
timezone, mitm,
bold_reading, dogwhistles,
- minimize_all_images, None)
+ minimize_all_images, None,
+ buy_sites)
if is_edited_post:
message_json['type'] = 'Update'
diff --git a/posts.py b/posts.py
index 21a8c0574..aa4052406 100644
--- a/posts.py
+++ b/posts.py
@@ -1092,6 +1092,29 @@ def _attach_post_license(post_json_object: {},
})
+def _attach_buy_link(post_json_object: {},
+ buy_url: str, translate: {}) -> None:
+ """Attaches a link for buying something
+ """
+ if not buy_url:
+ return
+ if '://' not in buy_url:
+ return
+ if ' ' in buy_url or '<' in buy_url:
+ return
+ buy_str = 'Buy'
+ if translate.get(buy_str):
+ buy_str = translate[buy_str]
+ if 'attachment' not in post_json_object:
+ post_json_object['attachment'] = []
+ post_json_object['attachment'].append({
+ "type": "Link",
+ "name": buy_str,
+ "href": buy_url,
+ "mediaType": "text/html"
+ })
+
+
def _create_post_s2s(base_dir: str, nickname: str, domain: str, port: int,
http_prefix: str, content: str, status_number: str,
published: str, new_post_id: str, post_context: {},
@@ -1102,7 +1125,8 @@ def _create_post_s2s(base_dir: str, nickname: str, domain: str, port: int,
post_object_type: str, summary: str,
in_reply_to_atom_uri: str, system_language: str,
conversation_id: str, low_bandwidth: bool,
- content_license_url: str) -> {}:
+ content_license_url: str, buy_url: str,
+ translate: {}) -> {}:
"""Creates a new server-to-server post
"""
actor_url = local_actor_url(http_prefix, nickname, domain)
@@ -1166,6 +1190,7 @@ def _create_post_s2s(base_dir: str, nickname: str, domain: str, port: int,
media_type, image_description, city, low_bandwidth,
content_license_url)
_attach_post_license(new_post['object'], content_license_url)
+ _attach_buy_link(new_post['object'], buy_url, translate)
return new_post
@@ -1179,7 +1204,8 @@ def _create_post_c2s(base_dir: str, nickname: str, domain: str, port: int,
post_object_type: str, summary: str,
in_reply_to_atom_uri: str, system_language: str,
conversation_id: str, low_bandwidth: str,
- content_license_url: str) -> {}:
+ content_license_url: str, buy_url: str,
+ translate: {}) -> {}:
"""Creates a new client-to-server post
"""
domain_full = get_full_domain(domain, port)
@@ -1233,6 +1259,7 @@ def _create_post_c2s(base_dir: str, nickname: str, domain: str, port: int,
media_type, image_description, city, low_bandwidth,
content_license_url)
_attach_post_license(new_post, content_license_url)
+ _attach_buy_link(new_post, buy_url, translate)
return new_post
@@ -1433,7 +1460,8 @@ def _create_post_base(base_dir: str,
system_language: str,
conversation_id: str, low_bandwidth: bool,
content_license_url: str,
- languages_understood: [], translate: {}) -> {}:
+ languages_understood: [], translate: {},
+ buy_url: str) -> {}:
"""Creates a message
"""
content = remove_invalid_chars(content)
@@ -1591,7 +1619,7 @@ def _create_post_base(base_dir: str,
post_object_type, summary,
in_reply_to_atom_uri, system_language,
conversation_id, low_bandwidth,
- content_license_url)
+ content_license_url, buy_url, translate)
else:
new_post = \
_create_post_c2s(base_dir, nickname, domain, port,
@@ -1604,7 +1632,7 @@ def _create_post_base(base_dir: str,
post_object_type, summary,
in_reply_to_atom_uri, system_language,
conversation_id, low_bandwidth,
- content_license_url)
+ content_license_url, buy_url, translate)
_create_post_mentions(cc_url, new_post, to_recipients, tags)
@@ -1845,7 +1873,8 @@ def create_public_post(base_dir: str,
location: str, is_article: bool, system_language: str,
conversation_id: str, low_bandwidth: bool,
content_license_url: str,
- languages_understood: [], translate: {}) -> {}:
+ languages_understood: [], translate: {},
+ buy_url: str) -> {}:
"""Public post
"""
domain_full = get_full_domain(domain, port)
@@ -1879,7 +1908,7 @@ def create_public_post(base_dir: str,
event_status, ticket_url, system_language,
conversation_id, low_bandwidth,
content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
def _append_citations_to_blog_post(base_dir: str,
@@ -1924,7 +1953,8 @@ def create_blog_post(base_dir: str,
location: str, system_language: str,
conversation_id: str, low_bandwidth: bool,
content_license_url: str,
- languages_understood: [], translate: {}) -> {}:
+ languages_understood: [], translate: {},
+ buy_url: str) -> {}:
blog_json = \
create_public_post(base_dir,
nickname, domain, port, http_prefix,
@@ -1937,7 +1967,7 @@ def create_blog_post(base_dir: str,
event_date, event_time, event_end_time, location,
True, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
blog_json['object']['url'] = \
blog_json['object']['url'].replace('/@', '/users/')
_append_citations_to_blog_post(base_dir, nickname, domain, blog_json)
@@ -1953,7 +1983,8 @@ def create_news_post(base_dir: str,
subject: str, system_language: str,
conversation_id: str, low_bandwidth: bool,
content_license_url: str,
- languages_understood: [], translate: {}) -> {}:
+ languages_understood: [], translate: {},
+ buy_url: str) -> {}:
client_to_server = False
in_reply_to = None
in_reply_to_atom_uri = None
@@ -1974,7 +2005,7 @@ def create_news_post(base_dir: str,
event_date, event_time, event_end_time, location,
True, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
blog['object']['type'] = 'Article'
return blog
@@ -1995,6 +2026,7 @@ def create_question_post(base_dir: str,
"""
domain_full = get_full_domain(domain, port)
local_actor = local_actor_url(http_prefix, nickname, domain_full)
+ buy_url = ''
message_json = \
_create_post_base(base_dir, nickname, domain, port,
'https://www.w3.org/ns/activitystreams#Public',
@@ -2008,7 +2040,7 @@ def create_question_post(base_dir: str,
None, None, None,
None, None, None, None, None, system_language,
None, low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
message_json['object']['type'] = 'Question'
message_json['object']['oneOf'] = []
message_json['object']['votersCount'] = 0
@@ -2043,7 +2075,8 @@ def create_unlisted_post(base_dir: str,
location: str, system_language: str,
conversation_id: str, low_bandwidth: bool,
content_license_url: str,
- languages_understood: [], translate: {}) -> {}:
+ languages_understood: [], translate: {},
+ buy_url: str) -> {}:
"""Unlisted post. This has the #Public and followers links inverted.
"""
domain_full = get_full_domain(domain, port)
@@ -2063,7 +2096,7 @@ def create_unlisted_post(base_dir: str,
None, None, None, None, None, system_language,
conversation_id, low_bandwidth,
content_license_url, languages_understood,
- translate)
+ translate, buy_url)
def create_followers_only_post(base_dir: str,
@@ -2082,7 +2115,7 @@ def create_followers_only_post(base_dir: str,
conversation_id: str, low_bandwidth: bool,
content_license_url: str,
languages_understood: [],
- translate: {}) -> {}:
+ translate: {}, buy_url: str) -> {}:
"""Followers only post
"""
domain_full = get_full_domain(domain, port)
@@ -2100,7 +2133,7 @@ def create_followers_only_post(base_dir: str,
None, None, None, None, None, system_language,
conversation_id, low_bandwidth,
content_license_url, languages_understood,
- translate)
+ translate, buy_url)
def get_mentioned_people(base_dir: str, http_prefix: str,
@@ -2157,7 +2190,8 @@ def create_direct_message_post(base_dir: str,
conversation_id: str, low_bandwidth: bool,
content_license_url: str,
languages_understood: [],
- dm_is_chat: bool, translate: {}) -> {}:
+ dm_is_chat: bool, translate: {},
+ buy_url: str) -> {}:
"""Direct Message post
"""
content = resolve_petnames(base_dir, nickname, domain, content)
@@ -2183,7 +2217,7 @@ def create_direct_message_post(base_dir: str,
None, None, None, None, None, system_language,
conversation_id, low_bandwidth,
content_license_url, languages_understood,
- translate)
+ translate, buy_url)
# mentioned recipients go into To rather than Cc
message_json['to'] = message_json['object']['cc']
message_json['object']['to'] = message_json['to']
@@ -2269,11 +2303,11 @@ def create_report_post(base_dir: str,
post_to = moderators_list
post_cc = None
post_json_object = None
+ buy_url = ''
for to_url in post_to:
# who is this report going to?
to_nickname = to_url.split('/users/')[1]
handle = to_nickname + '@' + domain
-
post_json_object = \
_create_post_base(base_dir, nickname, domain, port,
to_url, post_cc,
@@ -2286,7 +2320,7 @@ def create_report_post(base_dir: str,
None, None, None,
None, None, None, None, None, system_language,
None, low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
if not post_json_object:
continue
@@ -2411,7 +2445,7 @@ def send_post(signing_priv_key_pem: str, project_version: str,
shared_items_federated_domains: [],
shared_item_federation_tokens: {},
low_bandwidth: bool, content_license_url: str,
- translate: {},
+ translate: {}, buy_url: str,
debug: bool = False, in_reply_to: str = None,
in_reply_to_atom_uri: str = None, subject: str = None) -> int:
"""Post to another inbox. Used by unit tests.
@@ -2479,7 +2513,7 @@ def send_post(signing_priv_key_pem: str, project_version: str,
None, None, None, None, None, system_language,
conversation_id, low_bandwidth,
content_license_url, languages_understood,
- translate)
+ translate, buy_url)
# get the senders private key
private_key_pem = get_person_key(nickname, domain, base_dir, 'private')
@@ -2576,7 +2610,7 @@ def send_post_via_server(signing_priv_key_pem: str, project_version: str,
low_bandwidth: bool,
content_license_url: str,
event_date: str, event_time: str, event_end_time: str,
- location: str, translate: {},
+ location: str, translate: {}, buy_url: str,
debug: bool = False,
in_reply_to: str = None,
in_reply_to_atom_uri: str = None,
@@ -2668,7 +2702,7 @@ def send_post_via_server(signing_priv_key_pem: str, project_version: str,
None, None, None, None, None, system_language,
conversation_id, low_bandwidth,
content_license_url, languages_understood,
- translate)
+ translate, buy_url)
auth_header = create_basic_auth_header(from_nickname, password)
diff --git a/schedule.py b/schedule.py
index 04960e032..5ac876958 100644
--- a/schedule.py
+++ b/schedule.py
@@ -147,7 +147,8 @@ def _update_post_schedule(base_dir: str, handle: str, httpd,
httpd.lists_enabled,
httpd.content_license_url,
httpd.dogwhistles,
- httpd.min_images_for_accounts):
+ httpd.min_images_for_accounts,
+ httpd.buy_sites):
index_lines.remove(line)
try:
os.remove(post_filename)
diff --git a/tests.py b/tests.py
index 994cfa108..40b2ef2c8 100644
--- a/tests.py
+++ b/tests.py
@@ -773,6 +773,7 @@ def create_server_alice(path: str, domain: str, port: int,
conversation_id = None
translate = {}
content_license_url = 'https://creativecommons.org/licenses/by-nc/4.0'
+ buy_url = ''
create_public_post(path, nickname, domain, port, http_prefix,
"No wise fish would go anywhere without a porpoise",
test_save_to_file,
@@ -787,7 +788,7 @@ def create_server_alice(path: str, domain: str, port: int,
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
create_public_post(path, nickname, domain, port, http_prefix,
"Curiouser and curiouser!",
test_save_to_file,
@@ -802,7 +803,7 @@ def create_server_alice(path: str, domain: str, port: int,
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
create_public_post(path, nickname, domain, port, http_prefix,
"In the gardens of memory, in the palace " +
"of dreams, that is where you and I shall meet",
@@ -818,7 +819,7 @@ def create_server_alice(path: str, domain: str, port: int,
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
regenerate_index_for_box(path, nickname, domain, 'outbox')
global TEST_SERVER_ALICE_RUNNING
TEST_SERVER_ALICE_RUNNING = True
@@ -937,6 +938,7 @@ def create_server_bob(path: str, domain: str, port: int,
conversation_id = None
content_license_url = 'https://creativecommons.org/licenses/by-nc/4.0'
translate = {}
+ buy_url = ''
create_public_post(path, nickname, domain, port, http_prefix,
"It's your life, live it your way.",
test_save_to_file,
@@ -951,7 +953,7 @@ def create_server_bob(path: str, domain: str, port: int,
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
create_public_post(path, nickname, domain, port, http_prefix,
"One of the things I've realised is that " +
"I am very simple",
@@ -967,7 +969,7 @@ def create_server_bob(path: str, domain: str, port: int,
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
create_public_post(path, nickname, domain, port, http_prefix,
"Quantum physics is a bit of a passion of mine",
test_save_to_file,
@@ -982,7 +984,7 @@ def create_server_bob(path: str, domain: str, port: int,
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
regenerate_index_for_box(path, nickname, domain, 'outbox')
global TEST_SERVER_BOB_RUNNING
TEST_SERVER_BOB_RUNNING = True
@@ -1321,6 +1323,7 @@ def test_post_message_between_servers(base_dir: str) -> None:
low_bandwidth = False
signing_priv_key_pem = None
translate = {}
+ buy_url = ''
send_result = \
send_post(signing_priv_key_pem, __version__,
session_alice, alice_dir, 'alice', alice_domain, alice_port,
@@ -1335,7 +1338,7 @@ def test_post_message_between_servers(base_dir: str) -> None:
languages_understood,
alice_shared_items_federated_domains,
alice_shared_item_federation_tokens, low_bandwidth,
- content_license_url, translate,
+ content_license_url, translate, buy_url,
in_reply_to, in_reply_to_atom_uri, subject)
print('send_result: ' + str(send_result))
@@ -1402,6 +1405,8 @@ def test_post_message_between_servers(base_dir: str) -> None:
assert 'यह एक परीक्षण है' in received_json['object']['content']
print('Check that message received from Alice contains an attachment')
assert received_json['object']['attachment']
+ if len(received_json['object']['attachment']) != 2:
+ pprint(received_json['object']['attachment'])
assert len(received_json['object']['attachment']) == 2
attached = received_json['object']['attachment'][0]
pprint(attached)
@@ -1693,6 +1698,7 @@ def test_follow_between_servers(base_dir: str) -> None:
low_bandwidth = False
signing_priv_key_pem = None
translate = {}
+ buy_url = ''
send_result = \
send_post(signing_priv_key_pem, __version__,
session_alice, alice_dir, 'alice', alice_domain, alice_port,
@@ -1705,7 +1711,7 @@ def test_follow_between_servers(base_dir: str) -> None:
languages_understood,
alice_shared_items_federated_domains,
alice_shared_item_federation_tokens, low_bandwidth,
- content_license_url, translate,
+ content_license_url, translate, buy_url,
in_reply_to, in_reply_to_atom_uri, subject)
print('send_result: ' + str(send_result))
@@ -2060,6 +2066,7 @@ def test_shared_items_federation(base_dir: str) -> None:
low_bandwidth = False
signing_priv_key_pem = None
translate = {}
+ buy_url = ''
send_result = \
send_post(signing_priv_key_pem, __version__,
session_alice, alice_dir, 'alice', alice_domain, alice_port,
@@ -2072,7 +2079,7 @@ def test_shared_items_federation(base_dir: str) -> None:
languages_understood,
alice_shared_items_federated_domains,
alice_shared_item_federation_tokens, low_bandwidth,
- content_license_url, translate, True,
+ content_license_url, translate, buy_url, True,
in_reply_to, in_reply_to_atom_uri, subject)
print('send_result: ' + str(send_result))
@@ -2490,6 +2497,7 @@ def test_group_follow(base_dir: str) -> None:
if os.path.isfile(os.path.join(outbox_path, name))])
translate = {}
+ buy_url = ''
send_result = \
send_post(signing_priv_key_pem, __version__,
session_alice, alice_dir, 'alice', alice_domain, alice_port,
@@ -2502,7 +2510,7 @@ def test_group_follow(base_dir: str) -> None:
languages_understood,
alice_shared_items_federated_domains,
alice_shared_item_federation_tokens, low_bandwidth,
- content_license_url, translate,
+ content_license_url, translate, buy_url,
in_reply_to, in_reply_to_atom_uri, subject)
print('send_result: ' + str(send_result))
@@ -2877,6 +2885,7 @@ def _test_create_person_account(base_dir: str):
"(yawn)\n\n...then it's not really independent.\n\n" + \
"Politicians will threaten to withdraw funding if you do " + \
"anything which challenges middle class sensibilities or incomes."
+ buy_url = ''
test_post_json = \
create_public_post(base_dir, nickname, domain, port, http_prefix,
content, save_to_file,
@@ -2889,7 +2898,7 @@ def _test_create_person_account(base_dir: str):
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
assert test_post_json
assert test_post_json.get('object')
assert test_post_json['object']['content']
@@ -2915,7 +2924,7 @@ def _test_create_person_account(base_dir: str):
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
assert test_post_json
assert test_post_json.get('object')
assert test_post_json['object']['content']
@@ -3116,6 +3125,7 @@ def test_client_to_server(base_dir: str):
event_end_time = '12:30'
location = "Kinshasa"
translate = {}
+ buy_url = ''
send_result = \
send_post_via_server(signing_priv_key_pem, __version__,
alice_dir, session_alice, 'alice', password,
@@ -3128,7 +3138,7 @@ def test_client_to_server(base_dir: str):
system_language, languages_understood,
low_bandwidth, content_license_url,
event_date, event_time, event_end_time, location,
- translate, True, None, None,
+ translate, buy_url, True, None, None,
conversation_id, None)
print('send_result: ' + str(send_result))
@@ -4728,6 +4738,7 @@ def _test_reply_to_public_post(base_dir: str) -> None:
low_bandwidth = True
content_license_url = 'https://creativecommons.org/licenses/by-nc/4.0'
translate = {}
+ buy_url = ''
reply = \
create_public_post(base_dir, nickname, domain, port, http_prefix,
content, save_to_file,
@@ -4740,7 +4751,7 @@ def _test_reply_to_public_post(base_dir: str) -> None:
test_event_end_time, test_location,
test_is_article, system_language, conversation_id,
low_bandwidth, content_license_url,
- languages_understood, translate)
+ languages_understood, translate, buy_url)
# print(str(reply))
expected_str = \
'