From cece15e00001508a902ef2f0b78f30cb321f1c3b Mon Sep 17 00:00:00 2001 From: Corentin Dautreme Date: Sat, 21 Oct 2023 18:44:42 +0200 Subject: [PATCH 1/3] WIP Add bluesky support --- common.py | 1 + lys_5minutes.py | 110 +++++++++++++----- .../test_5minutes_bluesky_posts_generation.py | 71 +++++++++++ test/test_5minutes_tweets_generation.py | 34 +++--- 4 files changed, 168 insertions(+), 48 deletions(-) create mode 100644 test/test_5minutes_bluesky_posts_generation.py diff --git a/common.py b/common.py index c0520df..a11ad57 100644 --- a/common.py +++ b/common.py @@ -69,6 +69,7 @@ CLOCK_EMOJI = "\U0001F553" TV_EMOJI = "\U0001F4FA" ALERT_EMOJI = "\U0001F6A8" +DOWN_ARROW_EMOJI = "\U00002B07\U0000FE0F" BLUESKY="bluesky" TWITTER="twitter" diff --git a/lys_5minutes.py b/lys_5minutes.py index 6a347ff..0c67fa0 100644 --- a/lys_5minutes.py +++ b/lys_5minutes.py @@ -7,42 +7,43 @@ except ImportError: pass -from common import DATETIME_CET_FORMAT, flag_emojis, ALERT_EMOJI, BLUESKY, TWITTER, get_watch_link_string +from common import DATETIME_CET_FORMAT, flag_emojis, ALERT_EMOJI, DOWN_ARROW_EMOJI, BLUESKY, TWITTER, get_watch_link_string from twitter_utils import create_tweepy_client, send_tweet +def generate_event_string(event, shorten_urls=False): + flag = (flag_emojis[event['country']] + " ") if event['country'] in flag_emojis else "" + watch_link_string = "" + try: + watch_links = event['watchLinks'] + # tweeting only links that can be watched live + for watch_link in list(filter(lambda wl: 'live' in wl and wl['live'], watch_links)): + if watch_link_string != "": + watch_link_string += " OR " + if "link" in watch_link: + watch_link_string += get_watch_link_string(watch_link, event['country'], shorten_urls) + except KeyError: + pass + if watch_link_string == "": + watch_link_string = "(no watch link found)" + else: + watch_link_string = "(" + watch_link_string + ")" + event_string = "\n{}{} - {} {}".format(flag, event['name'], event['stage'], watch_link_string) + return event_string + + def generate_twitter_event_strings(events): event_strings = [] for event in events: - flag = (flag_emojis[event['country']] + " ") if event['country'] in flag_emojis else "" - watch_link_string = "" - try: - watch_links = event['watchLinks'] - # tweeting only links that can be watched live - for watch_link in list(filter(lambda wl: 'live' in wl and wl['live'], watch_links)): - if watch_link_string != "": - watch_link_string += " OR " - if "link" in watch_link: - watch_link_string += watch_link['link'] + ((" (" + watch_link['comment'] + ")") if "comment" in watch_link and watch_link['comment'] != "" and watch_link['comment'] != "Recommended link" else "") - additional_comments = [] - if "geoblocked" in watch_link and watch_link['geoblocked']: - additional_comments.append("geoblocked") - if "accountRequired" in watch_link and watch_link['accountRequired']: - additional_comments.append("account required: https://lyseurovision.github.io/help.html#account-" + event['country']) - if len(additional_comments) > 0: - watch_link_string += " (" + ", ".join(additional_comments) + ")" - except KeyError: - pass - if watch_link_string == "": - watch_link_string = "(no watch link found)" - else: - watch_link_string = "(" + watch_link_string + ")" - event_string = "\n{}{} - {} {}".format(flag, event['name'], event['stage'], watch_link_string) - event_strings.append(event_string) + event_strings.append(generate_event_string(event)) return event_strings -def build_tweets(event_strings): +def generate_bluesky_event_string(event): + return generate_event_string(event, shorten_urls=True) + + +def build_twitter_posts(event_strings): tweets = [] tweet = ALERT_EMOJI + " 5 MINUTES REMINDER!" for string in event_strings: @@ -56,16 +57,63 @@ def build_tweets(event_strings): return tweets +def build_bluesky_posts(events): + post_header = ALERT_EMOJI + " 5 MINUTES REMINDER!" + posts = [] + event_idx = [] + is_thread=False + tmp_post = "" + post_events = [] + for idx, event in enumerate(events): + event_string = generate_bluesky_event_string(event) + # bluesky character limit = 300; leaving room for the header + if len(tmp_post+event_string) < 260: + # add the event string to the current post + tmp_post += "\n---------" + event_string + # flag the index of the event in the list as part of the current post + post_events.append(idx) + else: + # we're ready to save the first post + # add the header + post = post_header + # if we're here, we're about to create/continue a thread, because the next event doesn't fit in the current post + if not is_thread: + # if we haven't started a thread yet, this means this is the first post of the thread + post += " (thread " + DOWN_ARROW_EMOJI + ")" + else: + # otherwise, we're just adding another post to the thread + post += " (cont.)" + is_thread = True + # the post is complete, we save it and save the indices of the events that are part of it + post += tmp_post + posts.append(post) + event_idx.append(post_events) + # we reset the tmp post, and open it with the next event (the one we couldn't add to the previous post) + tmp_post = "\n---------" + event_string + post_events = [idx] + # if we're out of events but still have event strings we haven't saved to a post, we do it now + if len(tmp_post) > 0: + post = post_header + if is_thread: + post += " (cont.)" + post += tmp_post + posts.append(post) + event_idx.append(post_events) + + # finally, we return the complete list of posts, alongside the indices of the events that compose it + return (posts, event_idx) + + def generate_twitter_thread(events): event_strings = generate_twitter_event_strings(events) - return build_tweets(event_strings) + return build_twitter_posts(event_strings) def generate_bluesky_thread(events): # TODO identify which post of the thread includes which event (to add right facets in right post) - # TODO return a list of list, that includes for each post a list of the indexes of the links that are present in the post? - # ex: posts = ["Post0=link 0, link 1", "Post1=link 2"], links_idx = [[0, 1], [2]] - # => we can then iterate on each list of links_idx and generate the facets for each post individually + # TODO return a list of list, that includes for each post a list of the indexes of the events that are present in the post? + # ex: posts = ["Post0=event 0, event 1", "Post1=event 2"], event_idx = [[0, 1], [2]] + # => we can then iterate on each list of event_idx and generate the facets for each post individually return [] diff --git a/test/test_5minutes_bluesky_posts_generation.py b/test/test_5minutes_bluesky_posts_generation.py new file mode 100644 index 0000000..c419c9d --- /dev/null +++ b/test/test_5minutes_bluesky_posts_generation.py @@ -0,0 +1,71 @@ +import unittest + +from lys_5minutes import build_bluesky_posts + +class FiveMinutesBlueskyPostsGenerationTest(unittest.TestCase): + def test_when_events_fit_in_one_post_then_should_generate_one_post(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}] + (posts, event_idx) = build_bluesky_posts(events) + self.assertEqual(len(posts), 1) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se)") + self.assertEqual(len(event_idx), 1) + self.assertEqual(len(event_idx[0]), 1) + self.assertEqual(event_idx[0], [0]) + + + def test_when_events_do_not_fit_in_one_tweet_then_should_generate_multiple_tweets(self): + events = [ + {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}, + {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.no', 'comment': 'Recommended link', 'live': 1}]}, + {'country': 'Estonia', 'name': 'Eesti Laul', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.ee', 'comment': 'Recommended link', 'live': 1}]}, + {'country': 'Finland', 'name': 'Uuden Musiikin Kilpailu', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.fi', 'comment': 'Recommended link', 'live': 1}]}, + {'country': 'Serbia', 'name': 'Beovizija', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.rs', 'comment': 'Recommended link', 'live': 1}]} + ] + (posts, event_idx) = build_bluesky_posts(events) + self.assertEqual(len(posts), 2) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER! (thread \U00002B07\U0000FE0F)\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se)\n---------\n\U0001F1F3\U0001F1F4 Melodi Grand Prix - Final (somereallyreallyreallyreallylongurl.no)\n---------\n\U0001F1EA\U0001F1EA Eesti Laul - Final (somereallyreallyreallyreallylongurl.ee)") + self.assertEqual(posts[1], "\U0001F6A8 5 MINUTES REMINDER! (cont.)\n---------\n\U0001F1EB\U0001F1EE Uuden Musiikin Kilpailu - Final (somereallyreallyreallyreallylongurl.fi)\n---------\n\U0001F1F7\U0001F1F8 Beovizija - Final (somereallyreallyreallyreallylongurl.rs)") + self.assertEqual(len(event_idx), 2) + # the first post should include the links of the first 3 events + self.assertEqual(len(event_idx[0]), 3) + # (which are the events at index 0, 1 and 2 in the events list) + self.assertEqual(event_idx[0], [0, 1, 2]) + # the second and final post should include the links of the last 2 events + self.assertEqual(len(event_idx[1]), 2) + # (which are the events at index 3 and 4 in the events list) + self.assertEqual(event_idx[1], [3, 4]) + + + def test_when_very_complicated_events_do_not_fit_in_one_tweet_then_should_generate_multiple_tweets(self): + events = [ + {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Best link every made out there!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}, + {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.no', 'comment': 'Next-best link every made out there!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}, + {'country': 'Estonia', 'name': 'Eesti Laul', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.ee', 'comment': 'Not the best link, but not the worst either!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}, + {'country': 'Finland', 'name': 'Uuden Musiikin Kilpailu', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.fi', 'comment': 'That link is pretty damn good!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}, + {'country': 'Serbia', 'name': 'Beovizija', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.rs', 'comment': 'Super mega ultra HD 16K very good link click it!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]} + ] + (posts, event_idx) = build_bluesky_posts(events) + + + def test_when_one_event_with_multiple_links_should_generate_tweet_with_all_links_and_the_valid_comments(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1}]}] + (posts, event_idx) = build_bluesky_posts(events) + + + def test_when_one_event_with_multiple_links_should_generate_tweet_with_live_links_only(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 0}]}] + (posts, event_idx) = build_bluesky_posts(events) + + + def test_when_one_event_contains_geoblocked_watch_links_should_add_geoblocked_comment_to_tweet(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'geoblocked': 1}]}] + (posts, event_idx) = build_bluesky_posts(events) + + + def test_when_one_event_contains_watch_link_that_requires_an_account_should_add_comment_to_tweet(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1}]}] + (posts, event_idx) = build_bluesky_posts(events) + + def test_when_one_event_contains_watch_link_that_requires_an_account_and_is_geoblocked_should_add_comment_to_tweet(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}] + (posts, event_idx) = build_bluesky_posts(events) diff --git a/test/test_5minutes_tweets_generation.py b/test/test_5minutes_tweets_generation.py index 99376d3..e41434a 100644 --- a/test/test_5minutes_tweets_generation.py +++ b/test/test_5minutes_tweets_generation.py @@ -1,12 +1,12 @@ import unittest -from lys_5minutes import generate_twitter_event_strings, build_tweets +from lys_5minutes import generate_twitter_event_strings, build_twitter_posts class FiveMinutesTweetsGenerationTest(unittest.TestCase): def test_when_events_fit_in_one_tweet_then_should_generate_one_tweet(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 1) self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se)") @@ -20,7 +20,7 @@ def test_when_events_do_not_fit_in_one_tweet_then_should_generate_multiple_tweet {'country': 'Serbia', 'name': 'Beovizija', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.rs', 'comment': 'Recommended link', 'live': 1}]} ] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 2) self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se)\n---------\n\U0001F1F3\U0001F1F4 Melodi Grand Prix - Final (https://somereallyreallyreallyreallylongurl.no)\n---------\n\U0001F1EA\U0001F1EA Eesti Laul - Final (https://somereallyreallyreallyreallylongurl.ee)") self.assertEqual(tweets[1], "\U0001F1EB\U0001F1EE Uuden Musiikin Kilpailu - Final (https://somereallyreallyreallyreallylongurl.fi)\n---------\n\U0001F1F7\U0001F1F8 Beovizija - Final (https://somereallyreallyreallyreallylongurl.rs)") @@ -34,45 +34,45 @@ def test_when_very_complicated_events_do_not_fit_in_one_tweet_then_should_genera {'country': 'Serbia', 'name': 'Beovizija', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.rs', 'comment': 'Super mega ultra HD 16K very good link click it!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]} ] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 5) - self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se (Best link every made out there!) (geoblocked, account required: https://lyseurovision.github.io/help.html#account-Sweden))") - self.assertEqual(tweets[1], "\U0001F1F3\U0001F1F4 Melodi Grand Prix - Final (https://somereallyreallyreallyreallylongurl.no (Next-best link every made out there!) (geoblocked, account required: https://lyseurovision.github.io/help.html#account-Norway))") - self.assertEqual(tweets[2], "\U0001F1EA\U0001F1EA Eesti Laul - Final (https://somereallyreallyreallyreallylongurl.ee (Not the best link, but not the worst either!) (geoblocked, account required: https://lyseurovision.github.io/help.html#account-Estonia))") - self.assertEqual(tweets[3], "\U0001F1EB\U0001F1EE Uuden Musiikin Kilpailu - Final (https://somereallyreallyreallyreallylongurl.fi (That link is pretty damn good!) (geoblocked, account required: https://lyseurovision.github.io/help.html#account-Finland))") - self.assertEqual(tweets[4], "\U0001F1F7\U0001F1F8 Beovizija - Final (https://somereallyreallyreallyreallylongurl.rs (Super mega ultra HD 16K very good link click it!) (geoblocked, account required: https://lyseurovision.github.io/help.html#account-Serbia))") + self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se (Best link every made out there!)(geoblocked, account required: see https://lyseurovision.github.io/help.html#account-Sweden))") + self.assertEqual(tweets[1], "\U0001F1F3\U0001F1F4 Melodi Grand Prix - Final (https://somereallyreallyreallyreallylongurl.no (Next-best link every made out there!)(geoblocked, account required: see https://lyseurovision.github.io/help.html#account-Norway))") + self.assertEqual(tweets[2], "\U0001F1EA\U0001F1EA Eesti Laul - Final (https://somereallyreallyreallyreallylongurl.ee (Not the best link, but not the worst either!)(geoblocked, account required: see https://lyseurovision.github.io/help.html#account-Estonia))") + self.assertEqual(tweets[3], "\U0001F1EB\U0001F1EE Uuden Musiikin Kilpailu - Final (https://somereallyreallyreallyreallylongurl.fi (That link is pretty damn good!)(geoblocked, account required: see https://lyseurovision.github.io/help.html#account-Finland))") + self.assertEqual(tweets[4], "\U0001F1F7\U0001F1F8 Beovizija - Final (https://somereallyreallyreallyreallylongurl.rs (Super mega ultra HD 16K very good link click it!)(geoblocked, account required: see https://lyseurovision.github.io/help.html#account-Serbia))") def test_when_one_event_with_multiple_links_should_generate_tweet_with_all_links_and_the_valid_comments(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1}]}] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 1) self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se OR https://svtplay.se (Another link))") def test_when_one_event_with_multiple_links_should_generate_tweet_with_live_links_only(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 0}]}] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 1) self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se)") def test_when_one_event_contains_geoblocked_watch_links_should_add_geoblocked_comment_to_tweet(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'geoblocked': 1}]}] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 1) - self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se OR https://svtplay.se (Another link) (geoblocked))") + self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se OR https://svtplay.se (Another link)(geoblocked))") def test_when_one_event_contains_watch_link_that_requires_an_account_should_add_comment_to_tweet(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1}]}] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 1) - self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se OR https://svtplay.se (Another link) (account required: https://lyseurovision.github.io/help.html#account-Sweden))") + self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se OR https://svtplay.se (Another link)(account required: see https://lyseurovision.github.io/help.html#account-Sweden))") def test_when_one_event_contains_watch_link_that_requires_an_account_and_is_geoblocked_should_add_comment_to_tweet(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}] event_strings = generate_twitter_event_strings(events) - tweets = build_tweets(event_strings) + tweets = build_twitter_posts(event_strings) self.assertEqual(len(tweets), 1) - self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se OR https://svtplay.se (Another link) (geoblocked, account required: https://lyseurovision.github.io/help.html#account-Sweden))") + self.assertEqual(tweets[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (https://svtplay.se OR https://svtplay.se (Another link)(geoblocked, account required: see https://lyseurovision.github.io/help.html#account-Sweden))") From 4d60a0170c5d8749fd50c5a73eb3e85ad247994d Mon Sep 17 00:00:00 2001 From: Corentin Dautreme Date: Sat, 21 Oct 2023 21:57:31 +0200 Subject: [PATCH 2/3] WIP Add Bluesky support --- .github/workflows/publish_lambda.yml | 8 +- bluesky_utils.py | 152 ++++++++++++++++++ common.py | 9 ++ lys_5minutes.py | 45 ++++-- lys_daily.py | 77 ++++++--- requirements.txt | 3 +- .../test_5minutes_bluesky_posts_generation.py | 104 ++++++++---- test/test_daily_tweets_generation.py | 55 +++---- 8 files changed, 355 insertions(+), 98 deletions(-) create mode 100644 bluesky_utils.py diff --git a/.github/workflows/publish_lambda.yml b/.github/workflows/publish_lambda.yml index 6e8eef8..7eee060 100644 --- a/.github/workflows/publish_lambda.yml +++ b/.github/workflows/publish_lambda.yml @@ -32,10 +32,10 @@ jobs: - name: Package (daily, weekly, 5 minutes & dump) if: success() run: | - zip -ur lys_daily.zip lys_daily.py common.py twitter_utils.py - zip -ur lys_weekly.zip lys_weekly.py common.py twitter_utils.py - zip -ur lys_5minutes.zip lys_5minutes.py common.py twitter_utils.py - zip -ur lys_dump.zip lys_dump.py common.py twitter_utils.py + zip -ur lys_daily.zip lys_daily.py common.py twitter_utils.py bluesky_utils.py + zip -ur lys_weekly.zip lys_weekly.py common.py twitter_utils.py bluesky_utils.py + zip -ur lys_5minutes.zip lys_5minutes.py common.py twitter_utils.py bluesky_utils.py + zip -ur lys_dump.zip lys_dump.py common.py twitter_utils.py bluesky_utils.py cd lib zip -ur ../lys_daily.zip * zip -ur ../lys_weekly.zip * diff --git a/bluesky_utils.py b/bluesky_utils.py new file mode 100644 index 0000000..d1bc07e --- /dev/null +++ b/bluesky_utils.py @@ -0,0 +1,152 @@ +import requests +import os +import datetime + +from bs4 import BeautifulSoup +from common import get_short_url + + +def get_session(): + BLUESKY_ACCOUNT_HANDLE=os.environ['BLUESKY_ACCOUNT_HANDLE'] + BLUESKY_ACCOUNT_APP_PASSWORD=os.environ['BLUESKY_ACCOUNT_APP_PASSWORD'] + + # json with accessJwt and refreshJwt + response = requests.post( + "https://bsky.social/xrpc/com.atproto.server.createSession", + json={"identifier": BLUESKY_ACCOUNT_HANDLE, "password": BLUESKY_ACCOUNT_APP_PASSWORD}, + ) + response.raise_for_status() + return response.json() + + +def get_timestamp(): + return datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z") + + +def parse_url_links(post_body, link_descriptions): + spans = [] + last_appearance_of = {} + for ld in link_descriptions: + previous_appearance = (last_appearance_of.get(ld['text']) + 1) if ld['text'] in last_appearance_of else 0 + start = post_body.find(ld['text'], previous_appearance) + # keep track of the last appearance of a link text to handle posts where a short link appears multiple times + last_appearance_of[ld['text']] = start + # we have to "count in utf-8" to make sure emojis are counted for the right amount of characters + start = len(post_body[:start].encode("utf-8")) + end = start + len(ld['text'].encode("utf-8")) + spans.append({ + "start": start, + "end": end, + "url": ld['url'], + }) + return spans + + +# stolen from https://atproto.com/blog/create-post +def get_facets(link_spans, menion_spans=[]): + facets = [] + for m in menion_spans: + resp = requests.get( + "https://bsky.social/xrpc/com.atproto.identity.resolveHandle", + params={"handle": m["handle"]}, + ) + # If the handle can't be resolved, just skip it! + # It will be rendered as text in the post instead of a link + if resp.status_code == 400: + continue + did = resp.json()["did"] + facets.append({ + "index": { + "byteStart": m["start"], + "byteEnd": m["end"], + }, + "features": [{"$type": "app.bsky.richtext.facet#mention", "did": did}], + }) + for u in link_spans: + facets.append({ + "index": { + "byteStart": u["start"], + "byteEnd": u["end"], + }, + "features": [ + { + "$type": "app.bsky.richtext.facet#link", + "uri": u["url"], + } + ], + }) + return facets + + +def get_facets_for_event_links_in_string(events, string): + link_descriptions = [] + + for event in events: + # extract links to insert in the post alongside their associated string + for link in event['watchLinks']: + link_string = get_short_url(link['link']) + link_descriptions.append({'text': link_string, 'url': link['link']}) + if 'accountRequired' in link and link['accountRequired']: + account_help_link = "https://lyseurovision.github.io/help.html#account-" + event['country'] + link_descriptions.append({'text': get_short_url(account_help_link), 'url': account_help_link}) + + spans = parse_url_links(string, link_descriptions) + facets = get_facets(spans) + return facets + + +def generate_url_card(url): + # the required fields for every embed card + card = { + "uri": url, + "title": "", + "description": "", + } + + # fetch the HTML + resp = requests.get(url) + resp.raise_for_status() + soup = BeautifulSoup(resp.text, "html.parser") + + # parse out the "og:title" and "og:description" HTML meta tags + title_tag = soup.find("meta", property="og:title") + if title_tag: + card["title"] = title_tag["content"] + description_tag = soup.find("meta", property="og:description") + if description_tag: + card["description"] = description_tag["content"] + + return { + "$type": "app.bsky.embed.external", + "external": card, + } + + +def generate_post(post_string, facets=[], include_card=False, url_for_card=None): + post = { + "$type": "app.bsky.feed.post", + "text": post_string, + "facets": facets, + "createdAt": get_timestamp(), + "langs": ["en-US"] + } + + if include_card: + # generate social card for the recommended link + post["embed"] = generate_url_card(url_for_card) + + return post + + +def publish_post(session, post): + resp = requests.post( + "https://bsky.social/xrpc/com.atproto.repo.createRecord", + headers={"Authorization": "Bearer " + session["accessJwt"]}, + json={ + "repo": session["did"], + "collection": "app.bsky.feed.post", + "record": post + } + ) + resp.raise_for_status() + return resp.json() diff --git a/common.py b/common.py index a11ad57..9d7a84f 100644 --- a/common.py +++ b/common.py @@ -110,6 +110,15 @@ def get_watch_link_string(watch_link, country, shorten_urls=False): return watch_link_string +def get_first_watch_link(event, live=True): + links = event['watchLinks'] + if live: + links = list(filter(lambda l: l['live'], links)) + if len(links) == 0: + return None + return links[0]['link'] + + def get_current_season_range_for_date(date): if date.month > 8: season_start = datetime.datetime(date.year, 9, 1, 0, 0, 0) diff --git a/lys_5minutes.py b/lys_5minutes.py index 0c67fa0..97740d0 100644 --- a/lys_5minutes.py +++ b/lys_5minutes.py @@ -7,8 +7,9 @@ except ImportError: pass -from common import DATETIME_CET_FORMAT, flag_emojis, ALERT_EMOJI, DOWN_ARROW_EMOJI, BLUESKY, TWITTER, get_watch_link_string +from common import DATETIME_CET_FORMAT, flag_emojis, ALERT_EMOJI, DOWN_ARROW_EMOJI, BLUESKY, TWITTER, get_watch_link_string, get_short_url from twitter_utils import create_tweepy_client, send_tweet +from bluesky_utils import get_session, get_facets_for_event_links_in_string, generate_post, publish_post def generate_event_string(event, shorten_urls=False): @@ -60,7 +61,7 @@ def build_twitter_posts(event_strings): def build_bluesky_posts(events): post_header = ALERT_EMOJI + " 5 MINUTES REMINDER!" posts = [] - event_idx = [] + posts_events = [] is_thread=False tmp_post = "" post_events = [] @@ -71,7 +72,7 @@ def build_bluesky_posts(events): # add the event string to the current post tmp_post += "\n---------" + event_string # flag the index of the event in the list as part of the current post - post_events.append(idx) + post_events.append(event) else: # we're ready to save the first post # add the header @@ -87,10 +88,10 @@ def build_bluesky_posts(events): # the post is complete, we save it and save the indices of the events that are part of it post += tmp_post posts.append(post) - event_idx.append(post_events) + posts_events.append(post_events) # we reset the tmp post, and open it with the next event (the one we couldn't add to the previous post) tmp_post = "\n---------" + event_string - post_events = [idx] + post_events = [event] # if we're out of events but still have event strings we haven't saved to a post, we do it now if len(tmp_post) > 0: post = post_header @@ -98,10 +99,10 @@ def build_bluesky_posts(events): post += " (cont.)" post += tmp_post posts.append(post) - event_idx.append(post_events) + posts_events.append(post_events) # finally, we return the complete list of posts, alongside the indices of the events that compose it - return (posts, event_idx) + return (posts, posts_events) def generate_twitter_thread(events): @@ -110,11 +111,15 @@ def generate_twitter_thread(events): def generate_bluesky_thread(events): - # TODO identify which post of the thread includes which event (to add right facets in right post) - # TODO return a list of list, that includes for each post a list of the indexes of the events that are present in the post? - # ex: posts = ["Post0=event 0, event 1", "Post1=event 2"], event_idx = [[0, 1], [2]] - # => we can then iterate on each list of event_idx and generate the facets for each post individually - return [] + (post_bodies, post_events) = build_bluesky_posts(events) + posts = [] + for idx, body in enumerate(post_bodies): + # get the events included in the post + events = post_events[idx] + # extract link facets from post + facets = get_facets_for_event_links_in_string(events, body) + posts.append(generate_post(body, facets, include_card=False)) + return posts def generate_thread(events, target): @@ -153,6 +158,22 @@ def post_to_twitter(tweets, is_test=True): def post_to_bluesky(posts, is_test=True): output = [] + session = get_session() + + # publish the first post of the thread + if not is_test: + parent = root = publish_post(session, posts[0]) + output.append(posts[0]['text']) + + for post in posts[1:]: + if not is_test: + post['reply'] = { + "root": root, + "parent": parent + } + parent = publish_post(session, post) + output.append(post['text']) + return output diff --git a/lys_daily.py b/lys_daily.py index 89285b3..fc50d43 100644 --- a/lys_daily.py +++ b/lys_daily.py @@ -9,12 +9,13 @@ pass from tweepy.errors import TweepyException, HTTPException -from common import DATETIME_CET_FORMAT, flag_emojis, CASSETTE_EMOJI, TROPHY_EMOJI, CLOCK_EMOJI, TV_EMOJI, BLUESKY, TWITTER +from common import DATETIME_CET_FORMAT, flag_emojis, CASSETTE_EMOJI, TROPHY_EMOJI, CLOCK_EMOJI, TV_EMOJI, DOWN_ARROW_EMOJI, BLUESKY, TWITTER, get_watch_link_string, get_first_watch_link from twitter_utils import create_tweepy_client, send_tweet +from bluesky_utils import get_session, get_facets_for_event_links_in_string, generate_post, publish_post GENERIC_EVENT_STRING = "{}\n---------\n" + CASSETTE_EMOJI + " {}\n" + TROPHY_EMOJI + " {}\n" + CLOCK_EMOJI + " {} CET\n---------\n" + TV_EMOJI + " {}" -def generate_event_string(event, twitter_post): +def generate_event_string(event, post, shorten_urls=False): time = datetime.datetime.strptime(event['dateTimeCet'], DATETIME_CET_FORMAT).strftime("%H:%M") watch_link_string = "" try: @@ -24,14 +25,7 @@ def generate_event_string(event, twitter_post): if watch_link_string != "": watch_link_string += " OR " if "link" in watch_link: - watch_link_string += watch_link['link'] + ((" (" + watch_link['comment'] + ")") if "comment" in watch_link and watch_link['comment'] != "" and watch_link['comment'] != "Recommended link" else "") - additional_comments = [] - if "geoblocked" in watch_link and watch_link['geoblocked']: - additional_comments.append("geoblocked") - if "accountRequired" in watch_link and watch_link['accountRequired']: - additional_comments.append("account required: https://lyseurovision.github.io/help.html#account-" + event['country']) - if len(additional_comments) > 0: - watch_link_string += " (" + ", ".join(additional_comments) + ")" + watch_link_string += get_watch_link_string(watch_link, event['country'], shorten_urls) watch_link_string += "." except KeyError: pass @@ -42,40 +36,55 @@ def generate_event_string(event, twitter_post): country = event['country'].upper() else: country = flag_emojis[event['country']] + " " + event['country'].upper() - return twitter_post + GENERIC_EVENT_STRING.format(country, event['name'], event['stage'], time, watch_link_string) + return post + GENERIC_EVENT_STRING.format(country, event['name'], event['stage'], time, watch_link_string) -def generate_daily_tweet_thread(events, is_morning): - twitter_post = "" +def generate_daily_thread_posts(events, is_morning, shorten_urls=False): + post = "" if is_morning: - twitter_post = "TODAY | " + post = "TODAY | " else: - twitter_post = "TONIGHT | " + post = "TONIGHT | " multi_parter_regex = re.compile(r".*\(part [0-9]+\)") event_count = len(list(filter(lambda e: re.match(multi_parter_regex, e['stage']) is None, events))) if len(events) == 1: event = events[0] - twitter_post = generate_event_string(event, twitter_post) - return [twitter_post] + post = generate_event_string(event, post, shorten_urls) + return [post] else: - tweets = [] - tweets.append(twitter_post + str(event_count) + " selection show{} across Europe{}!".format( + posts = [] + posts.append((post + str(event_count) + " selection show{} across Europe{}! (thread " + DOWN_ARROW_EMOJI + ")").format( "s" if event_count > 1 else "", " and Australia" if any("Australia" == e['country'] for e in events) else "") ) - for event in sorted(events, key=lambda e: (e['dateTimeCet'], e['country'])): - event_string = generate_event_string(event, twitter_post) - tweets.append(event_string) + for event in events: + event_string = generate_event_string(event, post, shorten_urls) + posts.append(event_string) - return tweets + return posts + + +def generate_daily_twitter_thread(events, is_morning): + return generate_daily_thread_posts(events, is_morning) def generate_daily_bluesky_thread(events, is_morning): - return [] + post_bodies = generate_daily_thread_posts(events, is_morning, shorten_urls=True) + posts = [] + # this will either be the only post (if only one event) or the introduction post ("X events across Europe!") + posts.append(generate_post(post_bodies[0])) + if len(post_bodies) > 1: + for idx, body in enumerate(post_bodies[1:]): + event = events[idx] + # extract link facets from post + facets = get_facets_for_event_links_in_string([event], body) + first_link = get_first_watch_link(event) + posts.append(generate_post(body, facets, include_card=first_link is not None, url_for_card=first_link)) + return posts def post_tweet(client, tweet, reply_tweet_id): @@ -104,7 +113,7 @@ def send_errors_via_dm(errors, total_tweets, failed_tweets): def generate_thread(events, is_morning, target): posts = [] if target == TWITTER: - posts = generate_daily_tweet_thread(events, is_morning) + posts = generate_daily_thread_posts(events, is_morning) elif target == BLUESKY: posts = generate_daily_bluesky_thread(events, is_morning) else: @@ -150,6 +159,22 @@ def post_to_twitter(tweets, is_test=True): def post_to_bluesky(posts, is_test=True): output = [] + session = get_session() + + # publish the first post of the thread + if not is_test: + parent = root = publish_post(session, posts[0]) + output.append(posts[0]['text']) + + for post in posts[1:]: + if not is_test: + post['reply'] = { + "root": root, + "parent": parent + } + parent = publish_post(session, post) + output.append(post['text']) + return output @@ -187,7 +212,7 @@ def main(event, context): posts = [] try: - posts = generate_thread(events, is_morning=(today.hour < 12), target=target) + posts = generate_thread(sorted(events, key=lambda e: (e['dateTimeCet'], e['country'])), is_morning=(today.hour < 12), target=target) except ValueError as e: output = ["Error: Unable to generate post(s) from events - " + str(e)] return output diff --git a/requirements.txt b/requirements.txt index b719554..fd6bf0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ tweepy==4.14.0 -requests==2.28.2 \ No newline at end of file +requests==2.28.2 +beautifulsoup4==4.9.0 \ No newline at end of file diff --git a/test/test_5minutes_bluesky_posts_generation.py b/test/test_5minutes_bluesky_posts_generation.py index c419c9d..9d4ab8b 100644 --- a/test/test_5minutes_bluesky_posts_generation.py +++ b/test/test_5minutes_bluesky_posts_generation.py @@ -5,15 +5,15 @@ class FiveMinutesBlueskyPostsGenerationTest(unittest.TestCase): def test_when_events_fit_in_one_post_then_should_generate_one_post(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}] - (posts, event_idx) = build_bluesky_posts(events) + (posts, posts_events) = build_bluesky_posts(events) self.assertEqual(len(posts), 1) self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se)") - self.assertEqual(len(event_idx), 1) - self.assertEqual(len(event_idx[0]), 1) - self.assertEqual(event_idx[0], [0]) + self.assertEqual(len(posts_events), 1) + self.assertEqual(len(posts_events[0]), 1) + self.assertEqual(posts_events[0][0], events[0]) - def test_when_events_do_not_fit_in_one_tweet_then_should_generate_multiple_tweets(self): + def test_when_events_do_not_fit_in_one_post_then_should_generate_multiple_posts(self): events = [ {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.no', 'comment': 'Recommended link', 'live': 1}]}, @@ -21,22 +21,25 @@ def test_when_events_do_not_fit_in_one_tweet_then_should_generate_multiple_tweet {'country': 'Finland', 'name': 'Uuden Musiikin Kilpailu', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.fi', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Serbia', 'name': 'Beovizija', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.rs', 'comment': 'Recommended link', 'live': 1}]} ] - (posts, event_idx) = build_bluesky_posts(events) + (posts, posts_events) = build_bluesky_posts(events) self.assertEqual(len(posts), 2) self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER! (thread \U00002B07\U0000FE0F)\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se)\n---------\n\U0001F1F3\U0001F1F4 Melodi Grand Prix - Final (somereallyreallyreallyreallylongurl.no)\n---------\n\U0001F1EA\U0001F1EA Eesti Laul - Final (somereallyreallyreallyreallylongurl.ee)") self.assertEqual(posts[1], "\U0001F6A8 5 MINUTES REMINDER! (cont.)\n---------\n\U0001F1EB\U0001F1EE Uuden Musiikin Kilpailu - Final (somereallyreallyreallyreallylongurl.fi)\n---------\n\U0001F1F7\U0001F1F8 Beovizija - Final (somereallyreallyreallyreallylongurl.rs)") - self.assertEqual(len(event_idx), 2) + self.assertEqual(len(posts_events), 2) # the first post should include the links of the first 3 events - self.assertEqual(len(event_idx[0]), 3) + self.assertEqual(len(posts_events[0]), 3) # (which are the events at index 0, 1 and 2 in the events list) - self.assertEqual(event_idx[0], [0, 1, 2]) + self.assertEqual(posts_events[0][0], events[0]) + self.assertEqual(posts_events[0][1], events[1]) + self.assertEqual(posts_events[0][2], events[2]) # the second and final post should include the links of the last 2 events - self.assertEqual(len(event_idx[1]), 2) + self.assertEqual(len(posts_events[1]), 2) # (which are the events at index 3 and 4 in the events list) - self.assertEqual(event_idx[1], [3, 4]) + self.assertEqual(posts_events[1][0], events[3]) + self.assertEqual(posts_events[1][1], events[4]) - def test_when_very_complicated_events_do_not_fit_in_one_tweet_then_should_generate_multiple_tweets(self): + def test_when_very_complicated_events_do_not_fit_in_one_post_then_should_generate_multiple_posts(self): events = [ {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Best link every made out there!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}, {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.no', 'comment': 'Next-best link every made out there!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}, @@ -44,28 +47,73 @@ def test_when_very_complicated_events_do_not_fit_in_one_tweet_then_should_genera {'country': 'Finland', 'name': 'Uuden Musiikin Kilpailu', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.fi', 'comment': 'That link is pretty damn good!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}, {'country': 'Serbia', 'name': 'Beovizija', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://somereallyreallyreallyreallylongurl.rs', 'comment': 'Super mega ultra HD 16K very good link click it!', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]} ] - (posts, event_idx) = build_bluesky_posts(events) + (posts, posts_events) = build_bluesky_posts(events) + self.assertEqual(len(posts), 5) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER! (thread \U00002B07\U0000FE0F)\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se (Best link every made out there!)(geoblocked, account required: see lyseurovision.github.io))") + self.assertEqual(posts[1], "\U0001F6A8 5 MINUTES REMINDER! (cont.)\n---------\n\U0001F1F3\U0001F1F4 Melodi Grand Prix - Final (somereallyreallyreallyreallylongurl.no (Next-best link every made out there!)(geoblocked, account required: see lyseurovision.github.io))") + self.assertEqual(posts[2], "\U0001F6A8 5 MINUTES REMINDER! (cont.)\n---------\n\U0001F1EA\U0001F1EA Eesti Laul - Final (somereallyreallyreallyreallylongurl.ee (Not the best link, but not the worst either!)(geoblocked, account required: see lyseurovision.github.io))") + self.assertEqual(posts[3], "\U0001F6A8 5 MINUTES REMINDER! (cont.)\n---------\n\U0001F1EB\U0001F1EE Uuden Musiikin Kilpailu - Final (somereallyreallyreallyreallylongurl.fi (That link is pretty damn good!)(geoblocked, account required: see lyseurovision.github.io))") + self.assertEqual(posts[4], "\U0001F6A8 5 MINUTES REMINDER! (cont.)\n---------\n\U0001F1F7\U0001F1F8 Beovizija - Final (somereallyreallyreallyreallylongurl.rs (Super mega ultra HD 16K very good link click it!)(geoblocked, account required: see lyseurovision.github.io))") + self.assertEqual(len(posts_events), 5) + self.assertEqual(len(posts_events[0]), 1) + self.assertEqual(len(posts_events[1]), 1) + self.assertEqual(len(posts_events[2]), 1) + self.assertEqual(len(posts_events[3]), 1) + self.assertEqual(len(posts_events[4]), 1) + self.assertEqual(posts_events[0][0], events[0]) + self.assertEqual(posts_events[1][0], events[1]) + self.assertEqual(posts_events[2][0], events[2]) + self.assertEqual(posts_events[3][0], events[3]) + self.assertEqual(posts_events[4][0], events[4]) - def test_when_one_event_with_multiple_links_should_generate_tweet_with_all_links_and_the_valid_comments(self): - events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1}]}] - (posts, event_idx) = build_bluesky_posts(events) + def test_when_one_event_with_multiple_links_should_generate_post_with_all_links_and_the_valid_comments(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'svtplay.se', 'comment': 'Another link', 'live': 1}]}] + (posts, posts_events) = build_bluesky_posts(events) + self.assertEqual(len(posts), 1) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se OR svtplay.se (Another link))") + self.assertEqual(len(posts_events), 1) + self.assertEqual(len(posts_events[0]), 1) + self.assertEqual(posts_events[0][0], events[0]) + + + def test_when_one_event_with_multiple_links_should_generate_post_with_live_links_only(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'svtplay.se', 'comment': 'Another link', 'live': 0}]}] + (posts, posts_events) = build_bluesky_posts(events) + self.assertEqual(len(posts), 1) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se)") + self.assertEqual(len(posts_events), 1) + self.assertEqual(len(posts_events[0]), 1) + self.assertEqual(posts_events[0][0], events[0]) - def test_when_one_event_with_multiple_links_should_generate_tweet_with_live_links_only(self): - events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 0}]}] - (posts, event_idx) = build_bluesky_posts(events) + def test_when_one_event_contains_geoblocked_watch_links_should_add_geoblocked_comment_to_post(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'svtplay.se', 'comment': 'Another link', 'live': 1, 'geoblocked': 1}]}] + (posts, posts_events) = build_bluesky_posts(events) + self.assertEqual(len(posts), 1) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se OR svtplay.se (Another link)(geoblocked))") + self.assertEqual(len(posts_events), 1) + self.assertEqual(len(posts_events[0]), 1) + self.assertEqual(posts_events[0][0], events[0]) - def test_when_one_event_contains_geoblocked_watch_links_should_add_geoblocked_comment_to_tweet(self): - events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'geoblocked': 1}]}] - (posts, event_idx) = build_bluesky_posts(events) + def test_when_one_event_contains_watch_link_that_requires_an_account_should_add_comment_to_post(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1}]}] + (posts, posts_events) = build_bluesky_posts(events) + self.assertEqual(len(posts), 1) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se OR svtplay.se (Another link)(account required: see lyseurovision.github.io))") + self.assertEqual(len(posts_events), 1) + self.assertEqual(len(posts_events[0]), 1) + self.assertEqual(posts_events[0][0], events[0]) - def test_when_one_event_contains_watch_link_that_requires_an_account_should_add_comment_to_tweet(self): - events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1}]}] - (posts, event_idx) = build_bluesky_posts(events) + def test_when_one_event_contains_watch_link_that_requires_an_account_and_is_geoblocked_should_add_comment_to_post(self): + events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}] + (posts, posts_events) = build_bluesky_posts(events) + self.assertEqual(len(posts), 1) + self.assertEqual(posts[0], "\U0001F6A8 5 MINUTES REMINDER!\n---------\n\U0001F1F8\U0001F1EA Melodifestivalen - Final (svtplay.se OR svtplay.se (Another link)(geoblocked, account required: see lyseurovision.github.io))") + self.assertEqual(len(posts_events), 1) + self.assertEqual(len(posts_events[0]), 1) + self.assertEqual(posts_events[0][0], events[0]) - def test_when_one_event_contains_watch_link_that_requires_an_account_and_is_geoblocked_should_add_comment_to_tweet(self): - events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://svtplay.se', 'comment': 'Another link', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]}] - (posts, event_idx) = build_bluesky_posts(events) + # TODO add tests on the post object (facets, etc.) diff --git a/test/test_daily_tweets_generation.py b/test/test_daily_tweets_generation.py index 4574c42..6bb4263 100644 --- a/test/test_daily_tweets_generation.py +++ b/test/test_daily_tweets_generation.py @@ -1,11 +1,12 @@ import unittest -from lys_daily import generate_daily_tweet_thread +from lys_daily import generate_daily_thread_posts +# TODO merge with the daily_post test class class DailyTweetsGenerationTest(unittest.TestCase): def test_when_one_event_then_should_generate_one_tweet(self): events = [{'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}] - tweets = generate_daily_tweet_thread(events, is_morning=False) + tweets = generate_daily_thread_posts(events, is_morning=False) self.assertEqual(len(tweets), 1) self.assertEqual(tweets[0], "TONIGHT | \U0001F1F8\U0001F1EA SWEDEN\n---------\n\U0001F4FC Melodifestivalen\n\U0001F3C6 Final\n\U0001F553 20:00 CET\n---------\n\U0001F4FA https://svtplay.se.") @@ -15,9 +16,9 @@ def test_when_2_events_then_should_generate_3_tweets(self): {'country': 'Denmark', 'name': 'Dansk Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://tv.dr.dk', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 3) - self.assertEqual(tweets[0], "TODAY | 2 selection shows across Europe!") + self.assertEqual(tweets[0], "TODAY | 2 selection shows across Europe! (thread \U00002B07\U0000FE0F)") self.assertEqual(tweets[1], "TODAY | \U0001F1E9\U0001F1F0 DENMARK\n---------\n\U0001F4FC Dansk Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 20:00 CET\n---------\n\U0001F4FA https://tv.dr.dk.") self.assertEqual(tweets[2], "TODAY | \U0001F1F8\U0001F1EA SWEDEN\n---------\n\U0001F4FC Melodifestivalen\n\U0001F3C6 Final\n\U0001F553 20:00 CET\n---------\n\U0001F4FA https://svtplay.se.") @@ -28,50 +29,50 @@ def test_when_multiple_events_and_1_Australian_event_then_should_mention_Austral {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Heat 5', 'dateTimeCet': '2021-02-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Heat 2', 'dateTimeCet': '2021-02-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 4) - self.assertEqual(tweets[0], "TODAY | 3 selection shows across Europe and Australia!") + self.assertEqual(tweets[0], "TODAY | 3 selection shows across Europe and Australia! (thread \U00002B07\U0000FE0F)") self.assertEqual(tweets[1], "TODAY | \U0001F1E6\U0001F1FA AUSTRALIA\n---------\n\U0001F4FC Australia Decides\n\U0001F3C6 Final\n\U0001F553 10:30 CET\n---------\n\U0001F4FA https://facebook.com.") self.assertEqual(tweets[2], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Heat 5\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp.") self.assertEqual(tweets[3], "TODAY | \U0001F1F8\U0001F1EA SWEDEN\n---------\n\U0001F4FC Melodifestivalen\n\U0001F3C6 Heat 2\n\U0001F553 20:00 CET\n---------\n\U0001F4FA https://svtplay.se.") def test_when_multiple_events_then_should_sort_tweets_by_start_time_and_country(self): events = [ - {'country': 'Denmark', 'name': 'Dansk Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:05:00', 'watchLinks': [{'link': 'https://tv.dr.dk', 'comment': 'Recommended link', 'live': 1}]}, + {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}, - {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}]} + {'country': 'Denmark', 'name': 'Dansk Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:05:00', 'watchLinks': [{'link': 'https://tv.dr.dk', 'comment': 'Recommended link', 'live': 1}]}, ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 4) - self.assertEqual(tweets[0], "TODAY | 3 selection shows across Europe!") + self.assertEqual(tweets[0], "TODAY | 3 selection shows across Europe! (thread \U00002B07\U0000FE0F)") self.assertEqual(tweets[1], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp.") self.assertEqual(tweets[2], "TODAY | \U0001F1F8\U0001F1EA SWEDEN\n---------\n\U0001F4FC Melodifestivalen\n\U0001F3C6 Final\n\U0001F553 20:00 CET\n---------\n\U0001F4FA https://svtplay.se.") self.assertEqual(tweets[3], "TODAY | \U0001F1E9\U0001F1F0 DENMARK\n---------\n\U0001F4FC Dansk Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 20:05 CET\n---------\n\U0001F4FA https://tv.dr.dk.") def test_when_events_include_multi_parter_then_should_count_the_multi_parter_as_only_one_event_in_the_initial_tweet(self): events = [ - {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Estonia', 'name': 'Eesti Laul', 'stage': 'Semi-final 1', 'dateTimeCet': '2021-03-13T18:30:00', 'watchLinks': [{'link': 'https://etv.err.ee/', 'comment': 'Recommended link', 'live': 1}]}, + {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Estonia', 'name': 'Eesti Laul', 'stage': 'Semi-final 1 (part 2)', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://etv.err.ee/', 'comment': 'Recommended link', 'live': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 4) - self.assertEqual(tweets[0], "TODAY | 2 selection shows across Europe!") + self.assertEqual(tweets[0], "TODAY | 2 selection shows across Europe! (thread \U00002B07\U0000FE0F)") def test_when_events_only_contains_a_multi_parter_show_then_should_count_the_multi_parter_show_as_only_one_event_in_the_initial_tweet(self): events = [ {'country': 'Estonia', 'name': 'Eesti Laul', 'stage': 'Semi-final 1', 'dateTimeCet': '2021-03-13T18:30:00', 'watchLinks': [{'link': 'https://etv.err.ee/', 'comment': 'Recommended link', 'live': 1}]}, {'country': 'Estonia', 'name': 'Eesti Laul', 'stage': 'Semi-final 1 (part 2)', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://etv.err.ee/', 'comment': 'Recommended link', 'live': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 3) - self.assertEqual(tweets[0], "TODAY | 1 selection show across Europe!") + self.assertEqual(tweets[0], "TODAY | 1 selection show across Europe! (thread \U00002B07\U0000FE0F)") def test_when_event_contains_multiple_watch_links_should_generate_one_tweet_that_includes_all_links_and_comments(self): events = [ {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Heat 1', 'dateTimeCet': '2021-02-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'A first link', 'live': 1}, {'link': 'https://tv.nrk.no', 'comment': 'Another link', 'live': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 1) self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Heat 1\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp (A first link) OR https://tv.nrk.no (Another link).") @@ -79,17 +80,17 @@ def test_when_event_contains_multiple_watch_links_should_generate_one_tweet_that events = [ {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Heat 1', 'dateTimeCet': '2021-02-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://tv.nrk.no', 'comment': 'Another link', 'live': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 1) self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Heat 1\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp OR https://tv.nrk.no (Another link).") def test_when_2_events_contain_multiple_watch_links_should_generate_3_tweet_that_includes_all_links_and_valid_comments(self): events = [ - {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se/melodifestivalen', 'comment': '', 'live': 1}]}, - {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://tv.nrk.no', 'live': 1}]} + {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://tv.nrk.no', 'live': 1}]}, + {'country': 'Sweden', 'name': 'Melodifestivalen', 'stage': 'Final', 'dateTimeCet': '2021-03-13T20:00:00', 'watchLinks': [{'link': 'https://svtplay.se/melodifestivalen', 'comment': '', 'live': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 3) self.assertEqual(tweets[1], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp OR https://tv.nrk.no.") self.assertEqual(tweets[2], "TODAY | \U0001F1F8\U0001F1EA SWEDEN\n---------\n\U0001F4FC Melodifestivalen\n\U0001F3C6 Final\n\U0001F553 20:00 CET\n---------\n\U0001F4FA https://svtplay.se/melodifestivalen.") @@ -99,7 +100,7 @@ def test_when_one_event_contains_multiple_watch_links_should_generate_tweet_that events = [ {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://tv.nrk.no', 'live': 0}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 1) self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp.") @@ -108,24 +109,24 @@ def test_when_one_event_contains_geoblocked_watch_links_should_add_geoblocked_co events = [ {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://tv.nrk.no', 'live': 1, 'geoblocked': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True, shorten_urls=True) self.assertEqual(len(tweets), 1) - self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp OR https://tv.nrk.no (geoblocked).") + self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA nrk.no OR tv.nrk.no (geoblocked).") def test_when_one_event_contains_watch_link_that_requires_an_account_should_add_comment_to_tweet(self): events = [ {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://tv.nrk.no', 'live': 1, 'accountRequired': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True) self.assertEqual(len(tweets), 1) - self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp OR https://tv.nrk.no (account required: https://lyseurovision.github.io/help.html#account-Norway).") + self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp OR https://tv.nrk.no (account required: see https://lyseurovision.github.io/help.html#account-Norway).") def test_when_one_event_contains_watch_link_that_requires_an_account_and_is_geoblocked_should_add_comment_to_tweet(self): events = [ {'country': 'Norway', 'name': 'Melodi Grand Prix', 'stage': 'Final', 'dateTimeCet': '2021-03-13T19:50:00', 'watchLinks': [{'link': 'https://nrk.no/mgp', 'comment': 'Recommended link', 'live': 1}, {'link': 'https://tv.nrk.no', 'live': 1, 'accountRequired': 1, 'geoblocked': 1}]} ] - tweets = generate_daily_tweet_thread(events, is_morning=True) + tweets = generate_daily_thread_posts(events, is_morning=True, shorten_urls=True) self.assertEqual(len(tweets), 1) - self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA https://nrk.no/mgp OR https://tv.nrk.no (geoblocked, account required: https://lyseurovision.github.io/help.html#account-Norway).") + self.assertEqual(tweets[0], "TODAY | \U0001F1F3\U0001F1F4 NORWAY\n---------\n\U0001F4FC Melodi Grand Prix\n\U0001F3C6 Final\n\U0001F553 19:50 CET\n---------\n\U0001F4FA nrk.no OR tv.nrk.no (geoblocked, account required: see lyseurovision.github.io).") From bdcd5ea0e2f1baa552bf91ef196c8b82781c7997 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Oct 2023 19:58:07 +0000 Subject: [PATCH 3/3] Bump requests from 2.28.2 to 2.31.0 Bumps [requests](https://github.com/psf/requests) from 2.28.2 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.28.2...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fd6bf0e..5ea6c24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ tweepy==4.14.0 -requests==2.28.2 +requests==2.31.0 beautifulsoup4==4.9.0 \ No newline at end of file