From 6492db5001bfe4c255b13d727fcd11328032b96e Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 20 Jun 2023 15:34:52 +0200 Subject: [PATCH] how do you like this, Elon Musk? --- CHANGES.rst | 3 +- README.rst | 45 ----- src/design/plone/policy/interfaces.py | 2 +- .../policy/profiles/to_1400/registry.xml | 9 - .../plone/policy/restapi/configure.zcml | 1 - .../policy/restapi/twitter_feed/__init__.py | 0 .../restapi/twitter_feed/configure.zcml | 21 -- .../plone/policy/restapi/twitter_feed/get.py | 179 ------------------ src/design/plone/policy/upgrades.py | 33 ++-- 9 files changed, 22 insertions(+), 271 deletions(-) delete mode 100644 src/design/plone/policy/profiles/to_1400/registry.xml delete mode 100644 src/design/plone/policy/restapi/twitter_feed/__init__.py delete mode 100644 src/design/plone/policy/restapi/twitter_feed/configure.zcml delete mode 100644 src/design/plone/policy/restapi/twitter_feed/get.py diff --git a/CHANGES.rst b/CHANGES.rst index 62730c3..df13cc5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,8 @@ Changelog 5.0.4 (unreleased) ------------------ -- Nothing changed yet. +- Remove twitter feeds. + [folix-01] 5.0.3 (2023-06-13) diff --git a/README.rst b/README.rst index 7386ff6..425a70a 100644 --- a/README.rst +++ b/README.rst @@ -164,51 +164,6 @@ La struttura degli attachments è la seguente:: Se l'invio va a buon fine, viene tornata una risposta con `204`. -@twitter-feed -------------- - -Endpoint per poter visualizzare una serie di tweet. - -Per poterla utilizzare, bisogna creare un'app su Twitter e impostare il token Bearer dentro al registry Plone nella entry *design.plone.policy.twitter_token*. - -Per fare la ricerca, utilizza l'endpoint `recent`_ che permette di visualizzare solo i tweet dell'ultima settimana. - -.. _recent: https://developer.twitter.com/en/docs/twitter-api/tweets/search/introduction - -Come parametri accetta i seguenti: - -- **authors**: una lista di username tra cui ricercare gli ultimi Tweet. -- **max_results**: un numero tra 10 e 100. - -Esempio di chiamata:: - - > curl -i -X GET http://localhost:8080/Plone/@twitter-feed?authors=foo&authors=bar -H 'Accept: application/json' -H 'Content-Type: application/json' - -La risposta è una lista di tweet con le informazioni necessarie per essere renderizzati:: - - [ - { - "author": { - "id": "12345678", - "name": "John Doe", - "profile_image_url": "https://pbs.twimg.com/profile_images/xxx/xxx_normal.jpg", - "username": "jdoe" - }, - "id": "xxxxx", - "like_count": 1, - "reply_count": 0, - "retweet_count": 0, - "text": "stringa html" - }, - ... - ] - -Il campo `text` contiene già eventuali link ad hashtag, menzioni e link esterni dentro ad un tag . - -**Per evitare troppe chiamate al servizio (c'è un limite di 500000 tweet al mese), c'è della cache: per ogni query -i risultati rimangono in cache per mezz'ora.** - - Amministrazione trasparente =========================== diff --git a/src/design/plone/policy/interfaces.py b/src/design/plone/policy/interfaces.py index 0d6fb20..890589d 100644 --- a/src/design/plone/policy/interfaces.py +++ b/src/design/plone/policy/interfaces.py @@ -11,4 +11,4 @@ class IDesignPlonePolicyLayer(IDefaultBrowserLayer): class IDesignPlonePolicySettings(Interface): - twitter_token = schema.TextLine(title="Twitter Bearer token") + """IDesignPlonePolicySettings interface""" diff --git a/src/design/plone/policy/profiles/to_1400/registry.xml b/src/design/plone/policy/profiles/to_1400/registry.xml deleted file mode 100644 index eb7ed17..0000000 --- a/src/design/plone/policy/profiles/to_1400/registry.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/src/design/plone/policy/restapi/configure.zcml b/src/design/plone/policy/restapi/configure.zcml index fad01d6..9eed2a2 100644 --- a/src/design/plone/policy/restapi/configure.zcml +++ b/src/design/plone/policy/restapi/configure.zcml @@ -7,6 +7,5 @@ - diff --git a/src/design/plone/policy/restapi/twitter_feed/__init__.py b/src/design/plone/policy/restapi/twitter_feed/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/design/plone/policy/restapi/twitter_feed/configure.zcml b/src/design/plone/policy/restapi/twitter_feed/configure.zcml deleted file mode 100644 index bea302a..0000000 --- a/src/design/plone/policy/restapi/twitter_feed/configure.zcml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/src/design/plone/policy/restapi/twitter_feed/get.py b/src/design/plone/policy/restapi/twitter_feed/get.py deleted file mode 100644 index f00f9b9..0000000 --- a/src/design/plone/policy/restapi/twitter_feed/get.py +++ /dev/null @@ -1,179 +0,0 @@ -# -*- coding: utf-8 -*- -from copy import deepcopy -from design.plone.policy.interfaces import IDesignPlonePolicySettings -from plone import api -from plone.memoize import ram -from plone.restapi.search.utils import unflatten_dotted_dict -from plone.restapi.services import Service -from time import time -from urllib.parse import urlencode -from zope.interface import implementer -from zope.publisher.interfaces import IPublishTraverse - -import logging -import requests -import six - - -logger = logging.getLogger(__name__) -ENDPOINT = "https://api.twitter.com/2/tweets/search/recent" - - -def _feed_cachekey(method, self, query): - """ - method for ramcache that store time and query. - Cache time is 30 minutes - """ - timestamp = time() // (60 * 30 * 1) - return "{timestamp}:{query}".format(timestamp=timestamp, query=urlencode(query)) - - -@implementer(IPublishTraverse) -class TwitterFeedGet(Service): - def reply(self): - token = api.portal.get_registry_record( - "twitter_token", interface=IDesignPlonePolicySettings - ) - if not token: - msg = "BEARER token not set: please set it into plone registry to be able to fetch Tweets." # noqa - logger.error(msg) - self.request.response.setStatus(500) - return dict(error=dict(message=msg)) - - query = self.generate_query() - if query.get("error", ""): - self.request.response.setStatus(400) - return dict(error=dict(message=query["error"])) - try: - res = self.retrieve_tweets(query=query) - except Exception as e: - logger.exception(e) - self.request.response.setStatus(500) - msg = getattr(e, "message", e.__str__()) - return dict(error=dict(message=msg)) - return res - - @ram.cache(_feed_cachekey) - def retrieve_tweets(self, query): - token = api.portal.get_registry_record( - "twitter_token", interface=IDesignPlonePolicySettings - ) - - resp = requests.get( - url=ENDPOINT, - params=query, - headers={"Authorization": "Bearer {}".format(token)}, - ) - # raise an exception if resp is not successful - resp.raise_for_status() - return self.convert_tweets(data=resp.json()) - - def generate_query(self): - query = self.request.form.copy() - query = unflatten_dotted_dict(query) - if not query: - return {"error": "You need to provide at least an author."} - max_results = int(query.get("max_results", "10")) - if max_results < 10 or max_results > 100: - return {"error": "max_results should be between 10 and 100."} - authors = query.get("authors", []) - if not authors: - return {"error": "You need to provide at least an author."} - if isinstance(authors, six.string_types): - authors = authors.split(",") - res = { - "query": "from: {}".format(" OR ".join(authors)), - # additional infos for tweets - "tweet.fields": "entities,source,public_metrics,created_at", - "expansions": "attachments.media_keys,author_id", - "media.fields": "type,preview_image_url,height,media_key,public_metrics,url,width", # noqa - "user.fields": "name,profile_image_url,username", - } - return res - - def convert_tweets(self, data): - tweets = data.get("data", []) - users = data.get("includes", {}).get("users", []) - - if not tweets: - return [] - - res = [] - for tweet in tweets: - html = self.convert_data_to_html(data=tweet) - if html: - tweet_data = { - "text": html, - "id": tweet["id"], - "created_at": tweet["created_at"], - "retweet_count": tweet["public_metrics"]["retweet_count"], - "reply_count": tweet["public_metrics"]["reply_count"], - "like_count": tweet["public_metrics"]["like_count"], - } - author_id = tweet.get("author_id", "") - for user in users: - if author_id == user["id"]: - tweet_data["author"] = user - res.append(tweet_data) - return res - - def convert_data_to_html(self, data): - entities = data.get("entities", {}) - text = data.get("text") - if not entities: - return text - html = deepcopy(text) - href_template = '{text}' - #  replace hashtags - for hashtag in entities.get("hashtags", []): - tag_text = text[hashtag["start"] : hashtag["end"]] # noqa - replaced = href_template.format( - url="https://twitter.com/hashtag/{}".format(hashtag["tag"]), - title=tag_text, - text=tag_text, - ) - html = html.replace( - tag_text, - replaced, - ) - - # replace mentions - for mention in entities.get("mentions", []): - mention_text = text[mention["start"] : mention["end"]] # noqa - replaced = href_template.format( - url="https://twitter.com/{}".format(mention["username"]), - title=mention_text, - text=mention_text, - ) - html = html.replace( - mention_text, - replaced, - ) - - #  replace urls - occurrences = {} - for url_data in entities.get("urls", []): - url = url_data["url"] - if url not in occurrences: - occurrences[url] = 1 - else: - occurrences[url] += 1 - for url_data in entities.get("urls", []): - url = url_data["url"] - to_replace = text[url_data["start"] : url_data["end"]] # noqa - if occurrences[url] > 1: - html = html.replace(to_replace, "") - else: - if url_data["display_url"].startswith("pic.twitter.com"): - html = html.replace(to_replace, "") - else: - replaced = href_template.format( - url=url, - title=url_data.get("title", url_data["display_url"]), - text=url_data["display_url"], - ) - html = html.replace( - to_replace, - replaced, - ) - return html diff --git a/src/design/plone/policy/upgrades.py b/src/design/plone/policy/upgrades.py index f72fa67..80f528f 100644 --- a/src/design/plone/policy/upgrades.py +++ b/src/design/plone/policy/upgrades.py @@ -26,7 +26,9 @@ def update_profile(context, profile, run_dependencies=True): - context.runImportStepFromProfile(DEFAULT_PROFILE, profile, run_dependencies) + context.runImportStepFromProfile( + DEFAULT_PROFILE, profile, run_dependencies + ) def update_types(context): @@ -63,7 +65,9 @@ def fix_field_name(blocks): installOrReinstallProduct(api.portal.get(), "collective.volto.formsupport") logger.info("Changing form block fields.") i = 0 - brains = api.content.find(object_provides="plone.restapi.behaviors.IBlocks") + brains = api.content.find( + object_provides="plone.restapi.behaviors.IBlocks" + ) tot = len(brains) fixed_items = [] for brain in brains: @@ -92,14 +96,7 @@ def to_1300(context): def to_1400(context): - old = api.portal.get_registry_record(name="design.plone.policy.twitter_token") - context.runAllImportStepsFromProfile("profile-design.plone.policy:to_1400") - update_registry(context) - - if old: - api.portal.set_registry_record( - "twitter_token", old, interface=IDesignPlonePolicySettings - ) + pass def to_1500(context): @@ -173,7 +170,9 @@ def to_1910(context): else: new_sizes.append(size) if "midi 300:65536" not in new_sizes: - new_sizes.insert(new_sizes.index("mini 200:65536") + 1, "midi 300:65536") + new_sizes.insert( + new_sizes.index("mini 200:65536") + 1, "midi 300:65536" + ) api.portal.set_registry_record("plone.allowed_sizes", new_sizes) @@ -234,7 +233,9 @@ def fix_block(blocks): blocks = value.get("blocks", {}) except AttributeError: logger.warning( - "[RICHTEXT] - {} (not converted)".format(brain.getURL()) + "[RICHTEXT] - {} (not converted)".format( + brain.getURL() + ) ) if blocks: res = fix_block(blocks) @@ -262,7 +263,9 @@ def update_folders(context, CHANGES=[], NEW_ITEMS=[]): portal_name = portal.getId() for item in CHANGES: try: - folder = portal.restrictedTraverse("{}{}".format(portal_name, item)) + folder = portal.restrictedTraverse( + "{}{}".format(portal_name, item) + ) old_title = folder.title old_path = "/".join(folder.getPhysicalPath()) api.content.rename(obj=folder, new_id=CHANGES[item][1]) @@ -276,7 +279,9 @@ def update_folders(context, CHANGES=[], NEW_ITEMS=[]): ) ) except KeyError: - logger.info("{} Impossibile modificare {}{}".format(RED, item, ENDC)) + logger.info( + "{} Impossibile modificare {}{}".format(RED, item, ENDC) + ) for item in NEW_ITEMS: folder = portal.restrictedTraverse("{}{}".format(portal_name, item[0]))