From 4f0db5537e8b60096cd548d7db340a09a0250e46 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 5 Nov 2021 11:49:51 -0600 Subject: [PATCH 01/33] Added function to send tweets and threads in session module so it will be used globally later --- src/sessions/twitter/session.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index a219016c..f9de86fd 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -590,4 +590,20 @@ class Session(base.baseSession): if self.logged == False: return if user != self.db["user_name"]: - log.debug("Connected streaming endpoint on account {}".format(user)) \ No newline at end of file + log.debug("Connected streaming endpoint on account {}".format(user)) + + def send_tweet(self, *tweets): + """ Convenience function to send a thread. """ + in_reply_to_status_id = None + for obj in tweets: + if len(obj["attachments"]) == 0: + item = self.api_call(call_name="update_status", status=obj["text"], _sound="tweet_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id) + in_reply_to_status_id = item.id + else: + media_ids = [] + for i in obj["attachments"]: + img = self.api_call("media_upload", filename=i["file"]) + self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) + media_ids.append(img.media_id) + item = self.api_call(call_name="update_status", status=obj["text"], _sound="tweet_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, media_ids=media_ids) + in_reply_to_status_id = item.id \ No newline at end of file From 66bf95ee6245d515de3a306447cae1d07358edb7 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 5 Nov 2021 13:10:45 -0600 Subject: [PATCH 02/33] Added reply function to session module --- src/sessions/twitter/session.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index f9de86fd..a32ead2d 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -606,4 +606,15 @@ class Session(base.baseSession): self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) media_ids.append(img.media_id) item = self.api_call(call_name="update_status", status=obj["text"], _sound="tweet_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, media_ids=media_ids) - in_reply_to_status_id = item.id \ No newline at end of file + in_reply_to_status_id = item.id + + def reply(self, text="", in_reply_to_status_id=None, attachments=[], *args, **kwargs): + if len(attachments) == 0: + item = self.api_call(call_name="update_status", status=text, _sound="reply_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, *args, **kwargs) + else: + media_ids = [] + for i in attachments: + img = self.api_call("media_upload", filename=i["file"]) + self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) + media_ids.append(img.media_id) + item = self.api_call(call_name="update_status", status=text, _sound="reply_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, media_ids=media_ids, *args, **kwargs) From cedb290956fec7345c893054ac0121bade2e6b28 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 5 Nov 2021 22:35:12 -0600 Subject: [PATCH 03/33] fixed an issue when deleting a buffer in a session where there were trending topics opened --- doc/changelog.md | 5 +++-- src/wxUI/buffers/trends.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/changelog.md b/doc/changelog.md index 1222937d..513204c2 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -3,10 +3,11 @@ TWBlue Changelog ## changes in this version * TWBlue should retrieve tweets from threads and conversations in a more reliable way. Tweets in the same thread (made by the same author) will be sorted correctly, although replies to the thread (made by different people) may not be ordered in the same way they are displayed in Twitter apps. ([#417](https://github.com/manuelcortez/TWBlue/issues/417)) -* fixed a bug when clearing the direct messages buffer. ([#418](https://github.com/manuelcortez/TWBlue/issues/418)) -* fixed an issue that was making TWBlue to show incorrectly titles for trending topic buffers upon startup. ([#421](https://github.com/manuelcortez/TWBlue/issues/421)) * When creating a filter, TWBlue will show an error if user has not provided a name for the filter. Before, unnamed filters were a cause of config breaks in the application. * It is again possible to read the changelog for TWBlue from the help menu in the menu bar. +* fixed a bug when clearing the direct messages buffer. ([#418](https://github.com/manuelcortez/TWBlue/issues/418)) +* fixed an issue that was making TWBlue to show incorrectly titles for trending topic buffers upon startup. ([#421](https://github.com/manuelcortez/TWBlue/issues/421)) +* fixed an issue that was making users of the graphical user interface to delete a buffer if a trends buffer was opened in the same session. * Updated Spanish, Japanese and french translations. ## Changes in Version 2021.10.30 diff --git a/src/wxUI/buffers/trends.py b/src/wxUI/buffers/trends.py index 975a88ca..b9e67101 100644 --- a/src/wxUI/buffers/trends.py +++ b/src/wxUI/buffers/trends.py @@ -32,3 +32,5 @@ class trendsPanel(wx.Panel): else: self.list.select_item(0) + def set_focus_in_list(self): + self.list.list.SetFocus() From 556ffd832b8e827b74d8c10fa9ab664c1f443027 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sun, 7 Nov 2021 06:34:27 -0600 Subject: [PATCH 04/33] Updated translation machine catalog for french translation --- src/locales/fr/LC_MESSAGES/twblue.mo | Bin 52802 -> 56563 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/locales/fr/LC_MESSAGES/twblue.mo b/src/locales/fr/LC_MESSAGES/twblue.mo index c83284cf0f3be22bf81f266e6aa87282f93e65b5..2d8ceb5cd79bfc8523ba19a9b3344d478969ea03 100644 GIT binary patch delta 20359 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 Date: Mon, 8 Nov 2021 15:29:23 -0600 Subject: [PATCH 05/33] Removed URL Shorteners --- src/url_shortener/__init__.py | 3 -- src/url_shortener/__main__.py | 46 ------------------- src/url_shortener/shorteners/__init__.py | 11 ----- src/url_shortener/shorteners/acortame.py | 30 ------------ src/url_shortener/shorteners/clckru.py | 22 --------- src/url_shortener/shorteners/hkcim.py | 21 --------- src/url_shortener/shorteners/isgd.py | 22 --------- src/url_shortener/shorteners/onjme.py | 21 --------- src/url_shortener/shorteners/tinyarrows.py | 21 --------- src/url_shortener/shorteners/tinyurl.py | 20 -------- src/url_shortener/shorteners/url_shortener.py | 44 ------------------ src/url_shortener/shorteners/xedcc.py | 21 --------- 12 files changed, 282 deletions(-) delete mode 100644 src/url_shortener/__init__.py delete mode 100644 src/url_shortener/__main__.py delete mode 100644 src/url_shortener/shorteners/__init__.py delete mode 100644 src/url_shortener/shorteners/acortame.py delete mode 100644 src/url_shortener/shorteners/clckru.py delete mode 100644 src/url_shortener/shorteners/hkcim.py delete mode 100644 src/url_shortener/shorteners/isgd.py delete mode 100644 src/url_shortener/shorteners/onjme.py delete mode 100644 src/url_shortener/shorteners/tinyarrows.py delete mode 100644 src/url_shortener/shorteners/tinyurl.py delete mode 100644 src/url_shortener/shorteners/url_shortener.py delete mode 100644 src/url_shortener/shorteners/xedcc.py diff --git a/src/url_shortener/__init__.py b/src/url_shortener/__init__.py deleted file mode 100644 index 419c8170..00000000 --- a/src/url_shortener/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import unicode_literals -from . import shorteners -from . __main__ import * diff --git a/src/url_shortener/__main__.py b/src/url_shortener/__main__.py deleted file mode 100644 index 7e3dcf05..00000000 --- a/src/url_shortener/__main__.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import unicode_literals -from functools import wraps -from . import shorteners - - -def service_selecter (func): - @wraps(func) - def wrapper (*args, **kwargs): - tmp = dict(kwargs) - if 'service' in tmp: - del(tmp['service']) - kwargs['service'] = find_service(kwargs['service'], **tmp) or default_service() - else: - kwargs['service'] = default_service() - return func(*args, **kwargs) - return wrapper - -@service_selecter -def shorten (url, service=None, **kwargs): - return service(**kwargs).shorten(url) - - -@service_selecter -def unshorten (url, service=None, **kwargs): - return service(**kwargs).unshorten(url) - - -def default_service (): - return shorteners.AcortameShortener - -def find_service (service, **kwargs): - for i in shorteners.__all__: - obj = getattr(shorteners, i)(**kwargs) - if obj.name.lower() == service.lower(): - return getattr(shorteners, i) - -def list_services (): - return [getattr(shorteners, i)().name for i in shorteners.__all__] - -def unshorten_any (url): - """Unshortens an URL using any available unshortener. Check to see if unshortened URL was created by a shortener (nested) and unshorten if so.""" - unshortened_url = shorteners.URLShortener().unshorten(url) - # None is returned if URL not unshortened - if unshortened_url: - return unshorten_any(unshortened_url) - return url diff --git a/src/url_shortener/shorteners/__init__.py b/src/url_shortener/shorteners/__init__.py deleted file mode 100644 index aa0b763d..00000000 --- a/src/url_shortener/shorteners/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -from .url_shortener import URLShortener -from .hkcim import HKCShortener -from . isgd import IsgdShortener -from . onjme import OnjmeShortener -from . tinyarrows import TinyArrowsShortener -from . tinyurl import TinyurlShortener -from . xedcc import XedccShortener -from . clckru import ClckruShortener -from . acortame import AcortameShortener -__all__ = ["HKCShortener", "IsgdShortener", "OnjmeShortener", "TinyArrowsShortener", "TinyurlShortener", "XedccShortener", "ClckruShortener", "AcortameShortener"] diff --git a/src/url_shortener/shorteners/acortame.py b/src/url_shortener/shorteners/acortame.py deleted file mode 100644 index e7adafb6..00000000 --- a/src/url_shortener/shorteners/acortame.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -from . url_shortener import URLShortener -import requests -import urllib.request, urllib.parse, urllib.error -class AcortameShortener (URLShortener): - def __init__(self, *args, **kwargs): - self.name = "acorta.me" - super(AcortameShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get ("https://acorta.me/api.php?action=shorturl&format=simple&url=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer - - def created_url (self, url): - return 'acorta.me' in url - - def unshorten (self, url): - if not 'acorta.me' in url: - #use generic expand method - return super(AcortameShortener, self).unshorten(url) - answer = url - api = requests.get ("https://acorta.me/api.php?action=expand&format=simple&shorturl=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer diff --git a/src/url_shortener/shorteners/clckru.py b/src/url_shortener/shorteners/clckru.py deleted file mode 100644 index 9c527ea1..00000000 --- a/src/url_shortener/shorteners/clckru.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -import urllib.request, urllib.parse, urllib.error -import requests -from . url_shortener import URLShortener - - -class ClckruShortener (URLShortener): - def __init__ (self, *args, **kwargs): - self.name = "clck.ru" - super(ClckruShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get ("http://clck.ru/--?url=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer - - def created_url (self, url): - return 'clck.ru' in url diff --git a/src/url_shortener/shorteners/hkcim.py b/src/url_shortener/shorteners/hkcim.py deleted file mode 100644 index 29ecd9ea..00000000 --- a/src/url_shortener/shorteners/hkcim.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -import urllib.request, urllib.parse, urllib.error -import requests -from . url_shortener import URLShortener - -class HKCShortener (URLShortener): - def __init__ (self, *args, **kwargs): - self.name = "HKC.im" - super(HKCShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get ("http://hkc.im/yourls-api.php?action=shorturl&format=simple&url=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer - - def created_url (self, url): - return 'hkc.im' in url.lower() diff --git a/src/url_shortener/shorteners/isgd.py b/src/url_shortener/shorteners/isgd.py deleted file mode 100644 index ee817360..00000000 --- a/src/url_shortener/shorteners/isgd.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -import urllib.request, urllib.parse, urllib.error -import requests -from . url_shortener import URLShortener - - -class IsgdShortener (URLShortener): - def __init__ (self, *args, **kwargs): - self.name = "Is.gd" - super(IsgdShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get ("http://is.gd/api.php?longurl=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer - - def created_url (self, url): - return 'is.gd' in url diff --git a/src/url_shortener/shorteners/onjme.py b/src/url_shortener/shorteners/onjme.py deleted file mode 100644 index c6a21315..00000000 --- a/src/url_shortener/shorteners/onjme.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -import urllib.request, urllib.parse, urllib.error -import requests -from . url_shortener import URLShortener - -class OnjmeShortener (URLShortener): - def __init__ (self, *args, **kwargs): - self.name = "Onj.me" - super(OnjmeShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get ("http://onj.me/yourls-api.php?action=shorturl&format=simple&url=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer - - def created_url (self, url): - return 'onj.me' in url.lower() diff --git a/src/url_shortener/shorteners/tinyarrows.py b/src/url_shortener/shorteners/tinyarrows.py deleted file mode 100644 index 45a8990c..00000000 --- a/src/url_shortener/shorteners/tinyarrows.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -import urllib.request, urllib.parse, urllib.error -import requests -from . url_shortener import URLShortener - -class TinyArrowsShortener (URLShortener): - def __init__ (self, *args, **kwargs): - self.name = "TinyArro.ws" - super(TinyArrowsShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get("http://tinyarro.ws/api-create.php?utfpure=1&url=%s" % urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer.decode('UTF-8') - - def created_url(self, url): - return "tinyarro.ws" in url diff --git a/src/url_shortener/shorteners/tinyurl.py b/src/url_shortener/shorteners/tinyurl.py deleted file mode 100644 index 0a5703a3..00000000 --- a/src/url_shortener/shorteners/tinyurl.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -from .url_shortener import URLShortener -import requests -import urllib.request, urllib.parse, urllib.error -class TinyurlShortener (URLShortener): - def __init__(self, *args, **kwargs): - self.name = "TinyURL.com" - super(TinyurlShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get ("http://tinyurl.com/api-create.php?url=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer - - def created_url (self, url): - return 'tinyurl.com' in url diff --git a/src/url_shortener/shorteners/url_shortener.py b/src/url_shortener/shorteners/url_shortener.py deleted file mode 100644 index cf1d6f76..00000000 --- a/src/url_shortener/shorteners/url_shortener.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import unicode_literals -from builtins import object -import requests - -class URLShortener (object): - - def __init__ (self, *args, **kwargs): - #Stub out arguments, silly object. :( - return super(URLShortener, self).__init__() - - def shorten (self, url): - if self.created_url(url): - return url - else: - return self._shorten(url) - - def _shorten (self, url): - raise NotImplementedError - - def created_url (self, url): - """Returns a boolean indicating whether or not this shortener created a provided url""" - raise NotImplementedError - - def unshorten(self, url): - try: - r=requests.head(url) - if 'location' in list(r.headers.keys()): - if 'dropbox.com' in r.headers['location']: - return handle_dropbox(r.headers['location']) - else: - return r.headers['location'] - else: # if the head method does not work, use get instead. Performance may decrease - r=requests.get(url, allow_redirects=False, stream=True) - # release the connection without downloading the content, we only need the response headers - r.close() - return r.headers['location'] - except: - return url #we cannot expand - -def handle_dropbox(url): - if url.endswith("dl=1"): - return url - else: - return url.replace("dl=0", "dl=1") diff --git a/src/url_shortener/shorteners/xedcc.py b/src/url_shortener/shorteners/xedcc.py deleted file mode 100644 index 117b4f7c..00000000 --- a/src/url_shortener/shorteners/xedcc.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals -from future import standard_library -standard_library.install_aliases() -import urllib.request, urllib.parse, urllib.error -import requests -from . url_shortener import URLShortener - -class XedccShortener (URLShortener): - def __init__ (self, *args, **kwargs): - self.name = "Xed.cc" - super(XedccShortener, self).__init__(*args, **kwargs) - - def _shorten (self, url): - answer = url - api = requests.get ("http://xed.cc/yourls-api.php?action=shorturl&format=simple&url=" + urllib.parse.quote(url)) - if api.status_code == 200: - answer = api.text - return answer - - def created_url (self, url): - return 'xed.cc' in url.lower() From 2c70fe46b7cc9f682bed849fd92072939654c8c9 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 15:30:40 -0600 Subject: [PATCH 06/33] Removed url_shortener from sound handling module --- src/sound.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sound.py b/src/sound.py index df47f221..cd0f5a93 100644 --- a/src/sound.py +++ b/src/sound.py @@ -6,7 +6,6 @@ import subprocess import platform import tempfile import glob -import url_shortener import audio_services import paths import sound_lib @@ -121,7 +120,7 @@ class URLStream(object): """ Takes an URL and prepares it to be streamed. This function will try to unshorten the passed URL and, if needed, to transform it into a valid URL.""" log.debug("Preparing URL: %s" % (url,)) self.prepared = False - self.url = url_shortener.unshorten(url) + self.url = url if self.url == None: self.url = url log.debug("Expanded URL: %s" % (self.url,)) From 38e3274adc1a59243a6768a3e6ac2104a19ad11c Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 16:09:32 -0600 Subject: [PATCH 07/33] Removed GUI for tweets/reply/dm/viewTweets items and attachment code --- src/controller/attach.py | 40 --- src/wxUI/dialogs/attach.py | 48 ---- src/wxUI/dialogs/message.py | 471 ------------------------------------ 3 files changed, 559 deletions(-) delete mode 100644 src/controller/attach.py delete mode 100644 src/wxUI/dialogs/attach.py delete mode 100644 src/wxUI/dialogs/message.py diff --git a/src/controller/attach.py b/src/controller/attach.py deleted file mode 100644 index 08427142..00000000 --- a/src/controller/attach.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from builtins import object -import os -import widgetUtils -import logging -from wxUI.dialogs import attach as gui -log = logging.getLogger("controller.attach") - -class attach(object): - def __init__(self): - self.attachments = list() - self.dialog = gui.attachDialog() - widgetUtils.connect_event(self.dialog.photo, widgetUtils.BUTTON_PRESSED, self.upload_image) - widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_attachment) - self.dialog.get_response() - log.debug("Attachments controller started.") - - def upload_image(self, *args, **kwargs): - image, description = self.dialog.get_image() - if image != None: - imageInfo = {"type": "photo", "file": image, "description": description} - log.debug("Image data to upload: %r" % (imageInfo,)) - self.attachments.append(imageInfo) - info = [_(u"Photo"), description] - self.dialog.attachments.insert_item(False, *info) - self.dialog.remove.Enable(True) - - def remove_attachment(self, *args, **kwargs): - current_item = self.dialog.attachments.get_selected() - log.debug("Removing item %d" % (current_item,)) - if current_item == -1: current_item = 0 - self.attachments.pop(current_item) - self.dialog.attachments.remove_item(current_item) - self.check_remove_status() - log.debug("Removed") - - def check_remove_status(self): - if len(self.attachments) == 0 and self.dialog.attachments.get_count() == 0: - self.dialog.remove.Enable(False) diff --git a/src/wxUI/dialogs/attach.py b/src/wxUI/dialogs/attach.py deleted file mode 100644 index 0b20a422..00000000 --- a/src/wxUI/dialogs/attach.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -""" Attach dialog. Taken from socializer: https://github.com/manuelcortez/socializer""" -from __future__ import unicode_literals -import wx -import widgetUtils -from multiplatform_widgets import widgets - -class attachDialog(widgetUtils.BaseDialog): - def __init__(self): - super(attachDialog, self).__init__(None, title=_(u"Add an attachment")) - panel = wx.Panel(self) - sizer = wx.BoxSizer(wx.VERTICAL) - lbl1 = wx.StaticText(panel, wx.ID_ANY, _(u"Attachments")) - self.attachments = widgets.list(panel, _(u"Type"), _(u"Title"), style=wx.LC_REPORT) - box = wx.BoxSizer(wx.HORIZONTAL) - box.Add(lbl1, 0, wx.ALL, 5) - box.Add(self.attachments.list, 0, wx.ALL, 5) - sizer.Add(box, 0, wx.ALL, 5) - static = wx.StaticBox(panel, label=_(u"Add attachments")) - self.photo = wx.Button(panel, wx.ID_ANY, _(u"&Photo")) - self.remove = wx.Button(panel, wx.ID_ANY, _(u"Remove attachment")) - self.remove.Enable(False) - btnsizer = wx.StaticBoxSizer(static, wx.HORIZONTAL) - btnsizer.Add(self.photo, 0, wx.ALL, 5) - sizer.Add(btnsizer, 0, wx.ALL, 5) - ok = wx.Button(panel, wx.ID_OK) - ok.SetDefault() - cancelBtn = wx.Button(panel, wx.ID_CANCEL) - btnSizer = wx.BoxSizer() - btnSizer.Add(ok, 0, wx.ALL, 5) - btnSizer.Add(cancelBtn, 0, wx.ALL, 5) - sizer.Add(btnSizer, 0, wx.ALL, 5) - panel.SetSizer(sizer) - self.SetClientSize(sizer.CalcMin()) - - def get_image(self): - openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) - if openFileDialog.ShowModal() == wx.ID_CANCEL: - return (None, None) - dsc = self.ask_description() - return (openFileDialog.GetPath(), dsc) - - def ask_description(self): - dlg = wx.TextEntryDialog(self, _(u"please provide a description"), _(u"Description")) - dlg.ShowModal() - result = dlg.GetValue() - dlg.Destroy() - return result diff --git a/src/wxUI/dialogs/message.py b/src/wxUI/dialogs/message.py deleted file mode 100644 index 8eadfdd9..00000000 --- a/src/wxUI/dialogs/message.py +++ /dev/null @@ -1,471 +0,0 @@ -# -*- coding: utf-8 -*- -import wx -import widgetUtils - -class textLimited(widgetUtils.BaseDialog): - def __init__(self, *args, **kwargs): - super(textLimited, self).__init__(parent=None, *args, **kwargs) - - def createTextArea(self, message="", text=""): - if not hasattr(self, "panel"): - self.panel = wx.Panel(self) - self.label = wx.StaticText(self.panel, -1, message) - self.SetTitle(str(len(text))) - self.text = wx.TextCtrl(self.panel, -1, text, size=(439, -1),style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) -# font = self.text.GetFont() -# dc = wx.WindowDC(self.text) -# dc.SetFont(font) -# x, y = dc.GetTextExtent("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") -# self.text.SetSize((x, y)) - self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) - self.text.SetFocus() - self.textBox = wx.BoxSizer(wx.HORIZONTAL) - self.textBox.Add(self.label, 0, wx.ALL, 5) - self.textBox.Add(self.text, 0, wx.ALL, 5) - - def text_focus(self): - self.text.SetFocus() - - def get_text(self): - return self.text.GetValue() - - def set_text(self, text): - return self.text.ChangeValue(text) - - def set_title(self, new_title): - return self.SetTitle(new_title) - - def enable_button(self, buttonName): - if hasattr(self, buttonName): - return getattr(self, buttonName).Enable() - - def disable_button(self, buttonName): - if hasattr(self, buttonName): - return getattr(self, buttonName).Disable() - - def onSelect(self, ev): - self.text.SelectAll() - - def handle_keys(self, event): - shift=event.ShiftDown() - if event.GetKeyCode() == wx.WXK_RETURN and shift==False and hasattr(self,'okButton'): - wx.PostEvent(self.okButton.GetEventHandler(), wx.PyCommandEvent(wx.EVT_BUTTON.typeId,wx.ID_OK)) - else: - event.Skip() - - def set_cursor_at_end(self): - self.text.SetInsertionPoint(len(self.text.GetValue())) - - def set_cursor_at_position(self, position): - self.text.SetInsertionPoint(position) - - def get_position(self): - return self.text.GetInsertionPoint() - - def popup_menu(self, menu): - self.PopupMenu(menu, self.text.GetPosition()) - -class tweet(textLimited): - def createControls(self, title, message, text): - self.mainBox = wx.BoxSizer(wx.VERTICAL) - self.createTextArea(message, text) - self.mainBox.Add(self.textBox, 0, wx.ALL, 5) - self.long_tweet = wx.CheckBox(self.panel, -1, _(u"&Long tweet")) - self.upload_image = wx.Button(self.panel, -1, _(u"&Upload image..."), size=wx.DefaultSize) - self.spellcheck = wx.Button(self.panel, -1, _("Check &spelling..."), size=wx.DefaultSize) - self.attach = wx.Button(self.panel, -1, _(u"&Attach audio..."), size=wx.DefaultSize) - self.shortenButton = wx.Button(self.panel, -1, _(u"Sh&orten URL"), size=wx.DefaultSize) - self.unshortenButton = wx.Button(self.panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) - self.shortenButton.Disable() - self.unshortenButton.Disable() - self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize) - self.autocompletionButton = wx.Button(self.panel, -1, _(u"Auto&complete users")) - self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Sen&d"), size=wx.DefaultSize) - self.okButton.SetDefault() - cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) - self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 10) - self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10) - self.buttonsBox1.Add(self.attach, 0, wx.ALL, 10) - self.mainBox.Add(self.buttonsBox1, 0, wx.ALL, 10) - self.buttonsBox2 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox2.Add(self.shortenButton, 0, wx.ALL, 10) - self.buttonsBox2.Add(self.unshortenButton, 0, wx.ALL, 10) - self.buttonsBox2.Add(self.translateButton, 0, wx.ALL, 10) - self.mainBox.Add(self.buttonsBox2, 0, wx.ALL, 10) - self.ok_cancelSizer = wx.BoxSizer(wx.HORIZONTAL) - self.ok_cancelSizer.Add(self.autocompletionButton, 0, wx.ALL, 10) - self.ok_cancelSizer.Add(self.okButton, 0, wx.ALL, 10) - self.ok_cancelSizer.Add(cancelButton, 0, wx.ALL, 10) - self.mainBox.Add(self.ok_cancelSizer) - selectId = wx.ID_ANY - self.Bind(wx.EVT_MENU, self.onSelect, id=selectId) - self.accel_tbl = wx.AcceleratorTable([ - (wx.ACCEL_CTRL, ord('A'), selectId), - ]) - self.SetAcceleratorTable(self.accel_tbl) - self.panel.SetSizer(self.mainBox) - - def __init__(self, title, message, text, *args, **kwargs): - super(tweet, self).__init__() - self.shift=False - self.createControls(message, title, text) - self.SetClientSize(self.mainBox.CalcMin()) - - def get_image(self): - openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) - if openFileDialog.ShowModal() == wx.ID_CANCEL: - return None - return open(openFileDialog.GetPath(), "rb") - - -class retweet(tweet): - def createControls(self, title, message, text): - self.mainBox = wx.BoxSizer(wx.VERTICAL) - self.createTextArea(message, "") - label = wx.StaticText(self.panel, -1, _(u"Retweet")) - self.text2 = wx.TextCtrl(self.panel, -1, text, size=(439, -1), style=wx.TE_MULTILINE|wx.TE_READONLY) - self.retweetBox = wx.BoxSizer(wx.HORIZONTAL) - self.retweetBox.Add(label, 0, wx.ALL, 5) - self.retweetBox.Add(self.text2, 0, wx.ALL, 5) - self.mainBox.Add(self.textBox, 0, wx.ALL, 5) - self.mainBox.Add(self.retweetBox, 0, wx.ALL, 5) - self.upload_image = wx.Button(self.panel, -1, _(u"&Upload image..."), size=wx.DefaultSize) - self.spellcheck = wx.Button(self.panel, -1, _("Check &spelling..."), size=wx.DefaultSize) - self.attach = wx.Button(self.panel, -1, _(u"&Attach audio..."), size=wx.DefaultSize) - self.shortenButton = wx.Button(self.panel, -1, _(u"Sh&orten URL"), size=wx.DefaultSize) - self.unshortenButton = wx.Button(self.panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) - self.shortenButton.Disable() - self.unshortenButton.Disable() - self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize) - self.autocompletionButton = wx.Button(self.panel, -1, _(u"Auto&complete users")) - self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Sen&d"), size=wx.DefaultSize) - self.okButton.SetDefault() - cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) - self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 10) - self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10) - self.buttonsBox1.Add(self.attach, 0, wx.ALL, 10) - self.mainBox.Add(self.buttonsBox1, 0, wx.ALL, 10) - self.buttonsBox2 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox2.Add(self.shortenButton, 0, wx.ALL, 10) - self.buttonsBox2.Add(self.unshortenButton, 0, wx.ALL, 10) - self.buttonsBox2.Add(self.translateButton, 0, wx.ALL, 10) - self.mainBox.Add(self.buttonsBox2, 0, wx.ALL, 10) - self.ok_cancelSizer = wx.BoxSizer(wx.HORIZONTAL) - self.ok_cancelSizer.Add(self.autocompletionButton, 0, wx.ALL, 10) - self.ok_cancelSizer.Add(self.okButton, 0, wx.ALL, 10) - self.ok_cancelSizer.Add(cancelButton, 0, wx.ALL, 10) - self.mainBox.Add(self.ok_cancelSizer) - selectId = wx.ID_ANY - self.Bind(wx.EVT_MENU, self.onSelect, id=selectId) - self.accel_tbl = wx.AcceleratorTable([ - (wx.ACCEL_CTRL, ord('A'), selectId), - ]) - self.SetAcceleratorTable(self.accel_tbl) - self.panel.SetSizer(self.mainBox) - - def __init__(self, title, message, text, *args, **kwargs): - super(tweet, self).__init__() - self.createControls(message, title, text) -# self.onTimer(wx.EVT_CHAR_HOOK) - self.SetClientSize(self.mainBox.CalcMin()) - - def get_image(self): - openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) - if openFileDialog.ShowModal() == wx.ID_CANCEL: - return None - return open(openFileDialog.GetPath(), "rb") - -class dm(textLimited): - def createControls(self, title, message, users): - self.panel = wx.Panel(self) - self.mainBox = wx.BoxSizer(wx.VERTICAL) - label = wx.StaticText(self.panel, -1, _(u"&Recipient")) - self.cb = wx.ComboBox(self.panel, -1, choices=users, value=users[0], size=wx.DefaultSize) - self.autocompletionButton = wx.Button(self.panel, -1, _(u"Auto&complete users")) - self.createTextArea(message, text="") - userBox = wx.BoxSizer(wx.HORIZONTAL) - userBox.Add(label, 0, wx.ALL, 5) - userBox.Add(self.cb, 0, wx.ALL, 5) - userBox.Add(self.autocompletionButton, 0, wx.ALL, 5) - self.mainBox.Add(userBox, 0, wx.ALL, 5) - self.mainBox.Add(self.textBox, 0, wx.ALL, 5) - self.spellcheck = wx.Button(self.panel, -1, _("Check &spelling..."), size=wx.DefaultSize) - self.attach = wx.Button(self.panel, -1, _(u"&Attach audio..."), size=wx.DefaultSize) - self.shortenButton = wx.Button(self.panel, -1, _(u"Sh&orten URL"), size=wx.DefaultSize) - self.unshortenButton = wx.Button(self.panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) - self.shortenButton.Disable() - self.unshortenButton.Disable() - self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize) - self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Sen&d"), size=wx.DefaultSize) - self.okButton.SetDefault() - cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) - self.buttonsBox = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) - self.buttonsBox.Add(self.attach, 0, wx.ALL, 5) - self.mainBox.Add(self.buttonsBox, 0, wx.ALL, 5) - self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox1.Add(self.shortenButton, 0, wx.ALL, 5) - self.buttonsBox1.Add(self.unshortenButton, 0, wx.ALL, 5) - self.buttonsBox1.Add(self.translateButton, 0, wx.ALL, 5) - self.mainBox.Add(self.buttonsBox1, 0, wx.ALL, 5) - self.buttonsBox3 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox3.Add(self.okButton, 0, wx.ALL, 5) - self.buttonsBox3.Add(cancelButton, 0, wx.ALL, 5) - self.mainBox.Add(self.buttonsBox3, 0, wx.ALL, 5) - self.panel.SetSizer(self.mainBox) - self.SetClientSize(self.mainBox.CalcMin()) - - def __init__(self, title, message, users, *args, **kwargs): - super(dm, self).__init__() - self.createControls(title, message, users) -# self.onTimer(wx.EVT_CHAR_HOOK) -# self.SetClientSize(self.mainBox.CalcMin()) - - def get_user(self): - return self.cb.GetValue() - - def set_user(self, user): - return self.cb.SetValue(user) - -class reply(textLimited): - - def get_image(self): - openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) - if openFileDialog.ShowModal() == wx.ID_CANCEL: - return None - return open(openFileDialog.GetPath(), "rb") - - def createControls(self, title, message, text): - self.mainBox = wx.BoxSizer(wx.VERTICAL) - self.createTextArea(message, text) - self.mainBox.Add(self.textBox, 0, wx.ALL, 5) - self.usersbox = wx.BoxSizer(wx.VERTICAL) - self.mentionAll = wx.CheckBox(self.panel, -1, _(u"&Mention to all"), size=wx.DefaultSize) - self.mentionAll.Disable() - self.usersbox.Add(self.mentionAll, 0, wx.ALL, 5) - self.checkboxes = [] - for i in self.users: - user_checkbox = wx.CheckBox(self.panel, -1, "@"+i, size=wx.DefaultSize) - self.checkboxes.append(user_checkbox) - self.usersbox.Add(self.checkboxes[-1], 0, wx.ALL, 5) - self.mainBox.Add(self.usersbox, 0, wx.ALL, 10) - self.long_tweet = wx.CheckBox(self.panel, -1, _(u"&Long tweet")) - self.upload_image = wx.Button(self.panel, -1, _(u"&Upload image..."), size=wx.DefaultSize) - self.spellcheck = wx.Button(self.panel, -1, _("Check &spelling..."), size=wx.DefaultSize) - self.attach = wx.Button(self.panel, -1, _(u"&Attach audio..."), size=wx.DefaultSize) - self.shortenButton = wx.Button(self.panel, -1, _(u"Sh&orten URL"), size=wx.DefaultSize) - self.unshortenButton = wx.Button(self.panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) - self.shortenButton.Disable() - self.unshortenButton.Disable() - self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize) - self.autocompletionButton = wx.Button(self.panel, -1, _(u"Auto&complete users")) - self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Sen&d"), size=wx.DefaultSize) - self.okButton.SetDefault() - cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) - self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 10) - self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10) - self.buttonsBox1.Add(self.attach, 0, wx.ALL, 10) - self.mainBox.Add(self.buttonsBox1, 0, wx.ALL, 10) - self.buttonsBox2 = wx.BoxSizer(wx.HORIZONTAL) - self.buttonsBox2.Add(self.shortenButton, 0, wx.ALL, 10) - self.buttonsBox2.Add(self.unshortenButton, 0, wx.ALL, 10) - self.buttonsBox2.Add(self.translateButton, 0, wx.ALL, 10) - self.mainBox.Add(self.buttonsBox2, 0, wx.ALL, 10) - self.ok_cancelSizer = wx.BoxSizer(wx.HORIZONTAL) - self.ok_cancelSizer.Add(self.autocompletionButton, 0, wx.ALL, 10) - self.ok_cancelSizer.Add(self.okButton, 0, wx.ALL, 10) - self.ok_cancelSizer.Add(cancelButton, 0, wx.ALL, 10) - self.mainBox.Add(self.ok_cancelSizer, 0, wx.ALL, 10) - selectId = wx.ID_ANY - self.Bind(wx.EVT_MENU, self.onSelect, id=selectId) - self.accel_tbl = wx.AcceleratorTable([ - (wx.ACCEL_CTRL, ord('A'), selectId), - ]) - self.SetAcceleratorTable(self.accel_tbl) - self.panel.SetSizer(self.mainBox) - - def __init__(self, title, message, text, users=[], *args, **kwargs): - self.users = users - super(reply, self).__init__() - self.shift=False - self.createControls(message, title, text) - self.SetClientSize(self.mainBox.CalcMin()) - -class viewTweet(widgetUtils.BaseDialog): - def set_title(self, lenght): - self.SetTitle(_(u"Tweet - %i characters ") % (lenght,)) - - def __init__(self, text, rt_count, favs_count, source, date="", *args, **kwargs): - super(viewTweet, self).__init__(None, size=(850,850)) - panel = wx.Panel(self) - label = wx.StaticText(panel, -1, _(u"Tweet")) - self.text = wx.TextCtrl(panel, -1, text, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180)) - dc = wx.WindowDC(self.text) - dc.SetFont(self.text.GetFont()) - (x, y) = dc.GetMultiLineTextExtent("0"*140) - self.text.SetSize((x, y)) - self.text.SetFocus() - textBox = wx.BoxSizer(wx.HORIZONTAL) - textBox.Add(label, 0, wx.ALL, 5) - textBox.Add(self.text, 1, wx.EXPAND, 5) - mainBox = wx.BoxSizer(wx.VERTICAL) - mainBox.Add(textBox, 0, wx.ALL, 5) - label2 = wx.StaticText(panel, -1, _(u"Image description")) - self.image_description = wx.TextCtrl(panel, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180)) - dc = wx.WindowDC(self.image_description) - dc.SetFont(self.image_description.GetFont()) - (x, y) = dc.GetMultiLineTextExtent("0"*450) - self.image_description.SetSize((x, y)) - self.image_description.Enable(False) - iBox = wx.BoxSizer(wx.HORIZONTAL) - iBox.Add(label2, 0, wx.ALL, 5) - iBox.Add(self.image_description, 1, wx.EXPAND, 5) - mainBox.Add(iBox, 0, wx.ALL, 5) - rtCountLabel = wx.StaticText(panel, -1, _(u"Retweets: ")) - rtCount = wx.TextCtrl(panel, -1, rt_count, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) - rtBox = wx.BoxSizer(wx.HORIZONTAL) - rtBox.Add(rtCountLabel, 0, wx.ALL, 5) - rtBox.Add(rtCount, 0, wx.ALL, 5) - favsCountLabel = wx.StaticText(panel, -1, _(u"Likes: ")) - favsCount = wx.TextCtrl(panel, -1, favs_count, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) - favsBox = wx.BoxSizer(wx.HORIZONTAL) - favsBox.Add(favsCountLabel, 0, wx.ALL, 5) - favsBox.Add(favsCount, 0, wx.ALL, 5) - sourceLabel = wx.StaticText(panel, -1, _(u"Source: ")) - sourceTweet = wx.TextCtrl(panel, -1, source, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) - sourceBox = wx.BoxSizer(wx.HORIZONTAL) - sourceBox.Add(sourceLabel, 0, wx.ALL, 5) - sourceBox.Add(sourceTweet, 0, wx.ALL, 5) - dateLabel = wx.StaticText(panel, -1, _(u"Date: ")) - dateTweet = wx.TextCtrl(panel, -1, date, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) - dc = wx.WindowDC(dateTweet) - dc.SetFont(dateTweet.GetFont()) - (x, y) = dc.GetTextExtent("0"*100) - dateTweet.SetSize((x, y)) - dateBox = wx.BoxSizer(wx.HORIZONTAL) - dateBox.Add(dateLabel, 0, wx.ALL, 5) - dateBox.Add(dateTweet, 0, wx.ALL, 5) - infoBox = wx.BoxSizer(wx.HORIZONTAL) - infoBox.Add(rtBox, 0, wx.ALL, 5) - infoBox.Add(favsBox, 0, wx.ALL, 5) - infoBox.Add(sourceBox, 0, wx.ALL, 5) - mainBox.Add(infoBox, 0, wx.ALL, 5) - mainBox.Add(dateBox, 0, wx.ALL, 5) - self.share = wx.Button(panel, wx.ID_ANY, _("Copy link to clipboard")) - self.share.Enable(False) - self.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize) - self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) - self.unshortenButton.Disable() - self.translateButton = wx.Button(panel, -1, _(u"&Translate..."), size=wx.DefaultSize) - cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) - cancelButton.SetDefault() - buttonsBox = wx.BoxSizer(wx.HORIZONTAL) - buttonsBox.Add(self.share, 0, wx.ALL, 5) - buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) - buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5) - buttonsBox.Add(self.translateButton, 0, wx.ALL, 5) - buttonsBox.Add(cancelButton, 0, wx.ALL, 5) - mainBox.Add(buttonsBox, 0, wx.ALL, 5) - selectId = wx.ID_ANY - self.Bind(wx.EVT_MENU, self.onSelect, id=selectId) - self.accel_tbl = wx.AcceleratorTable([ - (wx.ACCEL_CTRL, ord('A'), selectId), - ]) - self.SetAcceleratorTable(self.accel_tbl) - panel.SetSizer(mainBox) - self.SetClientSize(mainBox.CalcMin()) - - def set_text(self, text): - self.text.ChangeValue(text) - - def get_text(self): - return self.text.GetValue() - - def set_image_description(self, desc): - self.image_description.Enable(True) - if len(self.image_description.GetValue()) == 0: - self.image_description.SetValue(desc) - else: - self.image_description.SetValue(self.image_description.GetValue()+"\n"+desc) - - def text_focus(self): - self.text.SetFocus() - - def onSelect(self, ev): - self.text.SelectAll() - - def enable_button(self, buttonName): - if hasattr(self, buttonName): - return getattr(self, buttonName).Enable() - -class viewNonTweet(widgetUtils.BaseDialog): - - def __init__(self, text, date="", *args, **kwargs): - super(viewNonTweet, self).__init__(None, size=(850,850)) - self.SetTitle(_(u"View")) - panel = wx.Panel(self) - label = wx.StaticText(panel, -1, _(u"Item")) - self.text = wx.TextCtrl(parent=panel, id=-1, value=text, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180)) - dc = wx.WindowDC(self.text) - dc.SetFont(self.text.GetFont()) - (x, y) = dc.GetMultiLineTextExtent("0"*140) - self.text.SetSize((x, y)) - self.text.SetFocus() - textBox = wx.BoxSizer(wx.HORIZONTAL) - textBox.Add(label, 0, wx.ALL, 5) - textBox.Add(self.text, 1, wx.EXPAND, 5) - mainBox = wx.BoxSizer(wx.VERTICAL) - mainBox.Add(textBox, 0, wx.ALL, 5) - if date != "": - dateLabel = wx.StaticText(panel, -1, _(u"Date: ")) - date = wx.TextCtrl(panel, -1, date, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) - dc = wx.WindowDC(date) - dc.SetFont(date.GetFont()) - (x, y) = dc.GetTextExtent("0"*100) - date.SetSize((x, y)) - dateBox = wx.BoxSizer(wx.HORIZONTAL) - dateBox.Add(dateLabel, 0, wx.ALL, 5) - dateBox.Add(date, 0, wx.ALL, 5) - mainBox.Add(dateBox, 0, wx.ALL, 5) - self.share = wx.Button(panel, wx.ID_ANY, _("Copy link to clipboard")) - self.share.Enable(False) - self.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize) - self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) - self.unshortenButton.Disable() - self.translateButton = wx.Button(panel, -1, _(u"&Translate..."), size=wx.DefaultSize) - cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) - cancelButton.SetDefault() - buttonsBox = wx.BoxSizer(wx.HORIZONTAL) - buttonsBox.Add(self.share, 0, wx.ALL, 5) - buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) - buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5) - buttonsBox.Add(self.translateButton, 0, wx.ALL, 5) - buttonsBox.Add(cancelButton, 0, wx.ALL, 5) - mainBox.Add(buttonsBox, 0, wx.ALL, 5) - selectId = wx.ID_ANY - self.Bind(wx.EVT_MENU, self.onSelect, id=selectId) - self.accel_tbl = wx.AcceleratorTable([ - (wx.ACCEL_CTRL, ord('A'), selectId), - ]) - self.SetAcceleratorTable(self.accel_tbl) - panel.SetSizer(mainBox) - self.SetClientSize(mainBox.CalcMin()) - - def onSelect(self, ev): - self.text.SelectAll() - - def set_text(self, text): - self.text.ChangeValue(text) - - def get_text(self): - return self.text.GetValue() - - def text_focus(self): - self.text.SetFocus() - - def enable_button(self, buttonName): - if hasattr(self, buttonName): - return getattr(self, buttonName).Enable() From 6d505c6fe7fde6772f7bfcb32dc4b9c3bd30d435 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 16:11:09 -0600 Subject: [PATCH 08/33] Added new code for Tweet GUI and attachment of photos and gifts --- src/controller/messages.py | 253 +++++----- src/wxUI/dialogs/twitterDialogs/__init__.py | 1 + .../dialogs/twitterDialogs/tweetDialogs.py | 457 ++++++++++++++++++ 3 files changed, 601 insertions(+), 110 deletions(-) create mode 100644 src/wxUI/dialogs/twitterDialogs/__init__.py create mode 100644 src/wxUI/dialogs/twitterDialogs/tweetDialogs.py diff --git a/src/controller/messages.py b/src/controller/messages.py index a748464d..9417ad90 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -1,123 +1,67 @@ # -*- coding: utf-8 -*- -import re -import platform +import os import arrow import languageHandler -system = platform.system() +import wx import widgetUtils import output -import url_shortener import sound import config from pubsub import pub from twitter_text import parse_tweet -if system == "Windows": - from wxUI.dialogs import message, urlList - from wxUI import commonMessageDialogs - from extra import translator, SpellChecker, autocompletionUsers - from extra.AudioUploader import audioUploader -elif system == "Linux": - from gtkUI.dialogs import message +from wxUI.dialogs import twitterDialogs, urlList +#from wxUI.dialogs import twitterDialogs +from wxUI import commonMessageDialogs +from extra import translator, SpellChecker, autocompletionUsers +from extra.AudioUploader import audioUploader from sessions.twitter import utils -from . import attach class basicTweet(object): """ This class handles the tweet main features. Other classes should derive from this class.""" - def __init__(self, session, title, caption, text, messageType="tweet", max=280, *args, **kwargs): + def __init__(self, session, title, caption, text="", messageType="tweet", max=280, *args, **kwargs): super(basicTweet, self).__init__() self.max = max self.title = title self.session = session - self.message = getattr(message, messageType)(title, caption, text, *args, **kwargs) + self.message = getattr(twitterDialogs, messageType)(title=title, caption=caption, message=text, *args, **kwargs) widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) - widgetUtils.connect_event(self.message.attach, widgetUtils.BUTTON_PRESSED, self.attach) + widgetUtils.connect_event(self.message.add_audio, widgetUtils.BUTTON_PRESSED, self.attach) widgetUtils.connect_event(self.message.text, widgetUtils.ENTERED_TEXT, self.text_processor) - widgetUtils.connect_event(self.message.shortenButton, widgetUtils.BUTTON_PRESSED, self.shorten) - widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten) - widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) - if hasattr(self.message, "long_tweet"): - widgetUtils.connect_event(self.message.long_tweet, widgetUtils.CHECKBOX, self.text_processor) - if config.app["app-settings"]["remember_mention_and_longtweet"]: - self.message.long_tweet.SetValue(config.app["app-settings"]["longtweet"]) + widgetUtils.connect_event(self.message.translate, widgetUtils.BUTTON_PRESSED, self.translate) + if hasattr(self.message, "add"): + widgetUtils.connect_event(self.message.add, widgetUtils.BUTTON_PRESSED, self.on_attach) self.attachments = [] def translate(self, event=None): dlg = translator.gui.translateDialog() if dlg.get_response() == widgetUtils.OK: - text_to_translate = self.message.get_text() + text_to_translate = self.message.text.GetValue() language_dict = translator.translator.available_languages() for k in language_dict: if language_dict[k] == dlg.dest_lang.GetStringSelection(): dst = k msg = translator.translator.translate(text=text_to_translate, target=dst) - self.message.set_text(msg) + self.message.text.ChangeValue(msg) self.text_processor() - self.message.text_focus() + self.message.text.SetFocus() output.speak(_(u"Translated")) else: return - def shorten(self, event=None): - urls = utils.find_urls_in_text(self.message.get_text()) - if len(urls) == 0: - output.speak(_(u"There's no URL to be shortened")) - self.message.text_focus() - elif len(urls) == 1: - self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.shorten(urls[0]))) - output.speak(_(u"URL shortened")) - self.text_processor() - self.message.text_focus() - elif len(urls) > 1: - list_urls = urlList.urlList() - list_urls.populate_list(urls) - if list_urls.get_response() == widgetUtils.OK: - self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.shorten(list_urls.get_string()))) - output.speak(_(u"URL shortened")) - self.text_processor() - self.message.text_focus() - - def unshorten(self, event=None): - urls = utils.find_urls_in_text(self.message.get_text()) - if len(urls) == 0: - output.speak(_(u"There's no URL to be expanded")) - self.message.text_focus() - elif len(urls) == 1: - self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.unshorten(urls[0]))) - output.speak(_(u"URL expanded")) - self.text_processor() - self.message.text_focus() - elif len(urls) > 1: - list_urls = urlList.urlList() - list_urls.populate_list(urls) - if list_urls.get_response() == widgetUtils.OK: - self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.unshorten(list_urls.get_string()))) - output.speak(_(u"URL expanded")) - self.text_processor() - self.message.text_focus() - def text_processor(self, *args, **kwargs): - if len(self.message.get_text()) > 1: - self.message.enable_button("shortenButton") - self.message.enable_button("unshortenButton") - else: - self.message.disable_button("shortenButton") - self.message.disable_button("unshortenButton") - if self.message.get("long_tweet") == False and hasattr(self, "max"): - text = self.message.get_text() - results = parse_tweet(text) - self.message.set_title(_(u"%s - %s of %d characters") % (self.title, results.weightedLength, self.max)) - if results.weightedLength > self.max: - self.session.sound.play("max_length.ogg") - else: - self.message.set_title(_(u"%s - %s characters") % (self.title, len(self.message.get_text()))) + text = self.message.text.GetValue() + results = parse_tweet(text) + self.message.SetTitle(_("%s - %s of %d characters") % (self.title, results.weightedLength, self.max)) + if results.weightedLength > self.max: + self.session.sound.play("max_length.ogg") def spellcheck(self, event=None): - text = self.message.get_text() + text = self.message.text.GetValue() checker = SpellChecker.spellchecker.spellChecker(text, "") if hasattr(checker, "fixed_text"): - self.message.set_text(checker.fixed_text) + self.message.text.ChangeValue(checker.fixed_text) self.text_processor() - self.message.text_focus() + self.message.text.SetFocus() def attach(self, *args, **kwargs): def completed_callback(dlg): @@ -125,48 +69,145 @@ class basicTweet(object): pub.unsubscribe(dlg.uploaderDialog.update, "uploading") dlg.uploaderDialog.destroy() if "sndup.net/" in url: - self.message.set_text(self.message.get_text()+url+" #audio") + self.message.text.ChangeValue(self.message.text.GetValue()+url+" #audio") self.text_processor() else: commonMessageDialogs.common_error(url) - dlg.cleanup() dlg = audioUploader.audioUploader(self.session.settings, completed_callback) - self.message.text_focus() + self.message.text.SetFocus() + + def can_attach(self): + if len(self.attachments) == 0: + return True + elif len(self.attachments) == 1 and (self.attachments[0]["type"] == "video" or self.attachments[0]["type"] == "gif"): + return False + elif len(self.attachments) < 4: + return True + return False + + def on_attach(self, *args, **kwargs): + can_attach = self.can_attach() + menu = self.message.attach_menu(can_attach) + self.message.Bind(wx.EVT_MENU, self.on_attach_image, self.message.add_image) + self.message.Bind(wx.EVT_MENU, self.on_attach_video, self.message.add_video) + self.message.Bind(wx.EVT_MENU, self.on_attach_poll, self.message.add_poll) + self.message.PopupMenu(menu, self.message.add.GetPosition()) + + def on_attach_image(self, *args, **kwargs): + image, description = self.message.get_image() + if image != None: + if image.endswith("gif"): + image_type = "gif" + else: + image_type = "photo" + imageInfo = {"type": image_type, "file": image, "description": description} + if len(self.attachments) > 0 and image_type == "gif": + return self.message.unable_to_attach_file() + self.attachments.append(imageInfo) + self.message.add_item(item=[os.path.basename(imageInfo["file"]), imageInfo["type"], imageInfo["description"]]) + self.text_processor() + + def on_attach_video(self, *args, **kwargs): + video = self.message.get_video() + if video != None: + videoInfo = {"type": "video", "file": video, "description": ""} + if len(self.attachments) > 0: + return self.message.unable_to_attach_file() + self.attachments.append(videoInfo) + self.message.add_item(item=[os.path.basename(videoInfo["file"]), videoInfo["type"], videoInfo["description"]]) + self.text_processor() + + def on_attach_poll(self, *args, **kwargs): + pass class tweet(basicTweet): def __init__(self, session, title, caption, text, max=280, messageType="tweet", *args, **kwargs): + self.thread = [] super(tweet, self).__init__(session, title, caption, text, messageType, max, *args, **kwargs) self.image = None - widgetUtils.connect_event(self.message.upload_image, widgetUtils.BUTTON_PRESSED, self.upload_image) - widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) + widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) + if hasattr(self.message, "add_tweet"): + widgetUtils.connect_event(self.message.add_tweet, widgetUtils.BUTTON_PRESSED, self.add_tweet) + widgetUtils.connect_event(self.message.remove_tweet, widgetUtils.BUTTON_PRESSED, self.remove_tweet) + widgetUtils.connect_event(self.message.remove_attachment, widgetUtils.BUTTON_PRESSED, self.remove_attachment) self.text_processor() - def upload_image(self, *args, **kwargs): - a = attach.attach() - if len(a.attachments) != 0: - self.attachments = a.attachments - def autocomplete_users(self, *args, **kwargs): c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) c.show_menu() + def add_tweet(self, event, update_gui=True, *args, **kwargs): + text = self.message.text.GetValue() + attachments = self.attachments[::] + tweetdata = dict(text=text, attachments=attachments) + self.thread.append(tweetdata) + if update_gui: + self.message.reset_controls() + self.message.add_item(item=[text, len(attachments)], list_type="tweet") + self.message.text.SetFocus() + self.text_processor() + + def get_tweet_data(self): + self.add_tweet(event=None, update_gui=False) + return self.thread + + def text_processor(self, *args, **kwargs): + super(tweet, self).text_processor(*args, **kwargs) + if len(self.thread) > 0: + self.message.tweets.Enable(True) + self.message.remove_tweet.Enable(True) + else: + self.message.tweets.Enable(False) + self.message.remove_tweet.Enable(False) + if len(self.attachments) > 0: + self.message.attachments.Enable(True) + self.message.remove_attachment.Enable(True) + else: + self.message.attachments.Enable(False) + self.message.remove_attachment.Enable(False) + + def remove_tweet(self, *args, **kwargs): + tweet = self.message.tweets.GetFocusedItem() + if tweet > -1 and len(self.thread) > tweet: + self.thread.pop(tweet) + self.message.remove_item(list_type="tweet") + self.text_processor() + self.message.text.SetFocus() + + def remove_attachment(self, *args, **kwargs): + attachment = self.message.attachments.GetFocusedItem() + if attachment > -1 and len(self.attachments) > attachment: + self.attachments.pop(attachment) + self.message.remove_item(list_type="attachment") + self.text_processor() + self.message.text.SetFocus() + class reply(tweet): def __init__(self, session, title, caption, text, users=[], ids=[]): super(reply, self).__init__(session, title, caption, text, messageType="reply", users=users) self.ids = ids self.users = users if len(users) > 0: - widgetUtils.connect_event(self.message.mentionAll, widgetUtils.CHECKBOX, self.mention_all) - self.message.enable_button("mentionAll") + widgetUtils.connect_event(self.message.mention_all, widgetUtils.CHECKBOX, self.mention_all) + self.message.mention_all.Enable(True) if config.app["app-settings"]["remember_mention_and_longtweet"]: - self.message.mentionAll.SetValue(config.app["app-settings"]["mention_all"]) + self.message.mention_all.SetValue(config.app["app-settings"]["mention_all"]) self.mention_all() - self.message.set_cursor_at_end() + self.message.text.SetInsertionPoint(len(self.message.text.GetValue())) self.text_processor() + def text_processor(self, *args, **kwargs): + super(tweet, self).text_processor(*args, **kwargs) + if len(self.attachments) > 0: + self.message.attachments.Enable(True) + self.message.remove_attachment.Enable(True) + else: + self.message.attachments.Enable(False) + self.message.remove_attachment.Enable(False) + def mention_all(self, *args, **kwargs): - if self.message.mentionAll.GetValue() == True: + if self.message.mention_all.GetValue() == True: for i in self.message.checkboxes: i.SetValue(True) i.Hide() @@ -190,14 +231,14 @@ class reply(tweet): return people class dm(basicTweet): - def __init__(self, session, title, caption, text): - super(dm, self).__init__(session, title, caption, text, messageType="dm", max=10000) - widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) + def __init__(self, session, title, caption, users): + super(dm, self).__init__(session, title, caption, messageType="dm", max=10000, users=users) + widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) self.text_processor() widgetUtils.connect_event(self.message.cb, widgetUtils.ENTERED_TEXT, self.user_changed) def user_changed(self, *args, **kwargs): - self.title = _("Direct message to %s") % (self.message.get_user()) + self.title = _("Direct message to %s") % (self.message.cb.GetValue()) self.text_processor() def autocomplete_users(self, *args, **kwargs): @@ -264,29 +305,21 @@ class viewTweet(basicTweet): for z in tweet.retweeted_status.extended_entities["media"]: if "ext_alt_text" in z and z["ext_alt_text"] != None: image_description.append(z["ext_alt_text"]) - self.message = message.viewTweet(text, rt_count, favs_count, source, date) + self.message = twitterDialogs.viewTweet(text, rt_count, favs_count, source, date) results = parse_tweet(text) self.message.set_title(results.weightedLength) [self.message.set_image_description(i) for i in image_description] else: self.title = _(u"View item") text = tweet - self.message = message.viewNonTweet(text, date) + self.message = twitterDialogs.viewNonTweet(text, date) widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) if item_url != "": self.message.enable_button("share") widgetUtils.connect_event(self.message.share, widgetUtils.BUTTON_PRESSED, self.share) self.item_url = item_url widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) - if self.contain_urls() == True: - self.message.enable_button("unshortenButton") - widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten) - self.message.get_response() - - def contain_urls(self): - if len(utils.find_urls_in_text(self.message.get_text())) > 0: - return True - return False + self.message.ShowModal() def clear_text(self, text): urls = utils.find_urls_in_text(text) diff --git a/src/wxUI/dialogs/twitterDialogs/__init__.py b/src/wxUI/dialogs/twitterDialogs/__init__.py new file mode 100644 index 00000000..c6b568e0 --- /dev/null +++ b/src/wxUI/dialogs/twitterDialogs/__init__.py @@ -0,0 +1 @@ +from .tweetDialogs import tweet, reply, dm, viewTweet, viewNonTweet \ No newline at end of file diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py new file mode 100644 index 00000000..d07f10da --- /dev/null +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -0,0 +1,457 @@ +""" GUI dialogs for tweet writing and displaying. """ +import wx +from typing import List + +class tweet(wx.Dialog): + def __init__(self, title: str, caption: str, message: str = "", max_length: int = 280, thread_mode: bool = True, *args, **kwds) -> None: + """ Creates the basic Tweet dialog. This might be considered the base class for other dialogs. + title str: title to be used in the dialog. + caption str: This is the text to be placed alongside the text field. + message str: Text to be inserted in the tweet. + max_length int: Maximum amount of characters the tweet will accept. By default is 280 chahracters. + thread_mode bool: If set to False, disables the button that allows to make threads by adding more tweets. + """ + super(tweet, self).__init__(parent=None, *args, **kwds) + self.SetTitle(title) + self.create_controls(max_length=max_length, caption=caption, message=message, thread_mode=thread_mode) + + def create_controls(self, message: str, caption: str, max_length: int, thread_mode: bool) -> None: + panel = wx.Panel(self) + mainBox = wx.BoxSizer(wx.VERTICAL) + text_sizer = wx.BoxSizer(wx.VERTICAL) + mainBox.Add(text_sizer, 1, wx.EXPAND, 0) + label_1 = wx.StaticText(panel, wx.ID_ANY, caption) + text_sizer.Add(label_1, 0, 0, 0) + self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) + self.text.SetMinSize((1000, 158)) + self.text.SetMaxLength(max_length) + text_sizer.Add(self.text, 1, wx.EXPAND, 0) + list_sizer = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(list_sizer, 1, wx.EXPAND, 0) + Attachment_sizer = wx.BoxSizer(wx.VERTICAL) + list_sizer.Add(Attachment_sizer, 1, wx.EXPAND, 0) + label_2 = wx.StaticText(panel, wx.ID_ANY, _("Attachments")) + Attachment_sizer.Add(label_2, 0, 0, 0) + self.attachments = wx.ListCtrl(panel, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES) + self.attachments.AppendColumn(_("File")) + self.attachments.AppendColumn(_("Type")) + self.attachments.AppendColumn(_("Description")) + Attachment_sizer.Add(self.attachments, 1, wx.EXPAND, 0) + self.remove_attachment = wx.Button(panel, wx.ID_ANY, _("Delete attachment")) + self.remove_attachment.Enable(False) + Attachment_sizer.Add(self.remove_attachment, 0, 0, 0) + tweet_sizer = wx.BoxSizer(wx.VERTICAL) + list_sizer.Add(tweet_sizer, 1, wx.EXPAND, 0) + label_3 = wx.StaticText(panel, wx.ID_ANY, _("Added Tweets")) + tweet_sizer.Add(label_3, 0, 0, 0) + self.tweets = wx.ListCtrl(panel, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES) + self.tweets.AppendColumn(_("Text")) + self.tweets.AppendColumn(_("Attachments")) + self.tweets.Enable(False) + tweet_sizer.Add(self.tweets, 1, wx.EXPAND, 0) + self.remove_tweet = wx.Button(panel, wx.ID_ANY, _("Delete tweet")) + self.remove_tweet.Enable(False) + tweet_sizer.Add(self.remove_tweet, 0, 0, 0) + btn_sizer_1 = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(btn_sizer_1, 1, wx.EXPAND, 0) + self.add = wx.Button(panel, wx.ID_ANY, _("A&dd...")) + btn_sizer_1.Add(self.add, 0, 0, 0) + self.add_tweet = wx.Button(panel, wx.ID_ANY, _("Add tweet")) + self.add_tweet.Enable(thread_mode) + btn_sizer_1.Add(self.add_tweet, 0, 0, 0) + self.add_audio = wx.Button(panel, wx.ID_ANY, _("&Attach audio...")) + btn_sizer_1.Add(self.add_audio, 0, 0, 0) + btn_sizer_2 = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(btn_sizer_2, 1, wx.EXPAND, 0) + self.autocomplete_users = wx.Button(panel, wx.ID_ANY, _("Auto&complete users")) + btn_sizer_2.Add(self.autocomplete_users, 0, 0, 0) + self.spellcheck = wx.Button(panel, wx.ID_ANY, _("Check &spelling...")) + btn_sizer_2.Add(self.spellcheck, 0, 0, 0) + self.translate = wx.Button(panel, wx.ID_ANY, _("&Translate")) + btn_sizer_2.Add(self.translate, 0, 0, 0) + ok_cancel_sizer = wx.StdDialogButtonSizer() + mainBox.Add(ok_cancel_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 4) + self.send = wx.Button(panel, wx.ID_OK, _("Sen&d")) + self.send.SetDefault() + ok_cancel_sizer.Add(self.send, 0, 0, 0) + self.cancel = wx.Button(panel, wx.ID_CANCEL, "") + ok_cancel_sizer.AddButton(self.cancel) + ok_cancel_sizer.Realize() + panel.SetSizer(mainBox) + self.Fit() + self.SetAffirmativeId(self.send.GetId()) + self.SetEscapeId(self.cancel.GetId()) + self.Layout() + + def handle_keys(self, event: wx.Event, *args, **kwargs) -> None: + """ Allows to react to certain keyboard events from the text control. """ + shift=event.ShiftDown() + if event.GetKeyCode() == wx.WXK_RETURN and shift==False and hasattr(self,'send'): + self.EndModal(wx.ID_OK) + else: + event.Skip() + + def reset_controls(self) -> None: + """ Resetss text control and attachments to their default, empty values. This is used while adding more tweets in a thread. """ + self.text.ChangeValue("") + self.attachments.DeleteAllItems() + + def add_item(self, list_type: str = "attachment", item: List[str] = []) -> None: + """ Adds an item to a list control. Item should be a list with the same amount of items for each column present in the ListCtrl. """ + if list_type == "attachment": + self.attachments.Append(item) + else: + self.tweets.Append(item) + + def remove_item(self, list_type: str = "attachment") -> None: + if list_type == "attachment": + item = self.attachments.GetFocusedItem() + if item > -1: + self.attachments.DeleteItem(item) + else: + item = self.tweets.GetFocusedItem() + if item > -1: + self.tweets.DeleteItem(item) + + def attach_menu(self, event=None, enabled=True, *args, **kwargs): + menu = wx.Menu() + self.add_image = menu.Append(wx.ID_ANY, _("Image")) + self.add_image.Enable(enabled) + self.add_video = menu.Append(wx.ID_ANY, _("Video")) + self.add_video.Enable(enabled) + self.add_poll = menu.Append(wx.ID_ANY, _("Poll")) + self.add_poll.Enable(enabled) + return menu + + def ask_description(self): + dlg = wx.TextEntryDialog(self, _(u"please provide a description"), _(u"Description")) + dlg.ShowModal() + result = dlg.GetValue() + dlg.Destroy() + return result + + def get_image(self): + openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if openFileDialog.ShowModal() == wx.ID_CANCEL: + return (None, None) + dsc = self.ask_description() + return (openFileDialog.GetPath(), dsc) + + def get_video(self): + openFileDialog = wx.FileDialog(self, _("Select the video to be uploaded"), "", "", _("Video files (*.mp4)|*.mp4"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if openFileDialog.ShowModal() == wx.ID_CANCEL: + return None + return openFileDialog.GetPath() + + def unable_to_attach_file(self, *args, **kwargs): + return wx.MessageDialog(self, _("You need to delete other attachments first, as Videos and GIF's cannot be added with other attachments."), _("Error adding attachment"), wx.ICON_ERROR).ShowModal() + +class reply(tweet): + + def __init__(self, users: List[str] = [], *args, **kwargs) -> None: + self.users = users + super(reply, self).__init__(*args, **kwargs) + + def create_controls(self, message: str, caption: str, max_length: int, thread_mode: bool) -> None: + panel = wx.Panel(self) + mainBox = wx.BoxSizer(wx.VERTICAL) + text_sizer = wx.BoxSizer(wx.VERTICAL) + mainBox.Add(text_sizer, 1, wx.EXPAND, 0) + label_1 = wx.StaticText(panel, wx.ID_ANY, caption) + text_sizer.Add(label_1, 0, 0, 0) + self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) + self.text.SetMinSize((1000, 158)) + self.text.SetMaxLength(max_length) + text_sizer.Add(self.text, 1, wx.EXPAND, 0) + list_sizer = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(list_sizer, 1, wx.EXPAND, 0) + Attachment_sizer = wx.BoxSizer(wx.VERTICAL) + list_sizer.Add(Attachment_sizer, 1, wx.EXPAND, 0) + label_2 = wx.StaticText(panel, wx.ID_ANY, _("Attachments")) + Attachment_sizer.Add(label_2, 0, 0, 0) + self.attachments = wx.ListCtrl(panel, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES) + self.attachments.AppendColumn(_("File")) + self.attachments.AppendColumn(_("Type")) + self.attachments.AppendColumn(_("Description")) + Attachment_sizer.Add(self.attachments, 1, wx.EXPAND, 0) + self.remove_attachment = wx.Button(panel, wx.ID_ANY, _("Delete attachment")) + self.remove_attachment.Enable(False) + Attachment_sizer.Add(self.remove_attachment, 0, 0, 0) + user_sizer = wx.BoxSizer(wx.VERTICAL) + list_sizer.Add(user_sizer, 0, 0, 0) + self.mention_all = wx.CheckBox(panel, -1, _(u"&Mention to all"), size=wx.DefaultSize) + self.mention_all.Disable() + user_sizer.Add(self.mention_all, 0, wx.ALL, 5) + self.checkboxes = [] + for i in self.users: + user_checkbox = wx.CheckBox(panel, -1, "@"+i, size=wx.DefaultSize) + self.checkboxes.append(user_checkbox) + user_sizer.Add(self.checkboxes[-1], 0, wx.ALL, 5) + btn_sizer_1 = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(btn_sizer_1, 1, wx.EXPAND, 0) + self.add = wx.Button(panel, wx.ID_ANY, _("A&dd...")) + btn_sizer_1.Add(self.add, 0, 0, 0) + self.add_audio = wx.Button(panel, wx.ID_ANY, _("&Attach audio...")) + btn_sizer_1.Add(self.add_audio, 0, 0, 0) + btn_sizer_2 = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(btn_sizer_2, 1, wx.EXPAND, 0) + self.autocomplete_users = wx.Button(panel, wx.ID_ANY, _("Auto&complete users")) + btn_sizer_2.Add(self.autocomplete_users, 0, 0, 0) + self.spellcheck = wx.Button(panel, wx.ID_ANY, _("Check &spelling...")) + btn_sizer_2.Add(self.spellcheck, 0, 0, 0) + self.translate = wx.Button(panel, wx.ID_ANY, _("&Translate")) + btn_sizer_2.Add(self.translate, 0, 0, 0) + ok_cancel_sizer = wx.StdDialogButtonSizer() + mainBox.Add(ok_cancel_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 4) + self.send = wx.Button(panel, wx.ID_OK, _("Sen&d")) + self.send.SetDefault() + ok_cancel_sizer.Add(self.send, 0, 0, 0) + self.cancel = wx.Button(panel, wx.ID_CANCEL, "") + ok_cancel_sizer.AddButton(self.cancel) + ok_cancel_sizer.Realize() + panel.SetSizer(mainBox) + self.Fit() + self.SetAffirmativeId(self.send.GetId()) + self.SetEscapeId(self.cancel.GetId()) + self.Layout() + +class dm(tweet): + + def __init__(self, users: List[str] = [], *args, **kwargs) -> None: + self.users = users + super(dm, self).__init__(*args, **kwargs) + + def create_controls(self, message: str, caption: str, max_length: int, thread_mode: bool) -> None: + panel = wx.Panel(self) + mainBox = wx.BoxSizer(wx.VERTICAL) + label_recipient = wx.StaticText(panel, -1, _(u"&Recipient")) + self.cb = wx.ComboBox(panel, -1, choices=self.users, value=self.users[0], size=wx.DefaultSize) + self.autocomplete_users = wx.Button(panel, -1, _(u"Auto&complete users")) + recipient_sizer = wx.BoxSizer(wx.HORIZONTAL) + recipient_sizer.Add(label_recipient, 0, 0, 0) + recipient_sizer.Add(self.cb, 1, wx.EXPAND, 0) + mainBox.Add(recipient_sizer, 0, wx.EXPAND, 0) + text_sizer = wx.BoxSizer(wx.VERTICAL) + mainBox.Add(text_sizer, 1, wx.EXPAND, 0) + label_1 = wx.StaticText(panel, wx.ID_ANY, caption) + text_sizer.Add(label_1, 0, 0, 0) + self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) + self.text.SetMinSize((1000, 158)) + self.text.SetMaxLength(max_length) + self.text.SetFocus() + text_sizer.Add(self.text, 1, wx.EXPAND, 0) + list_sizer = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(list_sizer, 1, wx.EXPAND, 0) + Attachment_sizer = wx.BoxSizer(wx.VERTICAL) + list_sizer.Add(Attachment_sizer, 1, wx.EXPAND, 0) + label_2 = wx.StaticText(panel, wx.ID_ANY, _("Attachments")) + Attachment_sizer.Add(label_2, 0, 0, 0) + self.attachments = wx.ListCtrl(panel, wx.ID_ANY, style=wx.BORDER_SUNKEN | wx.LC_HRULES | wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES) + self.attachments.AppendColumn(_("File")) + self.attachments.AppendColumn(_("Type")) + self.attachments.AppendColumn(_("Description")) + Attachment_sizer.Add(self.attachments, 1, wx.EXPAND, 0) + self.remove_attachment = wx.Button(panel, wx.ID_ANY, _("Delete attachment")) + self.remove_attachment.Enable(False) + Attachment_sizer.Add(self.remove_attachment, 0, 0, 0) + btn_sizer_1 = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(btn_sizer_1, 1, wx.EXPAND, 0) + self.add = wx.Button(panel, wx.ID_ANY, _("A&dd...")) + btn_sizer_1.Add(self.add, 0, 0, 0) + self.add_audio = wx.Button(panel, wx.ID_ANY, _("&Attach audio...")) + btn_sizer_1.Add(self.add_audio, 0, 0, 0) + btn_sizer_2 = wx.BoxSizer(wx.HORIZONTAL) + mainBox.Add(btn_sizer_2, 1, wx.EXPAND, 0) + self.spellcheck = wx.Button(panel, wx.ID_ANY, _("Check &spelling...")) + btn_sizer_2.Add(self.spellcheck, 0, 0, 0) + self.translate = wx.Button(panel, wx.ID_ANY, _("&Translate")) + btn_sizer_2.Add(self.translate, 0, 0, 0) + ok_cancel_sizer = wx.StdDialogButtonSizer() + mainBox.Add(ok_cancel_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 4) + self.send = wx.Button(panel, wx.ID_OK, _("Sen&d")) + self.send.SetDefault() + ok_cancel_sizer.Add(self.send, 0, 0, 0) + self.cancel = wx.Button(panel, wx.ID_CANCEL, "") + ok_cancel_sizer.AddButton(self.cancel) + ok_cancel_sizer.Realize() + panel.SetSizer(mainBox) + self.Fit() + self.SetAffirmativeId(self.send.GetId()) + self.SetEscapeId(self.cancel.GetId()) + self.Layout() + +class viewTweet(wx.Dialog): + def set_title(self, lenght): + self.SetTitle(_(u"Tweet - %i characters ") % (lenght,)) + + def __init__(self, text, rt_count, favs_count, source, date="", *args, **kwargs): + super(viewTweet, self).__init__(None, size=(850,850)) + panel = wx.Panel(self) + label = wx.StaticText(panel, -1, _(u"Tweet")) + self.text = wx.TextCtrl(panel, -1, text, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180)) + dc = wx.WindowDC(self.text) + dc.SetFont(self.text.GetFont()) + (x, y) = dc.GetMultiLineTextExtent("W"*280) + self.text.SetSize((x, y)) + self.text.SetFocus() + textBox = wx.BoxSizer(wx.HORIZONTAL) + textBox.Add(label, 0, wx.ALL, 5) + textBox.Add(self.text, 1, wx.EXPAND, 5) + mainBox = wx.BoxSizer(wx.VERTICAL) + mainBox.Add(textBox, 0, wx.ALL, 5) + label2 = wx.StaticText(panel, -1, _(u"Image description")) + self.image_description = wx.TextCtrl(panel, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180)) + dc = wx.WindowDC(self.image_description) + dc.SetFont(self.image_description.GetFont()) + (x, y) = dc.GetMultiLineTextExtent("0"*450) + self.image_description.SetSize((x, y)) + self.image_description.Enable(False) + iBox = wx.BoxSizer(wx.HORIZONTAL) + iBox.Add(label2, 0, wx.ALL, 5) + iBox.Add(self.image_description, 1, wx.EXPAND, 5) + mainBox.Add(iBox, 0, wx.ALL, 5) + rtCountLabel = wx.StaticText(panel, -1, _(u"Retweets: ")) + rtCount = wx.TextCtrl(panel, -1, rt_count, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) + rtBox = wx.BoxSizer(wx.HORIZONTAL) + rtBox.Add(rtCountLabel, 0, wx.ALL, 5) + rtBox.Add(rtCount, 0, wx.ALL, 5) + favsCountLabel = wx.StaticText(panel, -1, _(u"Likes: ")) + favsCount = wx.TextCtrl(panel, -1, favs_count, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) + favsBox = wx.BoxSizer(wx.HORIZONTAL) + favsBox.Add(favsCountLabel, 0, wx.ALL, 5) + favsBox.Add(favsCount, 0, wx.ALL, 5) + sourceLabel = wx.StaticText(panel, -1, _(u"Source: ")) + sourceTweet = wx.TextCtrl(panel, -1, source, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) + sourceBox = wx.BoxSizer(wx.HORIZONTAL) + sourceBox.Add(sourceLabel, 0, wx.ALL, 5) + sourceBox.Add(sourceTweet, 0, wx.ALL, 5) + dateLabel = wx.StaticText(panel, -1, _(u"Date: ")) + dateTweet = wx.TextCtrl(panel, -1, date, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) + dc = wx.WindowDC(dateTweet) + dc.SetFont(dateTweet.GetFont()) + (x, y) = dc.GetTextExtent("0"*100) + dateTweet.SetSize((x, y)) + dateBox = wx.BoxSizer(wx.HORIZONTAL) + dateBox.Add(dateLabel, 0, wx.ALL, 5) + dateBox.Add(dateTweet, 0, wx.ALL, 5) + infoBox = wx.BoxSizer(wx.HORIZONTAL) + infoBox.Add(rtBox, 0, wx.ALL, 5) + infoBox.Add(favsBox, 0, wx.ALL, 5) + infoBox.Add(sourceBox, 0, wx.ALL, 5) + mainBox.Add(infoBox, 0, wx.ALL, 5) + mainBox.Add(dateBox, 0, wx.ALL, 5) + self.share = wx.Button(panel, wx.ID_ANY, _("Copy link to clipboard")) + self.share.Enable(False) + self.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize) + self.translateButton = wx.Button(panel, -1, _(u"&Translate..."), size=wx.DefaultSize) + cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) + cancelButton.SetDefault() + buttonsBox = wx.BoxSizer(wx.HORIZONTAL) + buttonsBox.Add(self.share, 0, wx.ALL, 5) + buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) + buttonsBox.Add(self.translateButton, 0, wx.ALL, 5) + buttonsBox.Add(cancelButton, 0, wx.ALL, 5) + mainBox.Add(buttonsBox, 0, wx.ALL, 5) + selectId = wx.ID_ANY + self.Bind(wx.EVT_MENU, self.onSelect, id=selectId) + self.accel_tbl = wx.AcceleratorTable([ + (wx.ACCEL_CTRL, ord('A'), selectId), + ]) + self.SetAcceleratorTable(self.accel_tbl) + panel.SetSizer(mainBox) + self.SetClientSize(mainBox.CalcMin()) + + def set_text(self, text): + self.text.ChangeValue(text) + + def get_text(self): + return self.text.GetValue() + + def set_image_description(self, desc): + self.image_description.Enable(True) + if len(self.image_description.GetValue()) == 0: + self.image_description.SetValue(desc) + else: + self.image_description.SetValue(self.image_description.GetValue()+"\n"+desc) + + def text_focus(self): + self.text.SetFocus() + + def onSelect(self, ev): + self.text.SelectAll() + + def enable_button(self, buttonName): + if hasattr(self, buttonName): + return getattr(self, buttonName).Enable() + +class viewNonTweet(wx.Dialog): + + def __init__(self, text, date="", *args, **kwargs): + super(viewNonTweet, self).__init__(None, size=(850,850)) + self.SetTitle(_(u"View")) + panel = wx.Panel(self) + label = wx.StaticText(panel, -1, _(u"Item")) + self.text = wx.TextCtrl(parent=panel, id=-1, value=text, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180)) + dc = wx.WindowDC(self.text) + dc.SetFont(self.text.GetFont()) + (x, y) = dc.GetMultiLineTextExtent("0"*140) + self.text.SetSize((x, y)) + self.text.SetFocus() + textBox = wx.BoxSizer(wx.HORIZONTAL) + textBox.Add(label, 0, wx.ALL, 5) + textBox.Add(self.text, 1, wx.EXPAND, 5) + mainBox = wx.BoxSizer(wx.VERTICAL) + mainBox.Add(textBox, 0, wx.ALL, 5) + if date != "": + dateLabel = wx.StaticText(panel, -1, _(u"Date: ")) + date = wx.TextCtrl(panel, -1, date, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) + dc = wx.WindowDC(date) + dc.SetFont(date.GetFont()) + (x, y) = dc.GetTextExtent("0"*100) + date.SetSize((x, y)) + dateBox = wx.BoxSizer(wx.HORIZONTAL) + dateBox.Add(dateLabel, 0, wx.ALL, 5) + dateBox.Add(date, 0, wx.ALL, 5) + mainBox.Add(dateBox, 0, wx.ALL, 5) + self.share = wx.Button(panel, wx.ID_ANY, _("Copy link to clipboard")) + self.share.Enable(False) + self.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize) + self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) + self.unshortenButton.Disable() + self.translateButton = wx.Button(panel, -1, _(u"&Translate..."), size=wx.DefaultSize) + cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) + cancelButton.SetDefault() + buttonsBox = wx.BoxSizer(wx.HORIZONTAL) + buttonsBox.Add(self.share, 0, wx.ALL, 5) + buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) + buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5) + buttonsBox.Add(self.translateButton, 0, wx.ALL, 5) + buttonsBox.Add(cancelButton, 0, wx.ALL, 5) + mainBox.Add(buttonsBox, 0, wx.ALL, 5) + selectId = wx.ID_ANY + self.Bind(wx.EVT_MENU, self.onSelect, id=selectId) + self.accel_tbl = wx.AcceleratorTable([ + (wx.ACCEL_CTRL, ord('A'), selectId), + ]) + self.SetAcceleratorTable(self.accel_tbl) + panel.SetSizer(mainBox) + self.SetClientSize(mainBox.CalcMin()) + + def onSelect(self, ev): + self.text.SelectAll() + + def set_text(self, text): + self.text.ChangeValue(text) + + def get_text(self): + return self.text.GetValue() + + def text_focus(self): + self.text.SetFocus() + + def enable_button(self, buttonName): + if hasattr(self, buttonName): + return getattr(self, buttonName).Enable() From f0456af65670127dc7b6e1f971a6633a4cd587ee Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 16:12:47 -0600 Subject: [PATCH 09/33] Fixed an issue when uploading Videos to Twitter --- src/sessions/twitter/session.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index a32ead2d..df3ab809 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -204,7 +204,7 @@ class Session(base.baseSession): except TweepyException as e: output.speak(str(e)) val = None - if type(e) != NotFound and type(e) != Forvidden: + if type(e) != NotFound and type(e) != Forbidden: tries = tries+1 time.sleep(5) elif report_failure: @@ -603,7 +603,8 @@ class Session(base.baseSession): media_ids = [] for i in obj["attachments"]: img = self.api_call("media_upload", filename=i["file"]) - self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) + if i["type"] == "photo": + self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) media_ids.append(img.media_id) item = self.api_call(call_name="update_status", status=obj["text"], _sound="tweet_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, media_ids=media_ids) in_reply_to_status_id = item.id @@ -615,6 +616,7 @@ class Session(base.baseSession): media_ids = [] for i in attachments: img = self.api_call("media_upload", filename=i["file"]) - self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) + if i["type"] == "photo": + self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) media_ids.append(img.media_id) item = self.api_call(call_name="update_status", status=text, _sound="reply_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, media_ids=media_ids, *args, **kwargs) From 2b6efef831d8fbe9ffe6a0456450785a2cc5a28a Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 16:13:59 -0600 Subject: [PATCH 10/33] Update code on autocompletion users module to use WX methods from controls in the new Tweet GUI'S --- src/extra/autocompletionUsers/completion.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/extra/autocompletionUsers/completion.py b/src/extra/autocompletionUsers/completion.py index 826dc699..9fa8c8ec 100644 --- a/src/extra/autocompletionUsers/completion.py +++ b/src/extra/autocompletionUsers/completion.py @@ -10,9 +10,9 @@ class autocompletionUsers(object): self.db = storage.storage(session_id) def show_menu(self, mode="tweet"): - position = self.window.get_position() + position = self.window.text.GetInsertionPoint() if mode == "tweet": - text = self.window.get_text() + text = self.window.text.GetValue() text = text[:position] try: pattern = text.split()[-1] @@ -24,14 +24,14 @@ class autocompletionUsers(object): users = self.db.get_users(pattern[1:]) if len(users) > 0: menu.append_options(users) - self.window.popup_menu(menu) + self.window.PopupMenu(menu, self.window.text.GetPosition()) menu.destroy() else: output.speak(_(u"There are no results in your users database")) else: output.speak(_(u"Autocompletion only works for users.")) elif mode == "dm": - text = self.window.get_user() + text = self.window.cb.GetValue() try: pattern = text.split()[-1] except IndexError: @@ -41,7 +41,7 @@ class autocompletionUsers(object): users = self.db.get_users(pattern) if len(users) > 0: menu.append_options(users) - self.window.popup_menu(menu) + self.window.PopupMenu(menu, self.window.text.GetPosition()) menu.destroy() else: output.speak(_(u"There are no results in your users database")) From cfc25eb89a3259eab292a412fa42afdec1d7b4b1 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 16:14:45 -0600 Subject: [PATCH 11/33] Implemented tweet, reply and direct message in base buffer --- src/controller/buffers/twitter/base.py | 83 +++++--------------------- 1 file changed, 16 insertions(+), 67 deletions(-) diff --git a/src/controller/buffers/twitter/base.py b/src/controller/buffers/twitter/base.py index 8da54709..619b7f8f 100644 --- a/src/controller/buffers/twitter/base.py +++ b/src/controller/buffers/twitter/base.py @@ -85,41 +85,15 @@ class BaseBuffer(base.Buffer): def post_status(self, *args, **kwargs): item = None - title = _(u"Tweet") - caption = _(u"Write the tweet here") + title = _("Tweet") + caption = _("Write the tweet here") tweet = messages.tweet(self.session, title, caption, "") - if tweet.message.get_response() == widgetUtils.OK: - if config.app["app-settings"]["remember_mention_and_longtweet"]: - config.app["app-settings"]["longtweet"] = tweet.message.long_tweet.GetValue() - config.app.write() - text = tweet.message.get_text() - if len(text) > 280 and tweet.message.get("long_tweet") == True: - if not hasattr(tweet, "attachments"): - text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text) - else: - text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1) - if not hasattr(tweet, "attachments") or len(tweet.attachments) == 0: - item = self.session.api_call(call_name="update_status", status=text, _sound="tweet_send.ogg", tweet_mode="extended") - else: - call_threaded(self.post_with_media, text=text, attachments=tweet.attachments) - # We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item. - # but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly. -# if item != None: -# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"]) - if hasattr(tweet.message, "destroy"): tweet.message.destroy() - self.session.settings.write() - - def post_with_media(self, text, attachments): - media_ids = [] - for i in attachments: - img = self.session.twitter.media_upload(i["file"]) - self.session.twitter.create_media_metadata(media_id=img.media_id, alt_text=i["description"]) - media_ids.append(img.media_id) - item = self.session.twitter.update_status(status=text, media_ids=media_ids) - # We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item. - # but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly. -# if item != None: -# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"]) + response = tweet.message.ShowModal() + if response == wx.ID_OK: + tweet_data = tweet.get_tweet_data() + call_threaded(self.session.send_tweet, *tweet_data) + if hasattr(tweet.message, "destroy"): + tweet.message.destroy() def get_formatted_message(self): if self.type == "dm" or self.name == "direct_messages": @@ -422,7 +396,6 @@ class BaseBuffer(base.Buffer): user = self.session.get_user(tweet.user) screen_name = user.screen_name id = tweet.id - twishort_enabled = hasattr(tweet, "twishort") users = utils.get_all_mentioned(tweet, self.session.db, field="screen_name") ids = utils.get_all_mentioned(tweet, self.session.db, field="id") # Build the window title @@ -430,38 +403,14 @@ class BaseBuffer(base.Buffer): title=_("Reply to {arg0}").format(arg0=screen_name) else: title=_("Reply") - message = messages.reply(self.session, title, _(u"Reply to %s") % (screen_name,), "", users=users, ids=ids) - if message.message.get_response() == widgetUtils.OK: + message = messages.reply(self.session, title, _("Reply to %s") % (screen_name,), "", users=users, ids=ids) + if message.message.ShowModal() == widgetUtils.OK: if config.app["app-settings"]["remember_mention_and_longtweet"]: - config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue() if len(users) > 0: - config.app["app-settings"]["mention_all"] = message.message.mentionAll.GetValue() + config.app["app-settings"]["mention_all"] = message.message.mention_all.GetValue() config.app.write() - params = {"_sound": "reply_send.ogg", "in_reply_to_status_id": id, "tweet_mode": "extended"} - text = message.message.get_text() - if twishort_enabled == False: - excluded_ids = message.get_ids() - params["exclude_reply_user_ids"] =excluded_ids - params["auto_populate_reply_metadata"] =True - else: - mentioned_people = message.get_people() - text = "@"+screen_name+" "+mentioned_people+u" "+text - if len(text) > 280 and message.message.get("long_tweet") == True: - if message.image == None: - text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text) - else: - text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1) - params["status"] = text - if message.image == None: - params["call_name"] = "update_status" - else: - params["call_name"] = "update_status_with_media" - params["media"] = message.file - item = self.session.api_call(**params) - # We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item. - # but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly. -# if item != None: -# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"]) + tweet_data = dict(text=message.message.text.GetValue(), attachments=message.attachments) + call_threaded(self.session.reply, in_reply_to_status_id=id, text=message.message.text.GetValue(), attachments=message.attachments, exclude_reply_user_ids=message.get_ids(), auto_populate_reply_metadata=True) if hasattr(message.message, "destroy"): message.message.destroy() self.session.settings.write() @@ -478,11 +427,11 @@ class BaseBuffer(base.Buffer): screen_name = self.session.get_user(tweet.user).screen_name users = utils.get_all_users(tweet, self.session) dm = messages.dm(self.session, _(u"Direct message to %s") % (screen_name,), _(u"New direct message"), users) - if dm.message.get_response() == widgetUtils.OK: - screen_name = dm.message.get("cb") + if dm.message.ShowModal() == widgetUtils.OK: + screen_name = dm.message.cb.GetValue() user = self.session.get_user_by_screen_name(screen_name) recipient_id = user - text = dm.message.get_text() + text = dm.message.text.GetValue() val = self.session.api_call(call_name="send_direct_message", recipient_id=recipient_id, text=text) if val != None: sent_dms = self.session.db["sent_direct_messages"] From 269db95fe3fedc62a4b5ddeaafcf324557204a27 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 16:44:03 -0600 Subject: [PATCH 12/33] Implemented code to quote Tweets by using the new GUI --- src/controller/buffers/twitter/base.py | 37 +++++++------------------- src/controller/messages.py | 3 +-- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/controller/buffers/twitter/base.py b/src/controller/buffers/twitter/base.py index 619b7f8f..1113e46d 100644 --- a/src/controller/buffers/twitter/base.py +++ b/src/controller/buffers/twitter/base.py @@ -458,40 +458,21 @@ class BaseBuffer(base.Buffer): else: self._retweet_with_comment(tweet, id) - def _retweet_with_comment(self, tweet, id, comment=''): - # If quoting a retweet, let's quote the original tweet instead the retweet. + def _retweet_with_comment(self, tweet, id): if hasattr(tweet, "retweeted_status"): tweet = tweet.retweeted_status - if hasattr(tweet, "full_text"): - comments = tweet.full_text - else: - comments = tweet.text - retweet = messages.tweet(self.session, _(u"Quote"), _(u"Add your comment to the tweet"), u"“@%s: %s ”" % (self.session.get_user(tweet.user).screen_name, comments), max=256, messageType="retweet") - if comment != '': - retweet.message.set_text(comment) - if retweet.message.get_response() == widgetUtils.OK: - text = retweet.message.get_text() + retweet = messages.tweet(session=self.session, title=_("Quote"), caption=_("Add your comment to the tweet"), max=256, thread_mode=False) + retweet = messages.tweet(session=self.session, title=_("Quote"), caption=_("Add your comment to the tweet"), max=256, thread_mode=False) + if retweet.message.ShowModal() == widgetUtils.OK: + text = retweet.message.text.GetValue() text = text+" https://twitter.com/{0}/status/{1}".format(self.session.get_user(tweet.user).screen_name, id) - if retweet.image == None: - # We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item. - # but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly. - item = self.session.api_call(call_name="update_status", _sound="retweet_send.ogg", status=text, in_reply_to_status_id=id, tweet_mode="extended") -# if item != None: -# new_item = self.session.twitter.get_status(id=item.id, include_ext_alt_text=True, tweet_mode="extended") -# pub.sendMessage("sent-tweet", data=new_item, user=self.session.db["user_name"]) - else: - call_threaded(self.session.api_call, call_name="update_status", _sound="retweet_send.ogg", status=text, media=retweet.image) - if hasattr(retweet.message, "destroy"): retweet.message.destroy() + tweet_data = dict(text=text, attachments=retweet.attachments) + call_threaded(self.session.send_tweet, *[tweet_data]) + if hasattr(retweet.message, "destroy"): + retweet.message.destroy() def _direct_retweet(self, id): item = self.session.api_call(call_name="retweet", _sound="retweet_send.ogg", id=id) - # We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item. - # but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly. -# if item != None: - # Retweets are returned as non-extended tweets, so let's get the object as extended - # just before sending the event message. See https://github.com/manuelcortez/TWBlue/issues/253 -# item = self.session.twitter.get_status(id=item.id, include_ext_alt_text=True, tweet_mode="extended") -# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"]) def onFocus(self, *args, **kwargs): tweet = self.get_tweet() diff --git a/src/controller/messages.py b/src/controller/messages.py index 9417ad90..776ceb11 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -122,10 +122,9 @@ class basicTweet(object): pass class tweet(basicTweet): - def __init__(self, session, title, caption, text, max=280, messageType="tweet", *args, **kwargs): + def __init__(self, session, title, caption, text="", max=280, messageType="tweet", *args, **kwargs): self.thread = [] super(tweet, self).__init__(session, title, caption, text, messageType, max, *args, **kwargs) - self.image = None widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) if hasattr(self.message, "add_tweet"): widgetUtils.connect_event(self.message.add_tweet, widgetUtils.BUTTON_PRESSED, self.add_tweet) From 80adf381c9bf718359e06739ce5674292f99c8e4 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 17:19:12 -0600 Subject: [PATCH 13/33] Implemented new GUI in people and direct message buffers --- .../buffers/twitter/directMessages.py | 18 ++++++------------ src/controller/buffers/twitter/people.py | 18 ++++++------------ src/controller/messages.py | 3 +++ 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/controller/buffers/twitter/directMessages.py b/src/controller/buffers/twitter/directMessages.py index b0c51e5b..60090c89 100644 --- a/src/controller/buffers/twitter/directMessages.py +++ b/src/controller/buffers/twitter/directMessages.py @@ -90,18 +90,12 @@ class DirectMessagesBuffer(base.BaseBuffer): def reply(self, *args, **kwargs): tweet = self.get_right_tweet() screen_name = self.session.get_user(tweet.message_create["sender_id"]).screen_name - message = messages.reply(self.session, _(u"Mention"), _(u"Mention to %s") % (screen_name,), "@%s " % (screen_name,), [screen_name,]) - if message.message.get_response() == widgetUtils.OK: - if config.app["app-settings"]["remember_mention_and_longtweet"]: - config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue() - config.app.write() - if message.image == None: - item = self.session.api_call(call_name="update_status", _sound="reply_send.ogg", status=message.message.get_text(), tweet_mode="extended") - if item != None: - pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"]) - else: - call_threaded(self.session.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", status=message.message.get_text(), media=message.file) - if hasattr(message.message, "destroy"): message.message.destroy() + message = messages.reply(session=self.session, title=_("Mention"), caption=_("Mention to %s") % (screen_name,), text="@%s " % (screen_name,), thread_mode=False, users=[screen_name,]) + if message.message.ShowModal() == widgetUtils.OK: + tweet_data = dict(text=message.message.text.GetValue(), attachments=message.attachments) + call_threaded(self.session.send_tweet, *[tweet_data]) + if hasattr(message.message, "destroy"): + message.message.destroy() def onFocus(self, *args, **kwargs): tweet = self.get_tweet() diff --git a/src/controller/buffers/twitter/people.py b/src/controller/buffers/twitter/people.py index 7f5c90b1..faf03794 100644 --- a/src/controller/buffers/twitter/people.py +++ b/src/controller/buffers/twitter/people.py @@ -92,18 +92,12 @@ class PeopleBuffer(base.BaseBuffer): def reply(self, *args, **kwargs): tweet = self.get_right_tweet() screen_name = tweet.screen_name - message = messages.reply(self.session, _(u"Mention"), _(u"Mention to %s") % (screen_name,), "@%s " % (screen_name,), [screen_name,]) - if message.message.get_response() == widgetUtils.OK: - if config.app["app-settings"]["remember_mention_and_longtweet"]: - config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue() - config.app.write() - if message.image == None: - item = self.session.api_call(call_name="update_status", _sound="reply_send.ogg", status=message.message.get_text(), tweet_mode="extended") - if item != None: - pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"]) - else: - call_threaded(self.session.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", status=message.message.get_text(), media=message.file) - if hasattr(message.message, "destroy"): message.message.destroy() + message = messages.tweet(session=self.session, title=_("Mention"), caption=_("Mention to %s") % (screen_name,), text="@%s " % (screen_name,), thread_mode=False) + if message.message.ShowModal() == widgetUtils.OK: + tweet_data = dict(text=message.message.text.GetValue(), attachments=message.attachments) + call_threaded(self.session.send_tweet, *[tweet_data]) + if hasattr(message.message, "destroy"): + message.message.destroy() def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False): # starts stream every 3 minutes. diff --git a/src/controller/messages.py b/src/controller/messages.py index 776ceb11..89ffe176 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -24,6 +24,8 @@ class basicTweet(object): self.title = title self.session = session self.message = getattr(twitterDialogs, messageType)(title=title, caption=caption, message=text, *args, **kwargs) + self.message.text.SetValue(text) + self.message.text.SetInsertionPoint(len(self.message.text.GetValue())) widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) widgetUtils.connect_event(self.message.add_audio, widgetUtils.BUTTON_PRESSED, self.attach) widgetUtils.connect_event(self.message.text, widgetUtils.ENTERED_TEXT, self.text_processor) @@ -42,6 +44,7 @@ class basicTweet(object): dst = k msg = translator.translator.translate(text=text_to_translate, target=dst) self.message.text.ChangeValue(msg) + self.message.text.SetInsertionPoint(len(self.message.text.GetValue())) self.text_processor() self.message.text.SetFocus() output.speak(_(u"Translated")) From a2393fe3e9eed5b36fa4d181dc33a6bc738f6358 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Mon, 8 Nov 2021 17:20:16 -0600 Subject: [PATCH 14/33] Updated dialogs package --- src/wxUI/dialogs/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wxUI/dialogs/__init__.py b/src/wxUI/dialogs/__init__.py index a21239a7..e69de29b 100644 --- a/src/wxUI/dialogs/__init__.py +++ b/src/wxUI/dialogs/__init__.py @@ -1,3 +0,0 @@ -from __future__ import absolute_import -from __future__ import unicode_literals -from . import baseDialog, trends, configuration, lists, message, search, find, show_user, update_profile, urlList, userSelection, utils, filterDialogs, userAliasDialogs From 15d0f4c65efb708e736f22eee5ef38ebd03a48f5 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Tue, 9 Nov 2021 13:34:49 -0600 Subject: [PATCH 15/33] Restored some imports on dialogs --- src/wxUI/dialogs/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wxUI/dialogs/__init__.py b/src/wxUI/dialogs/__init__.py index e69de29b..d00a14c0 100644 --- a/src/wxUI/dialogs/__init__.py +++ b/src/wxUI/dialogs/__init__.py @@ -0,0 +1 @@ +from . import baseDialog, trends, configuration, lists, search, find, show_user, update_profile, urlList, userSelection, utils, filterDialogs, userAliasDialogs From 8fed52118fa829292f5e974ba1fa8dc6a4c5718e Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Tue, 9 Nov 2021 13:46:36 -0600 Subject: [PATCH 16/33] Finished implementation of new tweet and reply calls in buffers --- src/controller/buffers/twitter/base.py | 1 - src/controller/buffers/twitter/trends.py | 14 +++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/controller/buffers/twitter/base.py b/src/controller/buffers/twitter/base.py index 1113e46d..72ea6449 100644 --- a/src/controller/buffers/twitter/base.py +++ b/src/controller/buffers/twitter/base.py @@ -84,7 +84,6 @@ class BaseBuffer(base.Buffer): return _(u"Unknown buffer") def post_status(self, *args, **kwargs): - item = None title = _("Tweet") caption = _("Write the tweet here") tweet = messages.tweet(self.session, title, caption, "") diff --git a/src/controller/buffers/twitter/trends.py b/src/controller/buffers/twitter/trends.py index e3be681d..ab623e05 100644 --- a/src/controller/buffers/twitter/trends.py +++ b/src/controller/buffers/twitter/trends.py @@ -4,7 +4,7 @@ import platform if platform.system() == "Windows": import wx from wxUI import buffers, commonMessageDialogs, menus - from controller import user + from controller import user, messages elif platform.system() == "Linux": from gi.repository import Gtk from gtkUI import buffers, commonMessageDialogs @@ -38,6 +38,18 @@ class TrendsBuffer(base.Buffer): self.get_formatted_message = self.get_message self.reply = self.search_topic + + def post_status(self, *args, **kwargs): + title = _("Tweet") + caption = _("Write the tweet here") + tweet = messages.tweet(self.session, title, caption, "") + response = tweet.message.ShowModal() + if response == wx.ID_OK: + tweet_data = tweet.get_tweet_data() + call_threaded(self.session.send_tweet, *tweet_data) + if hasattr(tweet.message, "destroy"): + tweet.message.destroy() + def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False): # starts stream every 3 minutes. current_time = time.time() From 605868eff9cf7bf0b563f8923c04087c980ca52a Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 09:47:53 -0600 Subject: [PATCH 17/33] Added functions to make TWBlue respect Twitter limits when adding media items --- src/controller/messages.py | 13 +++++++++++-- src/wxUI/dialogs/twitterDialogs/tweetDialogs.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/controller/messages.py b/src/controller/messages.py index 89ffe176..f613c436 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -10,7 +10,6 @@ import config from pubsub import pub from twitter_text import parse_tweet from wxUI.dialogs import twitterDialogs, urlList -#from wxUI.dialogs import twitterDialogs from wxUI import commonMessageDialogs from extra import translator, SpellChecker, autocompletionUsers from extra.AudioUploader import audioUploader @@ -87,7 +86,7 @@ class basicTweet(object): return False elif len(self.attachments) < 4: return True - return False + return False def on_attach(self, *args, **kwargs): can_attach = self.can_attach() @@ -98,6 +97,14 @@ class basicTweet(object): self.message.PopupMenu(menu, self.message.add.GetPosition()) def on_attach_image(self, *args, **kwargs): + can_attach = self.can_attach() + video_or_gif_present = False + for a in self.attachments: + if a["type"] == "video" or a["type"] == "gif": + video_or_gif = True + break + if can_attach == False or video_or_gif_present == True: + return self.message.unable_to_attach_file() image, description = self.message.get_image() if image != None: if image.endswith("gif"): @@ -112,6 +119,8 @@ class basicTweet(object): self.text_processor() def on_attach_video(self, *args, **kwargs): + if len(self.attachments) > 0: + return self.message.unable_to_attach_file() video = self.message.get_video() if video != None: videoInfo = {"type": "video", "file": video, "description": ""} diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index d07f10da..a7a20059 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -145,7 +145,7 @@ class tweet(wx.Dialog): return openFileDialog.GetPath() def unable_to_attach_file(self, *args, **kwargs): - return wx.MessageDialog(self, _("You need to delete other attachments first, as Videos and GIF's cannot be added with other attachments."), _("Error adding attachment"), wx.ICON_ERROR).ShowModal() + return wx.MessageDialog(self, _("It is not possible to add more attachments. Please make sure your tweet complies with Twitter'S attachment rules. You can add only one video or GIF in every tweet, and a maximum of 4 photos."), _("Error adding attachment"), wx.ICON_ERROR).ShowModal() class reply(tweet): From dbecf341ece9f0445634a8f66de660cca2b59b56 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 11:24:50 -0600 Subject: [PATCH 18/33] Stop using text_processor in tweet displayer --- src/controller/messages.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controller/messages.py b/src/controller/messages.py index f613c436..2777d2cc 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -332,6 +332,10 @@ class viewTweet(basicTweet): widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) self.message.ShowModal() + # We won't need text_processor in this dialog, so let's avoid it. + def text_processor(self): + pass + def clear_text(self, text): urls = utils.find_urls_in_text(text) for i in urls: From 1e2464c4faf9f97c144822ae1cac86024fe27cbe Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 11:36:06 -0600 Subject: [PATCH 19/33] Made Dm dialog to hide attachments panel when needed --- src/controller/messages.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/controller/messages.py b/src/controller/messages.py index 2777d2cc..06159457 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -133,6 +133,14 @@ class basicTweet(object): def on_attach_poll(self, *args, **kwargs): pass + def remove_attachment(self, *args, **kwargs): + attachment = self.message.attachments.GetFocusedItem() + if attachment > -1 and len(self.attachments) > attachment: + self.attachments.pop(attachment) + self.message.remove_item(list_type="attachment") + self.text_processor() + self.message.text.SetFocus() + class tweet(basicTweet): def __init__(self, session, title, caption, text="", max=280, messageType="tweet", *args, **kwargs): self.thread = [] @@ -186,13 +194,6 @@ class tweet(basicTweet): self.text_processor() self.message.text.SetFocus() - def remove_attachment(self, *args, **kwargs): - attachment = self.message.attachments.GetFocusedItem() - if attachment > -1 and len(self.attachments) > attachment: - self.attachments.pop(attachment) - self.message.remove_item(list_type="attachment") - self.text_processor() - self.message.text.SetFocus() class reply(tweet): def __init__(self, session, title, caption, text, users=[], ids=[]): @@ -256,6 +257,18 @@ class dm(basicTweet): c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) c.show_menu("dm") + def text_processor(self, *args, **kwargs): + super(dm, self).text_processor(*args, **kwargs) + if len(self.attachments) > 0: + self.message.attachments.Enable(True) + self.message.remove_attachment.Enable(True) + else: + self.message.attachments.Enable(False) + self.message.remove_attachment.Enable(False) + + def get_data(self): + return dict(text=self.message.text.GetValue(), attachments=self.attachments) + class viewTweet(basicTweet): def __init__(self, tweet, tweetList, is_tweet=True, utc_offset=0, date="", item_url=""): """ This represents a tweet displayer. However it could be used for showing something wich is not a tweet, like a direct message or an event. From 6346651edf703660a11cd049ff9390ccdbaf239b Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 10 Nov 2021 12:53:18 -0500 Subject: [PATCH 20/33] Added a shortcut for the add new tweet button --- src/wxUI/dialogs/twitterDialogs/tweetDialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index a7a20059..d8504c3e 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -57,7 +57,7 @@ class tweet(wx.Dialog): mainBox.Add(btn_sizer_1, 1, wx.EXPAND, 0) self.add = wx.Button(panel, wx.ID_ANY, _("A&dd...")) btn_sizer_1.Add(self.add, 0, 0, 0) - self.add_tweet = wx.Button(panel, wx.ID_ANY, _("Add tweet")) + self.add_tweet = wx.Button(panel, wx.ID_ANY, _("Add t&weet")) self.add_tweet.Enable(thread_mode) btn_sizer_1.Add(self.add_tweet, 0, 0, 0) self.add_audio = wx.Button(panel, wx.ID_ANY, _("&Attach audio...")) From 9e7542f513e80319afdcf6447c6e6429403c617f Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 10 Nov 2021 13:01:24 -0500 Subject: [PATCH 21/33] Removed url_shortener import --- src/sessions/twitter/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sessions/twitter/utils.py b/src/sessions/twitter/utils.py index f84dfaca..d44e0e3f 100644 --- a/src/sessions/twitter/utils.py +++ b/src/sessions/twitter/utils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import url_shortener, re +import re import output import config import logging From a43101e6949b33cfb588bb5f10f3d5c365416496 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 12:21:07 -0600 Subject: [PATCH 22/33] Reimplemented direct messages with support for media --- src/controller/buffers/twitter/base.py | 16 ++++++-------- src/controller/messages.py | 5 +++++ src/sessions/twitter/session.py | 21 +++++++++++++++++++ .../dialogs/twitterDialogs/tweetDialogs.py | 14 +++++++++++++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/controller/buffers/twitter/base.py b/src/controller/buffers/twitter/base.py index 72ea6449..ae22a576 100644 --- a/src/controller/buffers/twitter/base.py +++ b/src/controller/buffers/twitter/base.py @@ -425,21 +425,17 @@ class BaseBuffer(base.Buffer): else: screen_name = self.session.get_user(tweet.user).screen_name users = utils.get_all_users(tweet, self.session) - dm = messages.dm(self.session, _(u"Direct message to %s") % (screen_name,), _(u"New direct message"), users) + dm = messages.dm(self.session, _("Direct message to %s") % (screen_name,), _("New direct message"), users) if dm.message.ShowModal() == widgetUtils.OK: screen_name = dm.message.cb.GetValue() user = self.session.get_user_by_screen_name(screen_name) recipient_id = user text = dm.message.text.GetValue() - val = self.session.api_call(call_name="send_direct_message", recipient_id=recipient_id, text=text) - if val != None: - sent_dms = self.session.db["sent_direct_messages"] - if self.session.settings["general"]["reverse_timelines"] == False: - sent_dms.append(val) - else: - sent_dms.insert(0, val) - self.session.db["sent_direct_messages"] = sent_dms - pub.sendMessage("sent-dm", data=val, user=self.session.db["user_name"]) + if len(dm.attachments) > 0: + attachment = dm.attachments[0] + else: + attachment = None + call_threaded(self.session.direct_message, text=text, recipient=recipient_id, attachment=attachment) if hasattr(dm.message, "destroy"): dm.message.destroy() @_tweets_exist diff --git a/src/controller/messages.py b/src/controller/messages.py index 06159457..461b155e 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -269,6 +269,11 @@ class dm(basicTweet): def get_data(self): return dict(text=self.message.text.GetValue(), attachments=self.attachments) + def can_attach(self): + if len(self.attachments) == 0: + return True + return False + class viewTweet(basicTweet): def __init__(self, tweet, tweetList, is_tweet=True, utc_offset=0, date="", item_url=""): """ This represents a tweet displayer. However it could be used for showing something wich is not a tweet, like a direct message or an event. diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index df3ab809..4e732b22 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -620,3 +620,24 @@ class Session(base.baseSession): self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) media_ids.append(img.media_id) item = self.api_call(call_name="update_status", status=text, _sound="reply_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, media_ids=media_ids, *args, **kwargs) + + def direct_message(self, text, recipient, attachment=None, *args, **kwargs): + if attachment == None: + item = self.api_call(call_name="send_direct_message", recipient_id=recipient, text=text) + else: + if attachment["type"] == "photo": + media_category = "DmImage" + elif attachment["type"] == "gif": + media_category = "DmGif" + elif attachment["type"] == "video": + media_category = "DmVideo" + media = self.api_call("media_upload", filename=attachment["file"], media_category=media_category) + item = self.api_call(call_name="send_direct_message", recipient_id=recipient, text=text, attachment_type="media", attachment_media_id=media.media_id) + if item != None: + sent_dms = self.db["sent_direct_messages"] + if self.settings["general"]["reverse_timelines"] == False: + sent_dms.append(item) + else: + sent_dms.insert(0, item) + self.db["sent_direct_messages"] = sent_dms + pub.sendMessage("sent-dm", data=item, user=self.db["user_name"]) diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index a7a20059..1e4f1a3a 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -283,6 +283,20 @@ class dm(tweet): self.SetEscapeId(self.cancel.GetId()) self.Layout() + def get_image(self): + openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if openFileDialog.ShowModal() == wx.ID_CANCEL: + return (None, None) + return (openFileDialog.GetPath(), "") + + def attach_menu(self, event=None, enabled=True, *args, **kwargs): + menu = wx.Menu() + self.add_image = menu.Append(wx.ID_ANY, _("Image")) + self.add_image.Enable(enabled) + self.add_video = menu.Append(wx.ID_ANY, _("Video")) + self.add_video.Enable(enabled) + return menu + class viewTweet(wx.Dialog): def set_title(self, lenght): self.SetTitle(_(u"Tweet - %i characters ") % (lenght,)) From d9f4b86d913e9b920ee69a4b904446e311b06ad1 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 12:35:43 -0600 Subject: [PATCH 23/33] Empty attachments after adding a new tweet --- src/controller/messages.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/controller/messages.py b/src/controller/messages.py index 461b155e..67c8ec40 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -161,6 +161,7 @@ class tweet(basicTweet): attachments = self.attachments[::] tweetdata = dict(text=text, attachments=attachments) self.thread.append(tweetdata) + self.attachments = [] if update_gui: self.message.reset_controls() self.message.add_item(item=[text, len(attachments)], list_type="tweet") @@ -185,6 +186,11 @@ class tweet(basicTweet): else: self.message.attachments.Enable(False) self.message.remove_attachment.Enable(False) + if hasattr(self.message, "add_tweet"): + if len(self.message.text.GetValue()) > 0 or len(self.attachments) > 0: + self.message.add_tweet.Enable(True) + else: + self.message.add_tweet.Enable(False) def remove_tweet(self, *args, **kwargs): tweet = self.message.tweets.GetFocusedItem() @@ -266,9 +272,6 @@ class dm(basicTweet): self.message.attachments.Enable(False) self.message.remove_attachment.Enable(False) - def get_data(self): - return dict(text=self.message.text.GetValue(), attachments=self.attachments) - def can_attach(self): if len(self.attachments) == 0: return True From 77d13fdc28334d4e660af4e63bbb037542cc55b6 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 15:14:40 -0600 Subject: [PATCH 24/33] Added support for posting polls --- src/controller/buffers/twitter/base.py | 3 +- src/controller/messages.py | 12 +++- src/sessions/twitter/session.py | 33 ++++++++-- src/wxUI/dialogs/twitterDialogs/__init__.py | 2 +- .../dialogs/twitterDialogs/tweetDialogs.py | 65 +++++++++++++++++++ 5 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/controller/buffers/twitter/base.py b/src/controller/buffers/twitter/base.py index ae22a576..34196b7b 100644 --- a/src/controller/buffers/twitter/base.py +++ b/src/controller/buffers/twitter/base.py @@ -457,11 +457,10 @@ class BaseBuffer(base.Buffer): if hasattr(tweet, "retweeted_status"): tweet = tweet.retweeted_status retweet = messages.tweet(session=self.session, title=_("Quote"), caption=_("Add your comment to the tweet"), max=256, thread_mode=False) - retweet = messages.tweet(session=self.session, title=_("Quote"), caption=_("Add your comment to the tweet"), max=256, thread_mode=False) if retweet.message.ShowModal() == widgetUtils.OK: text = retweet.message.text.GetValue() text = text+" https://twitter.com/{0}/status/{1}".format(self.session.get_user(tweet.user).screen_name, id) - tweet_data = dict(text=text, attachments=retweet.attachments) + tweet_data = dict(text=text, attachments=retweet.attachments, poll_period=retweet.poll_period, poll_options=retweet.poll_options) call_threaded(self.session.send_tweet, *[tweet_data]) if hasattr(retweet.message, "destroy"): retweet.message.destroy() diff --git a/src/controller/messages.py b/src/controller/messages.py index 67c8ec40..3fb52d38 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -131,7 +131,11 @@ class basicTweet(object): self.text_processor() def on_attach_poll(self, *args, **kwargs): - pass + dlg = twitterDialogs.poll() + if dlg.ShowModal() == wx.ID_OK: + self.poll_options = dlg.get_options() + self.poll_period = 60*24*dlg.period.GetValue() + dlg.Destroy() def remove_attachment(self, *args, **kwargs): attachment = self.message.attachments.GetFocusedItem() @@ -144,6 +148,8 @@ class basicTweet(object): class tweet(basicTweet): def __init__(self, session, title, caption, text="", max=280, messageType="tweet", *args, **kwargs): self.thread = [] + self.poll_options = None + self.poll_period = None super(tweet, self).__init__(session, title, caption, text, messageType, max, *args, **kwargs) widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) if hasattr(self.message, "add_tweet"): @@ -159,9 +165,11 @@ class tweet(basicTweet): def add_tweet(self, event, update_gui=True, *args, **kwargs): text = self.message.text.GetValue() attachments = self.attachments[::] - tweetdata = dict(text=text, attachments=attachments) + tweetdata = dict(text=text, attachments=attachments, poll_options=self.poll_options, poll_period=self.poll_period) self.thread.append(tweetdata) self.attachments = [] + self.poll_options = None + self.poll_period = None if update_gui: self.message.reset_controls() self.message.add_item(item=[text, len(attachments)], list_type="tweet") diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index 4e732b22..40f40df5 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -218,6 +218,30 @@ class Session(base.baseSession): if _sound != None: self.sound.play(_sound) return val + def api_call_v2(self, call_name, action="", _sound=None, report_success=False, report_failure=True, preexec_message="", *args, **kwargs): + finished = False + tries = 0 + if preexec_message: + output.speak(preexec_message, True) + while finished==False and tries < 25: + try: + val = getattr(self.twitter_v2, call_name)(*args, **kwargs) + finished = True + except TweepyException as e: + log.exception("Error sending the tweet.") + output.speak(str(e)) + val = None + if type(e) != NotFound and type(e) != Forbidden: + tries = tries+1 + time.sleep(5) + elif report_failure: + output.speak(_("%s failed. Reason: %s") % (action, str(e))) + finished = True + if report_success: + output.speak(_("%s succeeded.") % action) + if _sound != None: self.sound.play(_sound) + return val + def search(self, name, *args, **kwargs): """ Search in twitter, passing args and kwargs as arguments to the Twython function.""" tl = self.twitter.search_tweets(*args, **kwargs) @@ -597,8 +621,9 @@ class Session(base.baseSession): in_reply_to_status_id = None for obj in tweets: if len(obj["attachments"]) == 0: - item = self.api_call(call_name="update_status", status=obj["text"], _sound="tweet_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id) - in_reply_to_status_id = item.id + item = self.api_call_v2(call_name="create_tweet", text=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"]) + print(item) + in_reply_to_status_id = item.data.id else: media_ids = [] for i in obj["attachments"]: @@ -606,8 +631,8 @@ class Session(base.baseSession): if i["type"] == "photo": self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) media_ids.append(img.media_id) - item = self.api_call(call_name="update_status", status=obj["text"], _sound="tweet_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, media_ids=media_ids) - in_reply_to_status_id = item.id + item = self.api_call_v2(call_name="create_tweet", status=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, media_ids=media_ids, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"]) + in_reply_to_status_id = item.data.id def reply(self, text="", in_reply_to_status_id=None, attachments=[], *args, **kwargs): if len(attachments) == 0: diff --git a/src/wxUI/dialogs/twitterDialogs/__init__.py b/src/wxUI/dialogs/twitterDialogs/__init__.py index c6b568e0..6a829f06 100644 --- a/src/wxUI/dialogs/twitterDialogs/__init__.py +++ b/src/wxUI/dialogs/twitterDialogs/__init__.py @@ -1 +1 @@ -from .tweetDialogs import tweet, reply, dm, viewTweet, viewNonTweet \ No newline at end of file +from .tweetDialogs import tweet, reply, dm, viewTweet, viewNonTweet, poll \ No newline at end of file diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index d175b033..d5fd35a6 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -469,3 +469,68 @@ class viewNonTweet(wx.Dialog): def enable_button(self, buttonName): if hasattr(self, buttonName): return getattr(self, buttonName).Enable() + +class poll(wx.Dialog): + def __init__(self, *args, **kwds): + super(poll, self).__init__(parent=None, id=wx.NewId(), title=_("Add a poll")) + sizer_1 = wx.BoxSizer(wx.VERTICAL) + period_sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer_1.Add(period_sizer, 1, wx.EXPAND, 0) + label_period = wx.StaticText(self, wx.ID_ANY, _("Participation time (in days)")) + period_sizer.Add(label_period, 0, 0, 0) + self.period = wx.SpinCtrl(self, wx.ID_ANY) + self.period.SetFocus() + self.period.SetRange(1, 7) + self.period.SetValue(7) + period_sizer.Add(self.period, 0, 0, 0) + sizer_2 = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Choices")), wx.VERTICAL) + sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) + option1_sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer_2.Add(option1_sizer, 1, wx.EXPAND, 0) + label_2 = wx.StaticText(self, wx.ID_ANY, _("Option 1")) + option1_sizer.Add(label_2, 0, 0, 0) + self.option1 = wx.TextCtrl(self, wx.ID_ANY, "") + option1_sizer.Add(self.option1, 0, 0, 0) + option2_sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer_2.Add(option2_sizer, 1, wx.EXPAND, 0) + label_3 = wx.StaticText(self, wx.ID_ANY, _("Option 2")) + option2_sizer.Add(label_3, 0, 0, 0) + self.option2 = wx.TextCtrl(self, wx.ID_ANY, "") + option2_sizer.Add(self.option2, 0, 0, 0) + option3_sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer_2.Add(option3_sizer, 1, wx.EXPAND, 0) + label_4 = wx.StaticText(self, wx.ID_ANY, _("Option 3")) + option3_sizer.Add(label_4, 0, 0, 0) + self.option3 = wx.TextCtrl(self, wx.ID_ANY, "") + option3_sizer.Add(self.option3, 0, 0, 0) + option4_sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer_2.Add(option4_sizer, 1, wx.EXPAND, 0) + label_5 = wx.StaticText(self, wx.ID_ANY, _("Option 4")) + option4_sizer.Add(label_5, 0, 0, 0) + self.option4 = wx.TextCtrl(self, wx.ID_ANY, "") + option4_sizer.Add(self.option4, 0, 0, 0) + btn_sizer = wx.StdDialogButtonSizer() + sizer_1.Add(btn_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 4) + self.button_OK = wx.Button(self, wx.ID_OK) + self.button_OK.SetDefault() + self.button_OK.Bind(wx.EVT_BUTTON, self.validate_data) + btn_sizer.AddButton(self.button_OK) + self.button_CANCEL = wx.Button(self, wx.ID_CANCEL, "") + btn_sizer.AddButton(self.button_CANCEL) + btn_sizer.Realize() + self.SetSizer(sizer_1) + sizer_1.Fit(self) + self.SetAffirmativeId(self.button_OK.GetId()) + self.SetEscapeId(self.button_CANCEL.GetId()) + self.Layout() + + def get_options(self): + controls = [self.option1, self.option2, self.option3, self.option4] + options = [option.GetValue() for option in controls if option.GetValue() != ""] + return options + + def validate_data(self, *args, **kwargs): + options = self.get_options() + if len(options) < 2: + return wx.MessageDialog(self, _("Please make sure you have provided at least two options for the poll."), _("Not enough information"), wx.ICON_ERROR).ShowModal() + self.EndModal(wx.ID_OK) \ No newline at end of file From b0cfc5978c6922169bf643c02bed7a8aecbaa5f7 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 16:32:14 -0600 Subject: [PATCH 25/33] Updated reply dialog with some changes in add menu --- src/controller/buffers/twitter/base.py | 4 ++-- .../buffers/twitter/directMessages.py | 4 ++-- src/controller/buffers/twitter/people.py | 4 ++-- src/controller/buffers/twitter/trends.py | 22 ++++++------------- src/controller/messages.py | 7 +++--- src/sessions/twitter/session.py | 2 +- .../dialogs/twitterDialogs/tweetDialogs.py | 8 +++++++ 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/controller/buffers/twitter/base.py b/src/controller/buffers/twitter/base.py index 34196b7b..1d81a604 100644 --- a/src/controller/buffers/twitter/base.py +++ b/src/controller/buffers/twitter/base.py @@ -408,8 +408,8 @@ class BaseBuffer(base.Buffer): if len(users) > 0: config.app["app-settings"]["mention_all"] = message.message.mention_all.GetValue() config.app.write() - tweet_data = dict(text=message.message.text.GetValue(), attachments=message.attachments) - call_threaded(self.session.reply, in_reply_to_status_id=id, text=message.message.text.GetValue(), attachments=message.attachments, exclude_reply_user_ids=message.get_ids(), auto_populate_reply_metadata=True) + tweet_data = dict(text=message.message.text.GetValue(), attachments=message.attachments, poll_options=message.poll_options, poll_period=message.poll_period) + call_threaded(self.session.reply, in_reply_to_status_id=id, text=message.message.text.GetValue(), attachments=message.attachments, exclude_reply_user_ids=message.get_ids()) if hasattr(message.message, "destroy"): message.message.destroy() self.session.settings.write() diff --git a/src/controller/buffers/twitter/directMessages.py b/src/controller/buffers/twitter/directMessages.py index 60090c89..06cbed9a 100644 --- a/src/controller/buffers/twitter/directMessages.py +++ b/src/controller/buffers/twitter/directMessages.py @@ -92,8 +92,8 @@ class DirectMessagesBuffer(base.BaseBuffer): screen_name = self.session.get_user(tweet.message_create["sender_id"]).screen_name message = messages.reply(session=self.session, title=_("Mention"), caption=_("Mention to %s") % (screen_name,), text="@%s " % (screen_name,), thread_mode=False, users=[screen_name,]) if message.message.ShowModal() == widgetUtils.OK: - tweet_data = dict(text=message.message.text.GetValue(), attachments=message.attachments) - call_threaded(self.session.send_tweet, *[tweet_data]) + tweet_data = message.get_tweet_data() + call_threaded(self.session.send_tweet, tweet_data) if hasattr(message.message, "destroy"): message.message.destroy() diff --git a/src/controller/buffers/twitter/people.py b/src/controller/buffers/twitter/people.py index faf03794..d29a46dc 100644 --- a/src/controller/buffers/twitter/people.py +++ b/src/controller/buffers/twitter/people.py @@ -94,8 +94,8 @@ class PeopleBuffer(base.BaseBuffer): screen_name = tweet.screen_name message = messages.tweet(session=self.session, title=_("Mention"), caption=_("Mention to %s") % (screen_name,), text="@%s " % (screen_name,), thread_mode=False) if message.message.ShowModal() == widgetUtils.OK: - tweet_data = dict(text=message.message.text.GetValue(), attachments=message.attachments) - call_threaded(self.session.send_tweet, *[tweet_data]) + tweet_data = message.get_tweet_data() + call_threaded(self.session.send_tweet, tweet_data) if hasattr(message.message, "destroy"): message.message.destroy() diff --git a/src/controller/buffers/twitter/trends.py b/src/controller/buffers/twitter/trends.py index ab623e05..39809df1 100644 --- a/src/controller/buffers/twitter/trends.py +++ b/src/controller/buffers/twitter/trends.py @@ -131,21 +131,13 @@ class TrendsBuffer(base.Buffer): def tweet_about_this_trend(self, *args, **kwargs): if self.buffer.list.get_count() == 0: return - title = _(u"Tweet") - caption = _(u"Write the tweet here") - tweet = messages.tweet(self.session, title, caption, self.get_message()+ " ") - tweet.message.set_cursor_at_end() - if tweet.message.get_response() == widgetUtils.OK: - text = tweet.message.get_text() - if len(text) > 280 and tweet.message.get("long_tweet") == True: - if tweet.image == None: - text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text) - else: - text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1) - if tweet.image == None: - call_threaded(self.session.api_call, call_name="update_status", status=text) - else: - call_threaded(self.session.api_call, call_name="update_status_with_media", status=text, media=tweet.image) + title = _("Tweet") + caption = _("Write the tweet here") + tweet = messages.tweet(session=self.session, title=title, caption=caption, text=self.get_message()+ " ") + tweet.message.SetInsertionPoint(len(tweet.message.GetValue())) + if tweet.message.ShowModal() == widgetUtils.OK: + tweet_data = tweet.get_tweet_data() + call_threaded(self.session.send_tweet, *tweet_data) if hasattr(tweet.message, "destroy"): tweet.message.destroy() def show_menu_by_key(self, ev): diff --git a/src/controller/messages.py b/src/controller/messages.py index 3fb52d38..421be6d7 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -93,7 +93,8 @@ class basicTweet(object): menu = self.message.attach_menu(can_attach) self.message.Bind(wx.EVT_MENU, self.on_attach_image, self.message.add_image) self.message.Bind(wx.EVT_MENU, self.on_attach_video, self.message.add_video) - self.message.Bind(wx.EVT_MENU, self.on_attach_poll, self.message.add_poll) + if hasattr(self.message, "add_poll"): + self.message.Bind(wx.EVT_MENU, self.on_attach_poll, self.message.add_poll) self.message.PopupMenu(menu, self.message.add.GetPosition()) def on_attach_image(self, *args, **kwargs): @@ -243,10 +244,10 @@ class reply(tweet): i.Show() def get_ids(self): - excluded_ids = "" + excluded_ids = [] for i in range(0, len(self.message.checkboxes)): if self.message.checkboxes[i].GetValue() == False: - excluded_ids = excluded_ids + "{0},".format(self.ids[i],) + excluded_ids.append(self.ids[i]) return excluded_ids def get_people(self): diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index 40f40df5..38f4baae 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -636,7 +636,7 @@ class Session(base.baseSession): def reply(self, text="", in_reply_to_status_id=None, attachments=[], *args, **kwargs): if len(attachments) == 0: - item = self.api_call(call_name="update_status", status=text, _sound="reply_send.ogg", tweet_mode="extended", in_reply_to_status_id=in_reply_to_status_id, *args, **kwargs) + item = self.api_call_v2(call_name="create_tweet", text=text, _sound="reply_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, *args, **kwargs) else: media_ids = [] for i in attachments: diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index d5fd35a6..84ea5fde 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -217,6 +217,14 @@ class reply(tweet): self.SetEscapeId(self.cancel.GetId()) self.Layout() + def attach_menu(self, event=None, enabled=True, *args, **kwargs): + menu = wx.Menu() + self.add_image = menu.Append(wx.ID_ANY, _("Image")) + self.add_image.Enable(enabled) + self.add_video = menu.Append(wx.ID_ANY, _("Video")) + self.add_video.Enable(enabled) + return menu + class dm(tweet): def __init__(self, users: List[str] = [], *args, **kwargs) -> None: From e9f6fd529efd96a5db327a2fb39ee41c50059e18 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 16:32:39 -0600 Subject: [PATCH 26/33] Updated changelog --- doc/changelog.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/changelog.md b/doc/changelog.md index 513204c2..cc097af8 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -2,6 +2,15 @@ TWBlue Changelog ## changes in this version +* Now it is possible to create a tweet from a trending topics buffer again. +* TWBlue now includes a completely new set of dialogs to handle tweeting, replying and sending direct messages that takes advantage of more Twitter features. + * It is possible to add videos in tweets and direct messages by using the new "add" button, located in every dialog where media can be added. Twitter suggests to add videos from 5 seconds up to 2 minutes lenght, in mp4 format (video Codec H.264 and audio codec AAC). Currently, TWBlue does not check if the uploaded video complies with Twitter media requirements. You can add only a video in a tweet or direct message. No other kind of media can be added after a video is in a tweet. If the video was unable to be uploaded successfully, the tweet or direct message won't be created. + * Now you can add a poll to tweets. Polls can have up to 4 different options and allow voting up to 7 days after being created. Take into account, though, that currently TWBlue does not support reading polls in tweets. + * TWBlue now support threads while creating a new tweet. There is a new button, called add tweet which will add the current tweet to the thread and will allow you to write another tweet in the thread. Every tweet might include media (up to 4 photos, or one GIF image or a video) or up to one poll. + * Some functionality was removed from tweet dialogs within TWBlue. Particularly, URL shorteners and long tweets via Twishort. You still can read long tweets posted via Twishort, though. + +## Changes in version 2021.11.07 + * TWBlue should retrieve tweets from threads and conversations in a more reliable way. Tweets in the same thread (made by the same author) will be sorted correctly, although replies to the thread (made by different people) may not be ordered in the same way they are displayed in Twitter apps. ([#417](https://github.com/manuelcortez/TWBlue/issues/417)) * When creating a filter, TWBlue will show an error if user has not provided a name for the filter. Before, unnamed filters were a cause of config breaks in the application. * It is again possible to read the changelog for TWBlue from the help menu in the menu bar. From b3bf0ab8e683625e1fa4d36b09528e6bf8cdf5b3 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 10 Nov 2021 17:35:24 -0600 Subject: [PATCH 27/33] Removed calls to SetMaxLength in dialogs --- src/wxUI/dialogs/twitterDialogs/tweetDialogs.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index 84ea5fde..e42bd9f4 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -25,7 +25,6 @@ class tweet(wx.Dialog): self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) self.text.SetMinSize((1000, 158)) - self.text.SetMaxLength(max_length) text_sizer.Add(self.text, 1, wx.EXPAND, 0) list_sizer = wx.BoxSizer(wx.HORIZONTAL) mainBox.Add(list_sizer, 1, wx.EXPAND, 0) @@ -163,7 +162,6 @@ class reply(tweet): self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) self.text.SetMinSize((1000, 158)) - self.text.SetMaxLength(max_length) text_sizer.Add(self.text, 1, wx.EXPAND, 0) list_sizer = wx.BoxSizer(wx.HORIZONTAL) mainBox.Add(list_sizer, 1, wx.EXPAND, 0) @@ -248,7 +246,6 @@ class dm(tweet): self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) self.text.SetMinSize((1000, 158)) - self.text.SetMaxLength(max_length) self.text.SetFocus() text_sizer.Add(self.text, 1, wx.EXPAND, 0) list_sizer = wx.BoxSizer(wx.HORIZONTAL) From 8abcb3623dff45e3b21ba93af72ca23d332aaec1 Mon Sep 17 00:00:00 2001 From: Oreonan Date: Thu, 11 Nov 2021 01:09:19 +0100 Subject: [PATCH 28/33] Update french interface --- src/locales/fr/LC_MESSAGES/twblue.po | 522 +++++++++++++++------------ 1 file changed, 294 insertions(+), 228 deletions(-) diff --git a/src/locales/fr/LC_MESSAGES/twblue.po b/src/locales/fr/LC_MESSAGES/twblue.po index e31dd0e1..3842a448 100644 --- a/src/locales/fr/LC_MESSAGES/twblue.po +++ b/src/locales/fr/LC_MESSAGES/twblue.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: TW Blue 0.94\n" -"POT-Creation-Date: 2021-11-03 18:55+0100\n" -"PO-Revision-Date: 2021-11-04 17:00+0100\n" +"POT-Creation-Date: 2021-11-11 01:00+0100\n" +"PO-Revision-Date: 2021-11-11 01:08+0100\n" "Last-Translator: Oreonan \n" "Language-Team: Oreonan \n" "Language: fr\n" @@ -16,10 +16,6 @@ msgstr "" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-Bookmarks: -1,-1,-1,-1,-1,384,-1,-1,-1,-1\n" -#: ../src\controller\attach.py:25 -msgid "Photo" -msgstr "Photo" - #: ../src\controller\buffers\base\base.py:91 msgid "This action is not supported for this buffer" msgstr "Cette action n'est pas supportée pour ce tampon" @@ -98,78 +94,81 @@ msgstr "Abonnements de {username}" msgid "Unknown buffer" msgstr "Tampon inconnu" -#: ../src\controller\buffers\twitter\base.py:88 -#: ../src\controller\buffers\twitter\trends.py:122 -#: ../src\controller\messages.py:214 ../src\wxUI\buffers\base.py:25 +#: ../src\controller\buffers\twitter\base.py:87 +#: ../src\controller\buffers\twitter\trends.py:43 +#: ../src\controller\buffers\twitter\trends.py:134 +#: ../src\controller\messages.py:296 ../src\wxUI\buffers\base.py:25 #: ../src\wxUI\buffers\events.py:15 ../src\wxUI\buffers\trends.py:18 -#: ../src\wxUI\dialogs\message.py:304 ../src\wxUI\sysTrayIcon.py:35 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:312 +#: ../src\wxUI\sysTrayIcon.py:35 msgid "Tweet" msgstr "Tweet" -#: ../src\controller\buffers\twitter\base.py:89 -#: ../src\controller\buffers\twitter\trends.py:123 +#: ../src\controller\buffers\twitter\base.py:88 +#: ../src\controller\buffers\twitter\trends.py:44 +#: ../src\controller\buffers\twitter\trends.py:135 msgid "Write the tweet here" msgstr "Écrivez le tweet ici" -#: ../src\controller\buffers\twitter\base.py:219 +#: ../src\controller\buffers\twitter\base.py:192 msgid "New tweet in {0}" msgstr "Nouveau tweet dans {0}" -#: ../src\controller\buffers\twitter\base.py:222 +#: ../src\controller\buffers\twitter\base.py:195 msgid "{0} new tweets in {1}." msgstr "{0} nouveau tweet dans {1}" -#: ../src\controller\buffers\twitter\base.py:261 +#: ../src\controller\buffers\twitter\base.py:234 #: ../src\controller\buffers\twitter\directMessages.py:88 -#: ../src\controller\buffers\twitter\people.py:180 +#: ../src\controller\buffers\twitter\people.py:174 msgid "%s items retrieved" msgstr "%s éléments récupérés" -#: ../src\controller\buffers\twitter\base.py:293 +#: ../src\controller\buffers\twitter\base.py:266 #: ../src\controller\buffers\twitter\people.py:80 msgid "This buffer is not a timeline; it can't be deleted." msgstr "Ce tampon n'est pas une chronologie ; Impossible de le supprimé." -#: ../src\controller\buffers\twitter\base.py:430 +#: ../src\controller\buffers\twitter\base.py:402 msgid "Reply to {arg0}" msgstr "Répondre à {arg0}" -#: ../src\controller\buffers\twitter\base.py:432 +#: ../src\controller\buffers\twitter\base.py:404 #: ../src\keystrokeEditor\constants.py:11 ../src\wxUI\buffers\base.py:27 msgid "Reply" msgstr "Répondre" -#: ../src\controller\buffers\twitter\base.py:433 +#: ../src\controller\buffers\twitter\base.py:405 msgid "Reply to %s" msgstr "Répondre à %s" -#: ../src\controller\buffers\twitter\base.py:480 -#: ../src\controller\buffers\twitter\directMessages.py:130 +#: ../src\controller\buffers\twitter\base.py:428 +#: ../src\controller\buffers\twitter\directMessages.py:124 msgid "New direct message" msgstr "Nouveau message" -#: ../src\controller\buffers\twitter\base.py:480 -#: ../src\controller\messages.py:200 +#: ../src\controller\buffers\twitter\base.py:428 +#: ../src\controller\messages.py:268 msgid "Direct message to %s" msgstr "Message à %s" -#: ../src\controller\buffers\twitter\base.py:520 +#: ../src\controller\buffers\twitter\base.py:459 msgid "Add your comment to the tweet" msgstr "Ajoutez votre commentaire pour le tweet" -#: ../src\controller\buffers\twitter\base.py:520 +#: ../src\controller\buffers\twitter\base.py:459 msgid "Quote" msgstr "Citer" -#: ../src\controller\buffers\twitter\base.py:596 +#: ../src\controller\buffers\twitter\base.py:520 msgid "Opening URL..." msgstr "Ouverture de l'URL..." -#: ../src\controller\buffers\twitter\base.py:633 +#: ../src\controller\buffers\twitter\base.py:557 msgid "User details" msgstr "Détails de l'utilisateur" -#: ../src\controller\buffers\twitter\base.py:654 +#: ../src\controller\buffers\twitter\base.py:578 msgid "Opening item in web browser..." msgstr "Ouverture de l'élément dans le navigateur Web..." @@ -184,15 +183,15 @@ msgstr "Mention pour %s" msgid "Mention" msgstr "Mention" -#: ../src\controller\buffers\twitter\directMessages.py:133 +#: ../src\controller\buffers\twitter\directMessages.py:127 msgid "{0} new direct messages." msgstr "{0} nouveau message" -#: ../src\controller\buffers\twitter\directMessages.py:136 +#: ../src\controller\buffers\twitter\directMessages.py:130 msgid "This action is not supported in the buffer yet." msgstr "Cette action n'est pas supportée dans le tampon actuel" -#: ../src\controller\buffers\twitter\directMessages.py:146 +#: ../src\controller\buffers\twitter\directMessages.py:140 msgid "" "Getting more items cannot be done in this buffer. Use the direct messages " "buffer instead." @@ -200,11 +199,11 @@ msgstr "" "Récupérer plus d'élément est impossible dans ce tampon, utilisez le tampon " "des messages à la place." -#: ../src\controller\buffers\twitter\people.py:253 +#: ../src\controller\buffers\twitter\people.py:247 msgid "{0} new followers." msgstr "{0} nouvel abonné" -#: ../src\controller\buffers\twitter\trends.py:146 +#: ../src\controller\buffers\twitter\trends.py:150 msgid "This action is not supported in the buffer, yet." msgstr "Cette action n'est pas supportée pour le tampon actuel" @@ -222,7 +221,7 @@ msgstr "Chronologies" #: ../src\controller\mainController.py:359 #: ../src\controller\mainController.py:883 -#: ../src\controller\mainController.py:1585 +#: ../src\controller\mainController.py:1582 msgid "Timeline for {}" msgstr "Chronologie de {}" @@ -232,7 +231,7 @@ msgstr "Chronologies des favoris" #: ../src\controller\mainController.py:363 #: ../src\controller\mainController.py:902 -#: ../src\controller\mainController.py:1587 +#: ../src\controller\mainController.py:1584 msgid "Likes for {}" msgstr "Favoris de {}" @@ -242,7 +241,7 @@ msgstr "Chronologies des abonnés" #: ../src\controller\mainController.py:367 #: ../src\controller\mainController.py:921 -#: ../src\controller\mainController.py:1589 +#: ../src\controller\mainController.py:1586 msgid "Followers for {}" msgstr "Abonnés de {}" @@ -252,7 +251,7 @@ msgstr "Chronologies des abonnements" #: ../src\controller\mainController.py:371 #: ../src\controller\mainController.py:940 -#: ../src\controller\mainController.py:1591 +#: ../src\controller\mainController.py:1588 msgid "Friends for {}" msgstr "Abonnements de {}" @@ -277,7 +276,7 @@ msgstr "Recherche de {}" #: ../src\controller\mainController.py:381 #: ../src\controller\mainController.py:982 -#: ../src\controller\mainController.py:1593 +#: ../src\controller\mainController.py:1590 msgid "Trending topics for %s" msgstr "Tendances pour %s" @@ -320,7 +319,7 @@ msgstr "Ajoute un alias pour l'utilisateur" msgid "Alias has been set correctly for {}." msgstr "L'alias pour {} a correctement été définie" -#: ../src\controller\mainController.py:829 ../src\controller\messages.py:245 +#: ../src\controller\mainController.py:829 ../src\controller\messages.py:327 msgid "MMM D, YYYY. H:m" msgstr "D MMM YYYY à H:m" @@ -410,72 +409,52 @@ msgstr "Tampon muet" msgid "Buffer mute off" msgstr "Tampon non muet" -#: ../src\controller\mainController.py:1545 +#: ../src\controller\mainController.py:1542 msgid "Copied" msgstr "Copié" -#: ../src\controller\mainController.py:1575 +#: ../src\controller\mainController.py:1572 msgid "Unable to update this buffer." msgstr "Impossible de mettre à jour ce tampon." -#: ../src\controller\mainController.py:1578 +#: ../src\controller\mainController.py:1575 msgid "Updating buffer..." msgstr "Actualisation..." -#: ../src\controller\mainController.py:1581 +#: ../src\controller\mainController.py:1578 msgid "{0} items retrieved" msgstr "{0} éléments récupérés" -#: ../src\controller\mainController.py:1600 -#: ../src\controller\mainController.py:1620 +#: ../src\controller\mainController.py:1597 +#: ../src\controller\mainController.py:1617 msgid "Invalid buffer" msgstr "Tampon invalide" -#: ../src\controller\mainController.py:1611 +#: ../src\controller\mainController.py:1608 msgid "Picture {0}" msgstr "Photo {0}" -#: ../src\controller\mainController.py:1612 +#: ../src\controller\mainController.py:1609 msgid "Select the picture" msgstr "Sélectionner la photo" -#: ../src\controller\mainController.py:1631 +#: ../src\controller\mainController.py:1628 msgid "Unable to extract text" msgstr "Impossible d'extraire le texte" -#: ../src\controller\messages.py:56 +#: ../src\controller\messages.py:49 msgid "Translated" msgstr "Traduit" -#: ../src\controller\messages.py:63 -msgid "There's no URL to be shortened" -msgstr "Aucune URL à réduire" - -#: ../src\controller\messages.py:67 ../src\controller\messages.py:75 -msgid "URL shortened" -msgstr "URL réduite" - -#: ../src\controller\messages.py:82 -msgid "There's no URL to be expanded" -msgstr "Aucune URL à élargir" - -#: ../src\controller\messages.py:86 ../src\controller\messages.py:94 -msgid "URL expanded" -msgstr "URL élargi" - -#: ../src\controller\messages.py:108 +#: ../src\controller\messages.py:56 msgid "%s - %s of %d characters" msgstr "%s - %s/%d caractères" -#: ../src\controller\messages.py:112 -msgid "%s - %s characters" -msgstr "%s - %s caractères" - -#: ../src\controller\messages.py:272 +#: ../src\controller\messages.py:354 msgid "View item" msgstr "Voir l'élément" -#: ../src\controller\messages.py:301 +#: ../src\controller\messages.py:379 msgid "Link copied to clipboard." msgstr "Lien copié dans le Presse-papiers" @@ -666,7 +645,7 @@ msgstr "Arrêté" msgid "&Record" msgstr "&Enregistrer" -#: ../src\extra\AudioUploader\audioUploader.py:136 ../src\sound.py:148 +#: ../src\extra\AudioUploader\audioUploader.py:136 ../src\sound.py:147 msgid "Playing..." msgstr "Lecture..." @@ -718,6 +697,9 @@ msgid "%s seconds" msgstr "%s secondes" #: ../src\extra\AudioUploader\wx_transfer_dialogs.py:15 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:36 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:173 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:258 msgid "File" msgstr "Fichier" @@ -1563,7 +1545,7 @@ msgid "New tweet" msgstr "Nouveau tweet" #: ../src\keystrokeEditor\constants.py:12 ../src\wxUI\buffers\base.py:26 -#: ../src\wxUI\commonMessageDialogs.py:10 ../src\wxUI\dialogs\message.py:126 +#: ../src\wxUI\commonMessageDialogs.py:10 msgid "Retweet" msgstr "Retweet" @@ -2009,15 +1991,17 @@ msgid "public" msgstr "public" #: ../src\sessions\twitter\session.py:211 +#: ../src\sessions\twitter\session.py:238 msgid "%s failed. Reason: %s" msgstr "%s erreur. Raison: %s" #: ../src\sessions\twitter\session.py:217 +#: ../src\sessions\twitter\session.py:241 msgid "%s succeeded." msgstr "%s réussi." -#: ../src\sessions\twitter\session.py:426 -#: ../src\sessions\twitter\session.py:504 +#: ../src\sessions\twitter\session.py:450 +#: ../src\sessions\twitter\session.py:528 msgid "Deleted account" msgstr "Compte supprimé" @@ -2045,7 +2029,7 @@ msgstr "Compte en cours d'autorisation..." msgid "Enter your PIN code here" msgstr "Entrer votre code PIN ici" -#: ../src\sound.py:161 +#: ../src\sound.py:160 msgid "Stopped." msgstr "Arrêté." @@ -2099,10 +2083,6 @@ msgstr "" msgid "Client" msgstr "Client" -#: ../src\wxUI\buffers\base.py:12 -msgid "Text" -msgstr "Texte" - #: ../src\wxUI\buffers\base.py:12 ../src\wxUI\buffers\events.py:14 msgid "Date" msgstr "Date" @@ -2114,6 +2094,11 @@ msgstr "Date" msgid "User" msgstr "Utilisateur" +#: ../src\wxUI\buffers\base.py:12 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:48 +msgid "Text" +msgstr "Texte" + #: ../src\wxUI\buffers\base.py:28 msgid "Direct message" msgstr "Message" @@ -2375,55 +2360,6 @@ msgstr "" "{0} a été quitté inopinément lors de sa dernière exécution. Si ce problème " "perciste, veuillez le signaler aux développeurs de {0}." -#: ../src\wxUI\dialogs\attach.py:10 -msgid "Add an attachment" -msgstr "Ajouter un fichier" - -#: ../src\wxUI\dialogs\attach.py:13 -msgid "Attachments" -msgstr "Fichiers joints" - -#: ../src\wxUI\dialogs\attach.py:14 -msgid "Title" -msgstr "Titre" - -#: ../src\wxUI\dialogs\attach.py:14 -msgid "Type" -msgstr "Type" - -#: ../src\wxUI\dialogs\attach.py:19 -msgid "Add attachments" -msgstr "Joindre des fichiers" - -#: ../src\wxUI\dialogs\attach.py:20 -msgid "&Photo" -msgstr "&Image" - -#: ../src\wxUI\dialogs\attach.py:21 -msgid "Remove attachment" -msgstr "Supprimer le fichier joint" - -#: ../src\wxUI\dialogs\attach.py:37 ../src\wxUI\dialogs\message.py:116 -#: ../src\wxUI\dialogs\message.py:175 ../src\wxUI\dialogs\message.py:235 -#: ../src\wxUI\dialogs\update_profile.py:82 -msgid "Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif" -msgstr "Fichiers image (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif" - -#: ../src\wxUI\dialogs\attach.py:37 ../src\wxUI\dialogs\message.py:116 -#: ../src\wxUI\dialogs\message.py:175 ../src\wxUI\dialogs\message.py:235 -#: ../src\wxUI\dialogs\update_profile.py:82 -msgid "Select the picture to be uploaded" -msgstr "Sélectionnez la photo à charger" - -#: ../src\wxUI\dialogs\attach.py:44 -msgid "please provide a description" -msgstr "Veuillez fournir une description" - -#: ../src\wxUI\dialogs\attach.py:44 ../src\wxUI\dialogs\lists.py:14 -#: ../src\wxUI\dialogs\lists.py:70 -msgid "Description" -msgstr "Description" - #: ../src\wxUI\dialogs\configuration.py:15 msgid "Language" msgstr "Langue" @@ -2811,6 +2747,14 @@ msgstr "Propriétaire" msgid "mode" msgstr "mode" +#: ../src\wxUI\dialogs\lists.py:14 ../src\wxUI\dialogs\lists.py:70 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:38 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:127 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:175 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:260 +msgid "Description" +msgstr "Description" + #: ../src\wxUI\dialogs\lists.py:19 ../src\wxUI\dialogs\lists.py:62 msgid "Create a new list" msgstr "Créer une nouvelle liste" @@ -2867,103 +2811,6 @@ msgstr "Sélectionnez une liste pour supprimer l'utilisateur" msgid "Do you really want to delete this list?" msgstr "Voulez-vous vraiment supprimer cette liste ?" -#: ../src\wxUI\dialogs\message.py:73 ../src\wxUI\dialogs\message.py:254 -msgid "&Long tweet" -msgstr "&Tweet long" - -#: ../src\wxUI\dialogs\message.py:74 ../src\wxUI\dialogs\message.py:133 -#: ../src\wxUI\dialogs\message.py:255 -msgid "&Upload image..." -msgstr "&Joindre une image..." - -#: ../src\wxUI\dialogs\message.py:75 ../src\wxUI\dialogs\message.py:134 -#: ../src\wxUI\dialogs\message.py:194 ../src\wxUI\dialogs\message.py:256 -#: ../src\wxUI\dialogs\message.py:359 ../src\wxUI\dialogs\message.py:435 -msgid "Check &spelling..." -msgstr "Correction &orthographique..." - -#: ../src\wxUI\dialogs\message.py:76 ../src\wxUI\dialogs\message.py:135 -#: ../src\wxUI\dialogs\message.py:195 ../src\wxUI\dialogs\message.py:257 -msgid "&Attach audio..." -msgstr "&Joindre un audio..." - -#: ../src\wxUI\dialogs\message.py:77 ../src\wxUI\dialogs\message.py:136 -#: ../src\wxUI\dialogs\message.py:196 ../src\wxUI\dialogs\message.py:258 -msgid "Sh&orten URL" -msgstr "&Réduire URL" - -#: ../src\wxUI\dialogs\message.py:78 ../src\wxUI\dialogs\message.py:137 -#: ../src\wxUI\dialogs\message.py:197 ../src\wxUI\dialogs\message.py:259 -#: ../src\wxUI\dialogs\message.py:360 ../src\wxUI\dialogs\message.py:436 -msgid "&Expand URL" -msgstr "&Élargir URL" - -#: ../src\wxUI\dialogs\message.py:81 ../src\wxUI\dialogs\message.py:140 -#: ../src\wxUI\dialogs\message.py:200 ../src\wxUI\dialogs\message.py:262 -#: ../src\wxUI\dialogs\message.py:362 ../src\wxUI\dialogs\message.py:438 -msgid "&Translate..." -msgstr "&Traduire..." - -#: ../src\wxUI\dialogs\message.py:82 ../src\wxUI\dialogs\message.py:141 -#: ../src\wxUI\dialogs\message.py:186 ../src\wxUI\dialogs\message.py:263 -msgid "Auto&complete users" -msgstr "Sai&sie automatique utilisateurs" - -#: ../src\wxUI\dialogs\message.py:83 ../src\wxUI\dialogs\message.py:142 -#: ../src\wxUI\dialogs\message.py:201 ../src\wxUI\dialogs\message.py:264 -msgid "Sen&d" -msgstr "Envoye&r" - -#: ../src\wxUI\dialogs\message.py:85 ../src\wxUI\dialogs\message.py:144 -#: ../src\wxUI\dialogs\message.py:203 ../src\wxUI\dialogs\message.py:266 -#: ../src\wxUI\dialogs\message.py:363 ../src\wxUI\dialogs\message.py:439 -msgid "C&lose" -msgstr "F&ermer" - -#: ../src\wxUI\dialogs\message.py:184 -msgid "&Recipient" -msgstr "&Destinataire" - -#: ../src\wxUI\dialogs\message.py:245 -msgid "&Mention to all" -msgstr "&Répondre à tout le monde" - -#: ../src\wxUI\dialogs\message.py:299 -msgid "Tweet - %i characters " -msgstr "Tweet - %i caractères " - -#: ../src\wxUI\dialogs\message.py:316 -msgid "Image description" -msgstr "Description de l'image" - -#: ../src\wxUI\dialogs\message.py:327 -msgid "Retweets: " -msgstr "Retweets: " - -#: ../src\wxUI\dialogs\message.py:332 -msgid "Likes: " -msgstr "Favoris: " - -#: ../src\wxUI\dialogs\message.py:337 -msgid "Source: " -msgstr "Source: " - -#: ../src\wxUI\dialogs\message.py:342 ../src\wxUI\dialogs\message.py:423 -msgid "Date: " -msgstr "Date: " - -#: ../src\wxUI\dialogs\message.py:357 ../src\wxUI\dialogs\message.py:433 -msgid "Copy link to clipboard" -msgstr "Copier le lien dans le Presse-papiers" - -#: ../src\wxUI\dialogs\message.py:408 -msgid "View" -msgstr "Voir" - -#: ../src\wxUI\dialogs\message.py:410 -msgid "Item" -msgstr "Élément" - #: ../src\wxUI\dialogs\search.py:12 msgid "Search on Twitter" msgstr "Rechercher sur Twitter" @@ -3045,6 +2892,225 @@ msgstr "Ville" msgid "&Location" msgstr "&Localisation" +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:33 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:49 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:170 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:255 +msgid "Attachments" +msgstr "Fichiers joints" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:37 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:174 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:259 +msgid "Type" +msgstr "Type" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:40 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:177 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:262 +msgid "Delete attachment" +msgstr "Supprimer la pièce jointe" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:45 +msgid "Added Tweets" +msgstr "Tweets ajoutés" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:52 +msgid "Delete tweet" +msgstr "Supprimer le tweet" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:57 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:192 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:267 +msgid "A&dd..." +msgstr "&Ajouter" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:59 +msgid "Add t&weet" +msgstr "A&jouter un tweet" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:62 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:194 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:269 +msgid "&Attach audio..." +msgstr "&Joindre un audio..." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:66 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:198 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:237 +msgid "Auto&complete users" +msgstr "Sai&sie automatique utilisateurs" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:68 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:200 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:273 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:367 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:440 +msgid "Check &spelling..." +msgstr "Correction &orthographique..." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:70 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:202 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:275 +msgid "&Translate" +msgstr "&Traduire" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:74 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:206 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:279 +msgid "Sen&d" +msgstr "Envoye&r" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:118 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:220 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:299 +msgid "Image" +msgstr "Image" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:120 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:222 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:301 +msgid "Video" +msgstr "Vidéo" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:122 +msgid "Poll" +msgstr "Sondage" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:127 +msgid "please provide a description" +msgstr "Veuillez fournir une description" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:134 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:292 +#: ../src\wxUI\dialogs\update_profile.py:82 +msgid "Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif" +msgstr "Fichiers image (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:134 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:292 +#: ../src\wxUI\dialogs\update_profile.py:82 +msgid "Select the picture to be uploaded" +msgstr "Sélectionnez la photo à charger" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:141 +msgid "Select the video to be uploaded" +msgstr "Sélectionnez la vidéo à téléverser" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:141 +msgid "Video files (*.mp4)|*.mp4" +msgstr "Fichiers vidéo (*.mp4)|*.mp4" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:147 +msgid "Error adding attachment" +msgstr "Erreur à l'ajout de la pièce jointe" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:147 +msgid "" +"It is not possible to add more attachments. Please make sure your tweet " +"complies with Twitter'S attachment rules. You can add only one video or GIF " +"in every tweet, and a maximum of 4 photos." +msgstr "" +"Il est impossible de joindre plus de contenu. Assurez-vous que votre tweet " +"respecte les règles d'attachement de Twitter. Vous pouvez seulement inclure " +"une vidéo ou un GIF par tweet, et 4 photos au maximum." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:182 +msgid "&Mention to all" +msgstr "&Répondre à tout le monde" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:235 +msgid "&Recipient" +msgstr "&Destinataire" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:307 +msgid "Tweet - %i characters " +msgstr "Tweet - %i caractères " + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:324 +msgid "Image description" +msgstr "Description de l'image" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:335 +msgid "Retweets: " +msgstr "Retweets: " + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:340 +msgid "Likes: " +msgstr "Favoris: " + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:345 +msgid "Source: " +msgstr "Source: " + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:350 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:428 +msgid "Date: " +msgstr "Date: " + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:365 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:438 +msgid "Copy link to clipboard" +msgstr "Copier le lien dans le Presse-papiers" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:368 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:443 +msgid "&Translate..." +msgstr "&Traduire..." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:369 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:444 +msgid "C&lose" +msgstr "F&ermer" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:413 +msgid "View" +msgstr "Voir" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:415 +msgid "Item" +msgstr "Élément" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:441 +msgid "&Expand URL" +msgstr "&Élargir URL" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:480 +msgid "Add a poll" +msgstr "Ajouter un sondage" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:484 +msgid "Participation time (in days)" +msgstr "Temps de participation (en jours)" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:491 +msgid "Choices" +msgstr "Choix" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:495 +msgid "Option 1" +msgstr "Option 1" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:501 +msgid "Option 2" +msgstr "Option 2" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:507 +msgid "Option 3" +msgstr "Option 3" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:513 +msgid "Option 4" +msgstr "Option 4" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:540 +msgid "Not enough information" +msgstr "Pas assez d'informations" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:540 +msgid "Please make sure you have provided at least two options for the poll." +msgstr "Assurez-vous d'avoir spécifier au moins 2 options pour le sondage." + #: ../src\wxUI\dialogs\update_profile.py:10 msgid "Update your profile" msgstr "Actualiser votre profil" From 0e91ca3d4d4007cc4d6a9875117fcda451160c22 Mon Sep 17 00:00:00 2001 From: Nikola Jovic Date: Thu, 11 Nov 2021 01:30:30 +0100 Subject: [PATCH 29/33] Updated Serbian translation --- src/locales/sr/LC_MESSAGES/twblue.mo | Bin 54001 -> 55001 bytes src/locales/sr/LC_MESSAGES/twblue.po | 631 ++++++++++++++++----------- 2 files changed, 371 insertions(+), 260 deletions(-) diff --git a/src/locales/sr/LC_MESSAGES/twblue.mo b/src/locales/sr/LC_MESSAGES/twblue.mo index 7701f9b14bede7ec7876a98982ea0b1f7f0d5221..4406f6caba50b773402c2458aa993947e8c8a32d 100644 GIT binary patch delta 18395 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 ZBwAVjX)*KI125N_6P-O>wNB6T{U3KBnK=Le diff --git a/src/locales/sr/LC_MESSAGES/twblue.po b/src/locales/sr/LC_MESSAGES/twblue.po index af9ba210..79d3e91d 100644 --- a/src/locales/sr/LC_MESSAGES/twblue.po +++ b/src/locales/sr/LC_MESSAGES/twblue.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: TwBlue 0.80\n" -"POT-Creation-Date: 2021-10-28 16:43+Central Europe Daylight Time\n" -"PO-Revision-Date: 2021-10-28 17:03+0100\n" +"POT-Creation-Date: 2021-11-11 01:16+0100\n" +"PO-Revision-Date: 2021-11-11 01:30+0100\n" "Last-Translator: Nikola Jović \n" "Language-Team: Aleksandar Đurić \n" "Language: sr_RS@latin\n" @@ -18,10 +18,6 @@ msgstr "" "X-Poedit-Bookmarks: -1,442,-1,-1,-1,-1,-1,-1,-1,-1\n" "X-Poedit-SourceCharset: UTF-8\n" -#: ../src\controller\attach.py:25 -msgid "Photo" -msgstr "Slika" - #: ../src\controller\buffers\base\base.py:91 msgid "This action is not supported for this buffer" msgstr "Ova radnja nije podržana na ovom kanalu" @@ -100,101 +96,104 @@ msgstr "Prijatelji korisnika {username}" msgid "Unknown buffer" msgstr "Nepoznat kanal" -#: ../src\controller\buffers\twitter\base.py:88 -#: ../src\controller\buffers\twitter\trends.py:121 -#: ../src\controller\messages.py:214 ../src\wxUI\buffers\base.py:25 +#: ../src\controller\buffers\twitter\base.py:87 +#: ../src\controller\buffers\twitter\trends.py:43 +#: ../src\controller\buffers\twitter\trends.py:134 +#: ../src\controller\messages.py:296 ../src\wxUI\buffers\base.py:25 #: ../src\wxUI\buffers\events.py:15 ../src\wxUI\buffers\trends.py:18 -#: ../src\wxUI\dialogs\message.py:304 ../src\wxUI\sysTrayIcon.py:35 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:312 +#: ../src\wxUI\sysTrayIcon.py:35 msgid "Tweet" msgstr "Tvit" -#: ../src\controller\buffers\twitter\base.py:89 -#: ../src\controller\buffers\twitter\trends.py:122 +#: ../src\controller\buffers\twitter\base.py:88 +#: ../src\controller\buffers\twitter\trends.py:44 +#: ../src\controller\buffers\twitter\trends.py:135 msgid "Write the tweet here" msgstr "Otkucajte tvit ovde:" -#: ../src\controller\buffers\twitter\base.py:219 +#: ../src\controller\buffers\twitter\base.py:192 msgid "New tweet in {0}" msgstr "Novi tvit u kanalu {0}" -#: ../src\controller\buffers\twitter\base.py:222 +#: ../src\controller\buffers\twitter\base.py:195 msgid "{0} new tweets in {1}." msgstr "{0} novih tvitova u kanalu {1}." -#: ../src\controller\buffers\twitter\base.py:261 -#: ../src\controller\buffers\twitter\directMessages.py:87 -#: ../src\controller\buffers\twitter\people.py:180 +#: ../src\controller\buffers\twitter\base.py:234 +#: ../src\controller\buffers\twitter\directMessages.py:88 +#: ../src\controller\buffers\twitter\people.py:174 msgid "%s items retrieved" msgstr "%s primljenih stavki" -#: ../src\controller\buffers\twitter\base.py:293 +#: ../src\controller\buffers\twitter\base.py:266 #: ../src\controller\buffers\twitter\people.py:80 msgid "This buffer is not a timeline; it can't be deleted." msgstr "Ovaj kanal nije vremenska linija i ne može biti izbrisan." -#: ../src\controller\buffers\twitter\base.py:430 +#: ../src\controller\buffers\twitter\base.py:402 msgid "Reply to {arg0}" msgstr "Odgovori {arg0}" -#: ../src\controller\buffers\twitter\base.py:432 +#: ../src\controller\buffers\twitter\base.py:404 #: ../src\keystrokeEditor\constants.py:11 ../src\wxUI\buffers\base.py:27 msgid "Reply" msgstr "Odgovori" -#: ../src\controller\buffers\twitter\base.py:433 +#: ../src\controller\buffers\twitter\base.py:405 msgid "Reply to %s" msgstr "Odgovori %s" -#: ../src\controller\buffers\twitter\base.py:480 -#: ../src\controller\buffers\twitter\directMessages.py:129 +#: ../src\controller\buffers\twitter\base.py:428 +#: ../src\controller\buffers\twitter\directMessages.py:124 msgid "New direct message" msgstr "Nova direktna poruka" -#: ../src\controller\buffers\twitter\base.py:480 -#: ../src\controller\messages.py:200 +#: ../src\controller\buffers\twitter\base.py:428 +#: ../src\controller\messages.py:268 msgid "Direct message to %s" msgstr "Direktna poruka za %s" -#: ../src\controller\buffers\twitter\base.py:520 +#: ../src\controller\buffers\twitter\base.py:459 msgid "Add your comment to the tweet" msgstr "Dodajte vaš komentar u tvit" -#: ../src\controller\buffers\twitter\base.py:520 +#: ../src\controller\buffers\twitter\base.py:459 msgid "Quote" msgstr "Citiraj" -#: ../src\controller\buffers\twitter\base.py:596 +#: ../src\controller\buffers\twitter\base.py:520 msgid "Opening URL..." msgstr "Otvaram vezu..." -#: ../src\controller\buffers\twitter\base.py:633 +#: ../src\controller\buffers\twitter\base.py:557 msgid "User details" msgstr "Podaci o korisniku" -#: ../src\controller\buffers\twitter\base.py:654 +#: ../src\controller\buffers\twitter\base.py:578 msgid "Opening item in web browser..." msgstr "Otvaram stavku u Web pretraživaču..." -#: ../src\controller\buffers\twitter\directMessages.py:92 +#: ../src\controller\buffers\twitter\directMessages.py:93 #: ../src\controller\buffers\twitter\people.py:95 msgid "Mention to %s" msgstr "Spomeni %s" -#: ../src\controller\buffers\twitter\directMessages.py:92 +#: ../src\controller\buffers\twitter\directMessages.py:93 #: ../src\controller\buffers\twitter\people.py:95 #: ../src\wxUI\buffers\people.py:17 msgid "Mention" msgstr "Spomeni" -#: ../src\controller\buffers\twitter\directMessages.py:132 +#: ../src\controller\buffers\twitter\directMessages.py:127 msgid "{0} new direct messages." msgstr "{0} novih direktnih poruka." -#: ../src\controller\buffers\twitter\directMessages.py:135 +#: ../src\controller\buffers\twitter\directMessages.py:130 msgid "This action is not supported in the buffer yet." msgstr "Ova radnja još uvek nije podržana na ovom kanalu." -#: ../src\controller\buffers\twitter\directMessages.py:145 +#: ../src\controller\buffers\twitter\directMessages.py:140 msgid "" "Getting more items cannot be done in this buffer. Use the direct messages " "buffer instead." @@ -202,11 +201,11 @@ msgstr "" "Nemoguće preuzeti dodatne stavke za ovaj kanal. Umesto toga, koristite kanal " "direktne poruke." -#: ../src\controller\buffers\twitter\people.py:253 +#: ../src\controller\buffers\twitter\people.py:247 msgid "{0} new followers." msgstr "{0} novih pratilaca." -#: ../src\controller\buffers\twitter\trends.py:145 +#: ../src\controller\buffers\twitter\trends.py:150 msgid "This action is not supported in the buffer, yet." msgstr "Ova radnja još uvek nije podržana na ovom kanalu" @@ -224,7 +223,7 @@ msgstr "Vremenske linije" #: ../src\controller\mainController.py:359 #: ../src\controller\mainController.py:883 -#: ../src\controller\mainController.py:1585 +#: ../src\controller\mainController.py:1582 msgid "Timeline for {}" msgstr "Vremenska linija od {}" @@ -234,7 +233,7 @@ msgstr "Vremenska linija omiljenih tvitova" #: ../src\controller\mainController.py:363 #: ../src\controller\mainController.py:902 -#: ../src\controller\mainController.py:1587 +#: ../src\controller\mainController.py:1584 msgid "Likes for {}" msgstr "Sviđanja od {}" @@ -244,7 +243,7 @@ msgstr "Vremenske linije pratilaca" #: ../src\controller\mainController.py:367 #: ../src\controller\mainController.py:921 -#: ../src\controller\mainController.py:1589 +#: ../src\controller\mainController.py:1586 msgid "Followers for {}" msgstr "Pratioci od {}" @@ -254,7 +253,7 @@ msgstr "Vremenske linije praćenih korisnika" #: ../src\controller\mainController.py:371 #: ../src\controller\mainController.py:940 -#: ../src\controller\mainController.py:1591 +#: ../src\controller\mainController.py:1588 msgid "Friends for {}" msgstr "Prijatelji od {}" @@ -279,6 +278,7 @@ msgstr "Pretraga za {}" #: ../src\controller\mainController.py:381 #: ../src\controller\mainController.py:982 +#: ../src\controller\mainController.py:1590 msgid "Trending topics for %s" msgstr "Teme u trendu za %s" @@ -321,7 +321,7 @@ msgstr "Dodaj nadimak za korisnika" msgid "Alias has been set correctly for {}." msgstr "Nadimak za {} je uspešno podešen." -#: ../src\controller\mainController.py:829 ../src\controller\messages.py:245 +#: ../src\controller\mainController.py:829 ../src\controller\messages.py:327 msgid "MMM D, YYYY. H:m" msgstr "MMM D, YYYY. H:m" @@ -410,72 +410,52 @@ msgstr "Utišavanje kanala uključeno" msgid "Buffer mute off" msgstr "Utišavanje kanala isključeno" -#: ../src\controller\mainController.py:1545 +#: ../src\controller\mainController.py:1542 msgid "Copied" msgstr "Kopirano" -#: ../src\controller\mainController.py:1575 +#: ../src\controller\mainController.py:1572 msgid "Unable to update this buffer." msgstr "Ne mogu da ažuriram ovaj kanal." -#: ../src\controller\mainController.py:1578 +#: ../src\controller\mainController.py:1575 msgid "Updating buffer..." msgstr "Ažuriram kanal..." -#: ../src\controller\mainController.py:1581 +#: ../src\controller\mainController.py:1578 msgid "{0} items retrieved" msgstr "{0} primljenih stavki" -#: ../src\controller\mainController.py:1598 -#: ../src\controller\mainController.py:1618 +#: ../src\controller\mainController.py:1597 +#: ../src\controller\mainController.py:1617 msgid "Invalid buffer" msgstr "Nevažeći kanal" -#: ../src\controller\mainController.py:1609 +#: ../src\controller\mainController.py:1608 msgid "Picture {0}" msgstr "Slika {0}" -#: ../src\controller\mainController.py:1610 +#: ../src\controller\mainController.py:1609 msgid "Select the picture" msgstr "Izaberite sliku" -#: ../src\controller\mainController.py:1629 +#: ../src\controller\mainController.py:1628 msgid "Unable to extract text" msgstr "Ne mogu da izdvojim tekst." -#: ../src\controller\messages.py:56 +#: ../src\controller\messages.py:49 msgid "Translated" msgstr "Prevedeno" -#: ../src\controller\messages.py:63 -msgid "There's no URL to be shortened" -msgstr "Nema veze koja bi mogla biti skraćena" - -#: ../src\controller\messages.py:67 ../src\controller\messages.py:75 -msgid "URL shortened" -msgstr "Veza je skraćena" - -#: ../src\controller\messages.py:82 -msgid "There's no URL to be expanded" -msgstr "Nema veze koja bi mogla biti proširena" - -#: ../src\controller\messages.py:86 ../src\controller\messages.py:94 -msgid "URL expanded" -msgstr "Veza je proširena" - -#: ../src\controller\messages.py:108 +#: ../src\controller\messages.py:56 msgid "%s - %s of %d characters" msgstr "%s - %s od %d znakova" -#: ../src\controller\messages.py:112 -msgid "%s - %s characters" -msgstr "%s - %s znakova" - -#: ../src\controller\messages.py:272 +#: ../src\controller\messages.py:354 msgid "View item" msgstr "Prikaži stavku" -#: ../src\controller\messages.py:301 +#: ../src\controller\messages.py:379 msgid "Link copied to clipboard." msgstr "Link kopiran u privremenu memoriju." @@ -667,7 +647,7 @@ msgstr "Zaustavljeno" msgid "&Record" msgstr "&Snimi" -#: ../src\extra\AudioUploader\audioUploader.py:136 ../src\sound.py:148 +#: ../src\extra\AudioUploader\audioUploader.py:136 ../src\sound.py:147 msgid "Playing..." msgstr "Reprodukujem..." @@ -719,6 +699,9 @@ msgid "%s seconds" msgstr "%s sekundi" #: ../src\extra\AudioUploader\wx_transfer_dialogs.py:15 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:36 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:173 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:258 msgid "File" msgstr "Datoteka" @@ -1486,7 +1469,7 @@ msgstr "" msgid "Send report" msgstr "Pošalji izveštaj" -#: ../src\issueReporter\wx_ui.py:75 ../src\wxUI\dialogs\filterDialogs.py:84 +#: ../src\issueReporter\wx_ui.py:75 ../src\wxUI\dialogs\filterDialogs.py:83 #: ../src\wxUI\dialogs\find.py:23 msgid "Cancel" msgstr "Otkaži" @@ -1560,7 +1543,7 @@ msgid "New tweet" msgstr "Novi tvit" #: ../src\keystrokeEditor\constants.py:12 ../src\wxUI\buffers\base.py:26 -#: ../src\wxUI\commonMessageDialogs.py:10 ../src\wxUI\dialogs\message.py:126 +#: ../src\wxUI\commonMessageDialogs.py:10 msgid "Retweet" msgstr "Retvituj" @@ -1773,7 +1756,7 @@ msgstr "Prečica" msgid "Action" msgstr "Radnja" -#: ../src\keystrokeEditor\wx_ui.py:18 ../src\wxUI\dialogs\filterDialogs.py:131 +#: ../src\keystrokeEditor\wx_ui.py:18 ../src\wxUI\dialogs\filterDialogs.py:135 #: ../src\wxUI\dialogs\lists.py:20 ../src\wxUI\dialogs\userAliasDialogs.py:53 msgid "Edit" msgstr "Izmeni" @@ -1823,7 +1806,7 @@ msgstr "Windows" msgid "Key" msgstr "Taster" -#: ../src\keystrokeEditor\wx_ui.py:71 ../src\wxUI\dialogs\filterDialogs.py:82 +#: ../src\keystrokeEditor\wx_ui.py:71 ../src\wxUI\dialogs\filterDialogs.py:80 #: ../src\wxUI\dialogs\find.py:21 ../src\wxUI\dialogs\userAliasDialogs.py:23 #: ../src\wxUI\dialogs\utils.py:36 msgid "OK" @@ -1999,16 +1982,18 @@ msgstr "Privatno" msgid "public" msgstr "Javno" -#: ../src\sessions\twitter\session.py:209 +#: ../src\sessions\twitter\session.py:211 +#: ../src\sessions\twitter\session.py:238 msgid "%s failed. Reason: %s" msgstr "%s nije uspelo. Razlog: %s" -#: ../src\sessions\twitter\session.py:215 +#: ../src\sessions\twitter\session.py:217 +#: ../src\sessions\twitter\session.py:241 msgid "%s succeeded." msgstr "%s uspelo." -#: ../src\sessions\twitter\session.py:424 -#: ../src\sessions\twitter\session.py:502 +#: ../src\sessions\twitter\session.py:450 +#: ../src\sessions\twitter\session.py:528 msgid "Deleted account" msgstr "Obrisan nalog" @@ -2036,7 +2021,7 @@ msgstr "Autorizacija naloga..." msgid "Enter your PIN code here" msgstr "Ovde upišite vaš PIN kod" -#: ../src\sound.py:161 +#: ../src\sound.py:160 msgid "Stopped." msgstr "Zaustavljeno" @@ -2088,10 +2073,6 @@ msgstr "" msgid "Client" msgstr "Klijent" -#: ../src\wxUI\buffers\base.py:12 -msgid "Text" -msgstr "Tekst" - #: ../src\wxUI\buffers\base.py:12 ../src\wxUI\buffers\events.py:14 msgid "Date" msgstr "Datum" @@ -2103,6 +2084,11 @@ msgstr "Datum" msgid "User" msgstr "Korisnik" +#: ../src\wxUI\buffers\base.py:12 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:48 +msgid "Text" +msgstr "Tekst" + #: ../src\wxUI\buffers\base.py:28 msgid "Direct message" msgstr "Direktna poruka" @@ -2359,55 +2345,6 @@ msgstr "" "{0} je neočekivano zatvoren pri poslednjem pokretanju. Ako se problem " "nastavi, molimo prijavite ga{0} programerima." -#: ../src\wxUI\dialogs\attach.py:10 -msgid "Add an attachment" -msgstr "Dodaj prilog" - -#: ../src\wxUI\dialogs\attach.py:13 -msgid "Attachments" -msgstr "Prilozi" - -#: ../src\wxUI\dialogs\attach.py:14 -msgid "Title" -msgstr "Naslov" - -#: ../src\wxUI\dialogs\attach.py:14 -msgid "Type" -msgstr "Vrsta" - -#: ../src\wxUI\dialogs\attach.py:19 -msgid "Add attachments" -msgstr "Dodaj priloge" - -#: ../src\wxUI\dialogs\attach.py:20 -msgid "&Photo" -msgstr "Slika" - -#: ../src\wxUI\dialogs\attach.py:21 -msgid "Remove attachment" -msgstr "Ukloni prilog" - -#: ../src\wxUI\dialogs\attach.py:37 ../src\wxUI\dialogs\message.py:116 -#: ../src\wxUI\dialogs\message.py:175 ../src\wxUI\dialogs\message.py:235 -#: ../src\wxUI\dialogs\update_profile.py:82 -msgid "Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif" -msgstr "Datoteke sa slikama" - -#: ../src\wxUI\dialogs\attach.py:37 ../src\wxUI\dialogs\message.py:116 -#: ../src\wxUI\dialogs\message.py:175 ../src\wxUI\dialogs\message.py:235 -#: ../src\wxUI\dialogs\update_profile.py:82 -msgid "Select the picture to be uploaded" -msgstr "Izaberite sliku koju želite da otpremite" - -#: ../src\wxUI\dialogs\attach.py:44 -msgid "please provide a description" -msgstr "Molimo vas navedite opis:" - -#: ../src\wxUI\dialogs\attach.py:44 ../src\wxUI\dialogs\lists.py:14 -#: ../src\wxUI\dialogs\lists.py:70 -msgid "Description" -msgstr "Opis" - #: ../src\wxUI\dialogs\configuration.py:15 msgid "Language" msgstr "Jezik" @@ -2544,7 +2481,7 @@ msgid "Status" msgstr "Status" #: ../src\wxUI\dialogs\configuration.py:144 -#: ../src\wxUI\dialogs\filterDialogs.py:126 +#: ../src\wxUI\dialogs\filterDialogs.py:130 msgid "Buffer" msgstr "Kanal" @@ -2669,91 +2606,99 @@ msgstr "Dodaci" msgid "Save" msgstr "Sačuvaj" -#: ../src\wxUI\dialogs\filterDialogs.py:16 +#: ../src\wxUI\dialogs\filterDialogs.py:13 msgid "Create a filter for this buffer" msgstr "Napravi filter za ovaj kanal" -#: ../src\wxUI\dialogs\filterDialogs.py:17 +#: ../src\wxUI\dialogs\filterDialogs.py:14 msgid "Filter title" msgstr "Naziv filtera" -#: ../src\wxUI\dialogs\filterDialogs.py:26 -#: ../src\wxUI\dialogs\filterDialogs.py:126 +#: ../src\wxUI\dialogs\filterDialogs.py:24 +#: ../src\wxUI\dialogs\filterDialogs.py:130 msgid "Filter by word" msgstr "Filtriraj na osnovu reči" -#: ../src\wxUI\dialogs\filterDialogs.py:27 +#: ../src\wxUI\dialogs\filterDialogs.py:25 msgid "Ignore tweets wich contain the following word" msgstr "Zanemari tvitove koji sadrže sledeću reč" -#: ../src\wxUI\dialogs\filterDialogs.py:28 +#: ../src\wxUI\dialogs\filterDialogs.py:26 msgid "Ignore tweets without the following word" msgstr "Zanemari tvitove bez sledeće reči" -#: ../src\wxUI\dialogs\filterDialogs.py:33 +#: ../src\wxUI\dialogs\filterDialogs.py:31 msgid "word" msgstr "Reč" -#: ../src\wxUI\dialogs\filterDialogs.py:38 +#: ../src\wxUI\dialogs\filterDialogs.py:36 msgid "Allow retweets" msgstr "Dozvoli retvitove" -#: ../src\wxUI\dialogs\filterDialogs.py:39 +#: ../src\wxUI\dialogs\filterDialogs.py:37 msgid "Allow quoted tweets" msgstr "Dozvoli citirane tvitove" -#: ../src\wxUI\dialogs\filterDialogs.py:40 +#: ../src\wxUI\dialogs\filterDialogs.py:38 msgid "Allow replies" msgstr "Dozvoli odgovore" -#: ../src\wxUI\dialogs\filterDialogs.py:48 +#: ../src\wxUI\dialogs\filterDialogs.py:46 msgid "Use this term as a regular expression" msgstr "Koristi ovaj termin kao regulara nizraz" -#: ../src\wxUI\dialogs\filterDialogs.py:50 -#: ../src\wxUI\dialogs\filterDialogs.py:126 +#: ../src\wxUI\dialogs\filterDialogs.py:48 +#: ../src\wxUI\dialogs\filterDialogs.py:130 msgid "Filter by language" msgstr "Filtriraj na osnovu jezika" -#: ../src\wxUI\dialogs\filterDialogs.py:51 +#: ../src\wxUI\dialogs\filterDialogs.py:49 msgid "Load tweets in the following languages" msgstr "Učitaj tvitove na sledećim jezicima" -#: ../src\wxUI\dialogs\filterDialogs.py:52 +#: ../src\wxUI\dialogs\filterDialogs.py:50 msgid "Ignore tweets in the following languages" msgstr "Zanemari tvitove na sledećim jezicima" -#: ../src\wxUI\dialogs\filterDialogs.py:53 +#: ../src\wxUI\dialogs\filterDialogs.py:51 msgid "Don't filter by language" msgstr "Ne filtriraj na osnovu jezika" -#: ../src\wxUI\dialogs\filterDialogs.py:64 +#: ../src\wxUI\dialogs\filterDialogs.py:62 msgid "Supported languages" msgstr "Podržani jezici" -#: ../src\wxUI\dialogs\filterDialogs.py:69 +#: ../src\wxUI\dialogs\filterDialogs.py:67 msgid "Add selected language to filter" msgstr "Dodaj izabrani jezik u filter" -#: ../src\wxUI\dialogs\filterDialogs.py:73 +#: ../src\wxUI\dialogs\filterDialogs.py:71 msgid "Selected languages" msgstr "Izabrani jezici" -#: ../src\wxUI\dialogs\filterDialogs.py:75 -#: ../src\wxUI\dialogs\filterDialogs.py:133 ../src\wxUI\dialogs\lists.py:21 +#: ../src\wxUI\dialogs\filterDialogs.py:73 +#: ../src\wxUI\dialogs\filterDialogs.py:137 ../src\wxUI\dialogs\lists.py:21 #: ../src\wxUI\dialogs\lists.py:132 ../src\wxUI\dialogs\userAliasDialogs.py:57 msgid "Remove" msgstr "Ukloni" -#: ../src\wxUI\dialogs\filterDialogs.py:123 +#: ../src\wxUI\dialogs\filterDialogs.py:120 +msgid "Missing filter name" +msgstr "Ime filtera nedostaje" + +#: ../src\wxUI\dialogs\filterDialogs.py:120 +msgid "You must define a name for the filter before creating it." +msgstr "Morate upisati ime za filter pre nego što ga napravite." + +#: ../src\wxUI\dialogs\filterDialogs.py:127 msgid "Manage filters" msgstr "Upravljanje filterima" -#: ../src\wxUI\dialogs\filterDialogs.py:125 +#: ../src\wxUI\dialogs\filterDialogs.py:129 msgid "Filters" msgstr "Filteri" -#: ../src\wxUI\dialogs\filterDialogs.py:126 +#: ../src\wxUI\dialogs\filterDialogs.py:130 msgid "Filter" msgstr "Filter" @@ -2785,6 +2730,14 @@ msgstr "Vlasnik" msgid "mode" msgstr "Način" +#: ../src\wxUI\dialogs\lists.py:14 ../src\wxUI\dialogs\lists.py:70 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:38 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:127 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:175 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:260 +msgid "Description" +msgstr "Opis" + #: ../src\wxUI\dialogs\lists.py:19 ../src\wxUI\dialogs\lists.py:62 msgid "Create a new list" msgstr "Stvori novu listu" @@ -2841,103 +2794,6 @@ msgstr "Izaberite listu sa koje želite da uklonite korisnika" msgid "Do you really want to delete this list?" msgstr "Želite li zaista da izbrišete ovu listu?" -#: ../src\wxUI\dialogs\message.py:73 ../src\wxUI\dialogs\message.py:254 -msgid "&Long tweet" -msgstr "Dug tvit" - -#: ../src\wxUI\dialogs\message.py:74 ../src\wxUI\dialogs\message.py:133 -#: ../src\wxUI\dialogs\message.py:255 -msgid "&Upload image..." -msgstr "Otpremi sliku..." - -#: ../src\wxUI\dialogs\message.py:75 ../src\wxUI\dialogs\message.py:134 -#: ../src\wxUI\dialogs\message.py:194 ../src\wxUI\dialogs\message.py:256 -#: ../src\wxUI\dialogs\message.py:359 ../src\wxUI\dialogs\message.py:435 -msgid "Check &spelling..." -msgstr "Proveri pravopis..." - -#: ../src\wxUI\dialogs\message.py:76 ../src\wxUI\dialogs\message.py:135 -#: ../src\wxUI\dialogs\message.py:195 ../src\wxUI\dialogs\message.py:257 -msgid "&Attach audio..." -msgstr "Priloži zvučni zapis..." - -#: ../src\wxUI\dialogs\message.py:77 ../src\wxUI\dialogs\message.py:136 -#: ../src\wxUI\dialogs\message.py:196 ../src\wxUI\dialogs\message.py:258 -msgid "Sh&orten URL" -msgstr "Skrati vezu" - -#: ../src\wxUI\dialogs\message.py:78 ../src\wxUI\dialogs\message.py:137 -#: ../src\wxUI\dialogs\message.py:197 ../src\wxUI\dialogs\message.py:259 -#: ../src\wxUI\dialogs\message.py:360 ../src\wxUI\dialogs\message.py:436 -msgid "&Expand URL" -msgstr "Proširi vezu" - -#: ../src\wxUI\dialogs\message.py:81 ../src\wxUI\dialogs\message.py:140 -#: ../src\wxUI\dialogs\message.py:200 ../src\wxUI\dialogs\message.py:262 -#: ../src\wxUI\dialogs\message.py:362 ../src\wxUI\dialogs\message.py:438 -msgid "&Translate..." -msgstr "Prevedi..." - -#: ../src\wxUI\dialogs\message.py:82 ../src\wxUI\dialogs\message.py:141 -#: ../src\wxUI\dialogs\message.py:186 ../src\wxUI\dialogs\message.py:263 -msgid "Auto&complete users" -msgstr "&Automatsko dovršavanje" - -#: ../src\wxUI\dialogs\message.py:83 ../src\wxUI\dialogs\message.py:142 -#: ../src\wxUI\dialogs\message.py:201 ../src\wxUI\dialogs\message.py:264 -msgid "Sen&d" -msgstr "Pošalji" - -#: ../src\wxUI\dialogs\message.py:85 ../src\wxUI\dialogs\message.py:144 -#: ../src\wxUI\dialogs\message.py:203 ../src\wxUI\dialogs\message.py:266 -#: ../src\wxUI\dialogs\message.py:363 ../src\wxUI\dialogs\message.py:439 -msgid "C&lose" -msgstr "Zatvori" - -#: ../src\wxUI\dialogs\message.py:184 -msgid "&Recipient" -msgstr "Primalac" - -#: ../src\wxUI\dialogs\message.py:245 -msgid "&Mention to all" -msgstr "Spomeni sve" - -#: ../src\wxUI\dialogs\message.py:299 -msgid "Tweet - %i characters " -msgstr "Tvit - %i znakova" - -#: ../src\wxUI\dialogs\message.py:316 -msgid "Image description" -msgstr "Opis slike" - -#: ../src\wxUI\dialogs\message.py:327 -msgid "Retweets: " -msgstr "Retvitova" - -#: ../src\wxUI\dialogs\message.py:332 -msgid "Likes: " -msgstr "Sviđanja:" - -#: ../src\wxUI\dialogs\message.py:337 -msgid "Source: " -msgstr "Izvor:" - -#: ../src\wxUI\dialogs\message.py:342 ../src\wxUI\dialogs\message.py:423 -msgid "Date: " -msgstr "Datum:" - -#: ../src\wxUI\dialogs\message.py:357 ../src\wxUI\dialogs\message.py:433 -msgid "Copy link to clipboard" -msgstr "Kopiraj link u privremenu memoriju" - -#: ../src\wxUI\dialogs\message.py:408 -msgid "View" -msgstr "Vidi" - -#: ../src\wxUI\dialogs\message.py:410 -msgid "Item" -msgstr "Stavka" - #: ../src\wxUI\dialogs\search.py:12 msgid "Search on Twitter" msgstr "Pretraži ttwitter" @@ -3019,6 +2875,225 @@ msgstr "Gradu" msgid "&Location" msgstr "Mesto" +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:33 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:49 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:170 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:255 +msgid "Attachments" +msgstr "Prilozi" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:37 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:174 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:259 +msgid "Type" +msgstr "Vrsta" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:40 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:177 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:262 +msgid "Delete attachment" +msgstr "Ukloni prilog" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:45 +msgid "Added Tweets" +msgstr "Dodati tvitovi" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:52 +msgid "Delete tweet" +msgstr "Obriši tvit" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:57 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:192 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:267 +msgid "A&dd..." +msgstr "&Dodaj..." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:59 +msgid "Add t&weet" +msgstr "Dodaj t&vit" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:62 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:194 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:269 +msgid "&Attach audio..." +msgstr "Priloži zvučni zapis..." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:66 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:198 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:237 +msgid "Auto&complete users" +msgstr "&Automatsko dovršavanje" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:68 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:200 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:273 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:367 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:440 +msgid "Check &spelling..." +msgstr "Proveri pravopis..." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:70 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:202 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:275 +msgid "&Translate" +msgstr "&Prevedi" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:74 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:206 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:279 +msgid "Sen&d" +msgstr "Pošalji" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:118 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:220 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:299 +msgid "Image" +msgstr "Slika" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:120 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:222 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:301 +msgid "Video" +msgstr "Video" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:122 +msgid "Poll" +msgstr "Anketa" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:127 +msgid "please provide a description" +msgstr "Molimo vas navedite opis:" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:134 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:292 +#: ../src\wxUI\dialogs\update_profile.py:82 +msgid "Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif" +msgstr "Datoteke sa slikama" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:134 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:292 +#: ../src\wxUI\dialogs\update_profile.py:82 +msgid "Select the picture to be uploaded" +msgstr "Izaberite sliku koju želite da otpremite" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:141 +msgid "Select the video to be uploaded" +msgstr "Izaberite video zapis koji želite da otpremite" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:141 +msgid "Video files (*.mp4)|*.mp4" +msgstr "Datoteke video zapisa (*.mp4)|*.mp4" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:147 +msgid "Error adding attachment" +msgstr "Greška pri dodavanju priloga" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:147 +msgid "" +"It is not possible to add more attachments. Please make sure your tweet " +"complies with Twitter'S attachment rules. You can add only one video or GIF " +"in every tweet, and a maximum of 4 photos." +msgstr "" +"Nije moguće dodati više priloga. Molimo uverite se da vaš tvit poštuje " +"Twitter pravila o prilozima. Možete dodati samo jedan video ili GIF u svaki " +"tvit, i najviše 4 slike." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:182 +msgid "&Mention to all" +msgstr "Spomeni sve" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:235 +msgid "&Recipient" +msgstr "Primalac" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:307 +msgid "Tweet - %i characters " +msgstr "Tvit - %i znakova" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:324 +msgid "Image description" +msgstr "Opis slike" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:335 +msgid "Retweets: " +msgstr "Retvitova" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:340 +msgid "Likes: " +msgstr "Sviđanja:" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:345 +msgid "Source: " +msgstr "Izvor:" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:350 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:428 +msgid "Date: " +msgstr "Datum:" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:365 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:438 +msgid "Copy link to clipboard" +msgstr "Kopiraj link u privremenu memoriju" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:368 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:443 +msgid "&Translate..." +msgstr "Prevedi..." + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:369 +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:444 +msgid "C&lose" +msgstr "Zatvori" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:413 +msgid "View" +msgstr "Vidi" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:415 +msgid "Item" +msgstr "Stavka" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:441 +msgid "&Expand URL" +msgstr "Proširi vezu" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:480 +msgid "Add a poll" +msgstr "Dodaj anketu" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:484 +msgid "Participation time (in days)" +msgstr "Vreme učešća (u danima)" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:491 +msgid "Choices" +msgstr "Opcije" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:495 +msgid "Option 1" +msgstr "Opcija 1" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:501 +msgid "Option 2" +msgstr "Opcija 2" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:507 +msgid "Option 3" +msgstr "Opcija 3" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:513 +msgid "Option 4" +msgstr "Opcija 4" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:540 +msgid "Not enough information" +msgstr "Nema dovoljno informacija" + +#: ../src\wxUI\dialogs\twitterDialogs\tweetDialogs.py:540 +msgid "Please make sure you have provided at least two options for the poll." +msgstr "Molimo uverite se da ste ponudili bar dve opcije u anketi." + #: ../src\wxUI\dialogs\update_profile.py:10 msgid "Update your profile" msgstr "Ažurirajte vaš profil" @@ -3435,6 +3510,42 @@ msgstr "Ažuriraj" msgid "Your {0} version is up to date" msgstr "Imate najnoviju verziju {0}." +#~ msgid "Photo" +#~ msgstr "Slika" + +#~ msgid "There's no URL to be shortened" +#~ msgstr "Nema veze koja bi mogla biti skraćena" + +#~ msgid "URL shortened" +#~ msgstr "Veza je skraćena" + +#~ msgid "There's no URL to be expanded" +#~ msgstr "Nema veze koja bi mogla biti proširena" + +#~ msgid "URL expanded" +#~ msgstr "Veza je proširena" + +#~ msgid "%s - %s characters" +#~ msgstr "%s - %s znakova" + +#~ msgid "Title" +#~ msgstr "Naslov" + +#~ msgid "Add attachments" +#~ msgstr "Dodaj priloge" + +#~ msgid "&Photo" +#~ msgstr "Slika" + +#~ msgid "&Long tweet" +#~ msgstr "Dug tvit" + +#~ msgid "&Upload image..." +#~ msgstr "Otpremi sliku..." + +#~ msgid "Sh&orten URL" +#~ msgstr "Skrati vezu" + #~ msgid "Friends' Timelines" #~ msgstr "Vremenska linija prijatelja" From 622b0e6128228de20d0b7ec913bed795809d4e8e Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 11 Nov 2021 08:36:29 -0600 Subject: [PATCH 30/33] Access correctly on_reply_to_tweet_id from the dict response returned by call to V2 API --- src/sessions/twitter/session.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index 38f4baae..1b985c9c 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -622,8 +622,7 @@ class Session(base.baseSession): for obj in tweets: if len(obj["attachments"]) == 0: item = self.api_call_v2(call_name="create_tweet", text=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"]) - print(item) - in_reply_to_status_id = item.data.id + in_reply_to_status_id = item.data["id"] else: media_ids = [] for i in obj["attachments"]: @@ -632,7 +631,7 @@ class Session(base.baseSession): self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) media_ids.append(img.media_id) item = self.api_call_v2(call_name="create_tweet", status=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, media_ids=media_ids, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"]) - in_reply_to_status_id = item.data.id + in_reply_to_status_id = item.data["id"] def reply(self, text="", in_reply_to_status_id=None, attachments=[], *args, **kwargs): if len(attachments) == 0: From 538d6137d044e3f0f2eb0e61acc241b479d6cf2e Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 11 Nov 2021 08:37:07 -0600 Subject: [PATCH 31/33] Limit Poll options to 25 characters each --- src/wxUI/dialogs/twitterDialogs/tweetDialogs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index e42bd9f4..8db397ec 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -495,24 +495,28 @@ class poll(wx.Dialog): label_2 = wx.StaticText(self, wx.ID_ANY, _("Option 1")) option1_sizer.Add(label_2, 0, 0, 0) self.option1 = wx.TextCtrl(self, wx.ID_ANY, "") + self.option1.SetMaxLength(25) option1_sizer.Add(self.option1, 0, 0, 0) option2_sizer = wx.BoxSizer(wx.HORIZONTAL) sizer_2.Add(option2_sizer, 1, wx.EXPAND, 0) label_3 = wx.StaticText(self, wx.ID_ANY, _("Option 2")) option2_sizer.Add(label_3, 0, 0, 0) self.option2 = wx.TextCtrl(self, wx.ID_ANY, "") + self.option2.SetMaxLength(25) option2_sizer.Add(self.option2, 0, 0, 0) option3_sizer = wx.BoxSizer(wx.HORIZONTAL) sizer_2.Add(option3_sizer, 1, wx.EXPAND, 0) label_4 = wx.StaticText(self, wx.ID_ANY, _("Option 3")) option3_sizer.Add(label_4, 0, 0, 0) self.option3 = wx.TextCtrl(self, wx.ID_ANY, "") + self.option3.SetMaxLength(25) option3_sizer.Add(self.option3, 0, 0, 0) option4_sizer = wx.BoxSizer(wx.HORIZONTAL) sizer_2.Add(option4_sizer, 1, wx.EXPAND, 0) label_5 = wx.StaticText(self, wx.ID_ANY, _("Option 4")) option4_sizer.Add(label_5, 0, 0, 0) self.option4 = wx.TextCtrl(self, wx.ID_ANY, "") + self.option4.SetMaxLength(25) option4_sizer.Add(self.option4, 0, 0, 0) btn_sizer = wx.StdDialogButtonSizer() sizer_1.Add(btn_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 4) From b329ac8e3e6a59076da3fa690b416b82fae85ec0 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 11 Nov 2021 12:33:38 -0600 Subject: [PATCH 32/33] Improved a little bit text dialogs's size for sending tweets, replies and dm's --- src/wxUI/dialogs/twitterDialogs/tweetDialogs.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py index 8db397ec..b917a25b 100644 --- a/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py +++ b/src/wxUI/dialogs/twitterDialogs/tweetDialogs.py @@ -18,13 +18,12 @@ class tweet(wx.Dialog): def create_controls(self, message: str, caption: str, max_length: int, thread_mode: bool) -> None: panel = wx.Panel(self) mainBox = wx.BoxSizer(wx.VERTICAL) - text_sizer = wx.BoxSizer(wx.VERTICAL) + text_sizer = wx.BoxSizer(wx.HORIZONTAL) mainBox.Add(text_sizer, 1, wx.EXPAND, 0) label_1 = wx.StaticText(panel, wx.ID_ANY, caption) text_sizer.Add(label_1, 0, 0, 0) - self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) + self.text = wx.TextCtrl(panel, wx.ID_ANY, "", size=(444, -1), style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) - self.text.SetMinSize((1000, 158)) text_sizer.Add(self.text, 1, wx.EXPAND, 0) list_sizer = wx.BoxSizer(wx.HORIZONTAL) mainBox.Add(list_sizer, 1, wx.EXPAND, 0) @@ -155,13 +154,12 @@ class reply(tweet): def create_controls(self, message: str, caption: str, max_length: int, thread_mode: bool) -> None: panel = wx.Panel(self) mainBox = wx.BoxSizer(wx.VERTICAL) - text_sizer = wx.BoxSizer(wx.VERTICAL) + text_sizer = wx.BoxSizer(wx.HORIZONTAL) mainBox.Add(text_sizer, 1, wx.EXPAND, 0) label_1 = wx.StaticText(panel, wx.ID_ANY, caption) text_sizer.Add(label_1, 0, 0, 0) - self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) + self.text = wx.TextCtrl(panel, wx.ID_ANY, "", size=(500, 200), style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) - self.text.SetMinSize((1000, 158)) text_sizer.Add(self.text, 1, wx.EXPAND, 0) list_sizer = wx.BoxSizer(wx.HORIZONTAL) mainBox.Add(list_sizer, 1, wx.EXPAND, 0) @@ -232,20 +230,19 @@ class dm(tweet): def create_controls(self, message: str, caption: str, max_length: int, thread_mode: bool) -> None: panel = wx.Panel(self) mainBox = wx.BoxSizer(wx.VERTICAL) - label_recipient = wx.StaticText(panel, -1, _(u"&Recipient")) + label_recipient = wx.StaticText(panel, -1, _("&Recipient")) self.cb = wx.ComboBox(panel, -1, choices=self.users, value=self.users[0], size=wx.DefaultSize) self.autocomplete_users = wx.Button(panel, -1, _(u"Auto&complete users")) recipient_sizer = wx.BoxSizer(wx.HORIZONTAL) recipient_sizer.Add(label_recipient, 0, 0, 0) recipient_sizer.Add(self.cb, 1, wx.EXPAND, 0) mainBox.Add(recipient_sizer, 0, wx.EXPAND, 0) - text_sizer = wx.BoxSizer(wx.VERTICAL) + text_sizer = wx.BoxSizer(wx.HORIZONTAL) mainBox.Add(text_sizer, 1, wx.EXPAND, 0) label_1 = wx.StaticText(panel, wx.ID_ANY, caption) text_sizer.Add(label_1, 0, 0, 0) - self.text = wx.TextCtrl(panel, wx.ID_ANY, "", style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) + self.text = wx.TextCtrl(panel, wx.ID_ANY, "", size=(500, 200), style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_CHAR_HOOK, self.handle_keys, self.text) - self.text.SetMinSize((1000, 158)) self.text.SetFocus() text_sizer.Add(self.text, 1, wx.EXPAND, 0) list_sizer = wx.BoxSizer(wx.HORIZONTAL) From d6571a95cb35563a4b88fcc57a8f4aa3b58ab071 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 11 Nov 2021 15:13:58 -0600 Subject: [PATCH 33/33] Quote tweets properly according to Twitter's API V2 docs --- src/controller/buffers/twitter/base.py | 4 ++-- src/sessions/twitter/session.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/controller/buffers/twitter/base.py b/src/controller/buffers/twitter/base.py index 1d81a604..3ad5b77c 100644 --- a/src/controller/buffers/twitter/base.py +++ b/src/controller/buffers/twitter/base.py @@ -459,11 +459,11 @@ class BaseBuffer(base.Buffer): retweet = messages.tweet(session=self.session, title=_("Quote"), caption=_("Add your comment to the tweet"), max=256, thread_mode=False) if retweet.message.ShowModal() == widgetUtils.OK: text = retweet.message.text.GetValue() - text = text+" https://twitter.com/{0}/status/{1}".format(self.session.get_user(tweet.user).screen_name, id) tweet_data = dict(text=text, attachments=retweet.attachments, poll_period=retweet.poll_period, poll_options=retweet.poll_options) + tweet_data.update(quote_tweet_id=id) call_threaded(self.session.send_tweet, *[tweet_data]) if hasattr(retweet.message, "destroy"): - retweet.message.destroy() + retweet.message.Destroy() def _direct_retweet(self, id): item = self.session.api_call(call_name="retweet", _sound="retweet_send.ogg", id=id) diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index 1b985c9c..a311ab42 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -620,8 +620,9 @@ class Session(base.baseSession): """ Convenience function to send a thread. """ in_reply_to_status_id = None for obj in tweets: + # When quoting a tweet, the tweet_data dict might contain a parameter called quote_tweet_id. Let's add it, or None, so quotes will be posted successfully. if len(obj["attachments"]) == 0: - item = self.api_call_v2(call_name="create_tweet", text=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"]) + item = self.api_call_v2(call_name="create_tweet", text=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"], quote_tweet_id=obj.get("quote_tweet_id")) in_reply_to_status_id = item.data["id"] else: media_ids = [] @@ -630,7 +631,7 @@ class Session(base.baseSession): if i["type"] == "photo": self.api_call(call_name="create_media_metadata", media_id=img.media_id, alt_text=i["description"]) media_ids.append(img.media_id) - item = self.api_call_v2(call_name="create_tweet", status=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, media_ids=media_ids, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"]) + item = self.api_call_v2(call_name="create_tweet", status=obj["text"], _sound="tweet_send.ogg", in_reply_to_tweet_id=in_reply_to_status_id, media_ids=media_ids, poll_duration_minutes=obj["poll_period"], poll_options=obj["poll_options"], quote_tweet_id=obj.get("quote_tweet_id")) in_reply_to_status_id = item.data["id"] def reply(self, text="", in_reply_to_status_id=None, attachments=[], *args, **kwargs):