Compare commits

...

42 Commits

Author SHA1 Message Date
f9864a887d Fixed a small traceback that was happening when translating a direct message 2021-08-26 09:16:02 -05:00
3d8519313e Switched Geocoding library to OpenStreetMap's Nominatim API. Closes #390 2021-08-26 08:56:51 -05:00
f4ecf10885 Allow to copy tweet URLS from tweet displayer dialog 2021-08-25 16:30:37 -05:00
c67b415934 Updated changelog 2021-08-25 12:49:53 -05:00
10511d3022 Improvements to reading tweets with many mentions on them 2021-08-25 11:13:12 -05:00
9ea36a26d2 Updated changelog with windows 11 keymap 2021-07-31 22:33:32 -05:00
97286496fc fixed changelog in diverged commits 2021-07-31 22:28:46 -05:00
José Manuel Delicado
6436af76f5 Merge pull request #393 from jpavonabian/windows11
Added a fix.
2021-07-28 07:50:38 +02:00
Jesus
576b5064c0 Added a fix. 2021-07-28 07:23:57 +02:00
José Manuel Delicado
342265b3c0 Merge pull request #392 from jpavonabian/windows11
Fixed shortkuts
2021-07-26 13:38:32 +02:00
Jesus
54938ecb6c Fixed shortkuts 2021-07-26 12:47:51 +02:00
José Manuel Delicado
7aff8252d2 Merge pull request #391 from jpavonabian/windows11
Keypad for windows 11
2021-07-24 12:46:43 +02:00
Jesus
9c680130f7 Keypad para windows 11 2021-07-24 06:24:08 +02:00
24d1ad093d Streaming API: Ignore retweets if original tweet is present in a buffer 2021-07-16 10:22:51 -05:00
b2b9cd810f Fixed an issue when indefined keystrokes 2021-07-13 17:53:01 -05:00
582be54dea Added add_alias action to keymaps as undefined 2021-07-13 17:25:48 -05:00
ff0fbeafa3 Modified keystrokeEditor to allow undefined keystrokes 2021-07-13 17:22:20 -05:00
e314cf0599 Allow search users by names in autocomplete users 2021-07-10 05:24:56 -05:00
José Manuel Delicado Alcolea
e7b72112cf Merge branch 'next-gen' of github.com:manuelcortez/TWBlue into next-gen 2021-07-07 08:42:14 +02:00
José Manuel Delicado Alcolea
70c095febe setup.py: patch cx_freeze to include our Microsoft Visual C++ runtime files 2021-07-07 08:41:08 +02:00
6119b029f8 Merge pull request #388 from zstanecic/next-gen
Updated russian interface
2021-07-06 17:13:59 -05:00
b74cd9a73d Ignore undefined actions in keymaps 2021-07-06 17:01:42 -05:00
8ff6809f08 Updated changelog 2021-07-06 16:22:52 -05:00
39af9d8623 Merge pull request #389 from manuelcortez/user_alias
User aliases within TWBlue
2021-07-06 15:58:54 -05:00
3688d7548c Check alias before returning any user object 2021-07-06 13:59:34 -05:00
07f9afb14e Added option to user menu in the menu bar 2021-07-06 13:58:34 -05:00
de12dadac2 Added GUI for user alias addition 2021-07-06 13:58:13 -05:00
877c909482 Added user-aliases section on session configs 2021-07-06 13:56:56 -05:00
José Manuel Delicado Alcolea
1206aba83b Added a few more dependencies to requirements.txt 2021-07-06 20:42:03 +02:00
zstanecic
fcd631b2de Merge remote-tracking branch 'upstream/next-gen' into next-gen 2021-07-06 19:16:47 +02:00
Jose Manuel Delicado
86130954d7 Updated Windows dependencies. Latest versions of NSIS and VLC, among others 2021-07-06 12:39:47 +02:00
Jose Manuel Delicado
23a56c637d Add some indirect dependencies to requirements.txt in order to easily keep them updated 2021-07-05 23:03:40 +02:00
zstanecic
d5ac0db67b updated the russian localization. 2021-07-05 16:04:19 +02:00
bb4869b7be Pushed a new snapshot 2021-07-04 17:05:06 -05:00
44b6e82183 Fix reply dialog issue 2021-07-04 11:50:03 -05:00
Jose Manuel Delicado
5268f166f8 Use Python 3.8.10 for automated builds 2021-07-04 18:43:33 +02:00
Jose Manuel Delicado
37ad6b5fbf paths.py: replace socializer by TW Blue in data_path function definition 2021-07-04 18:32:17 +02:00
Jose Manuel Delicado
bcc72c932d Updated Windows dependencies. Python 3.8.10 2021-07-04 18:26:49 +02:00
b9a9bd03c2 Pushed a new snapshot 2021-07-04 09:47:33 -05:00
e6543bcf77 Allow streaming API support to be disabled from global settings dialog 2021-07-04 09:44:48 -05:00
03b61946f8 Fixed user searches 2021-07-04 09:43:53 -05:00
8fe2f4c64d Exclude muted users from Streaming API 2021-07-04 09:15:04 -05:00
33 changed files with 318 additions and 88 deletions

View File

@@ -17,7 +17,7 @@ snapshot32:
- Set-Variable -Name "time" -Value (date -Format "%H:%m") - Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time} - echo ${time}
- echo "started by ${GITLAB_USER_NAME}" - echo "started by ${GITLAB_USER_NAME}"
- choco install python --version 3.8.7 -y -ForceX86 - choco install python --version 3.8.10 -y -ForceX86
- '&$env:PYTHON -V' - '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip' - '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt' - '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
@@ -54,7 +54,7 @@ snapshot64:
- Set-Variable -Name "time" -Value (date -Format "%H:%m") - Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time} - echo ${time}
- echo "started by ${GITLAB_USER_NAME}" - echo "started by ${GITLAB_USER_NAME}"
- choco install python --version 3.8.7 -y - choco install python --version 3.8.10 -y
- '&$env:PYTHON -V' - '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip' - '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt' - '&$env:PYTHON -m pip install --upgrade -r requirements.txt'

View File

@@ -2,6 +2,14 @@
## changes in this version ## changes in this version
* When reading a tweet, if the tweet contains more than 2 consecutive mentions, TWBlue will announce how many more users the tweet includes, as opposed to read every user in the conversation. You still can display the tweet to read all users.
* In the tweet displayer, It is possible to copy a link to the current tweet or person by pressing a button called "copy link to clipboard".
* Added a keymap capable to work under Windows 11. ([#391](https://github.com/manuelcortez/TWBlue/pull/391))
* Added user aliases to TWBlue. This feature allows you to rename user's display names on Twitter, so the next time you'll read an user it will be announced as you configured. For adding an alias to an user, select the "add alias" option in the user menu, located in the menu bar. This feature works only if you have set display screen names unchecked. Users are displayed with their display name in people buffers only. This action is supported in all keymaps, although it is undefined by default. ([#389](https://github.com/manuelcortez/TWBlue/pull/389))
* There are some changes to the autocomplete users feature:
* Now users can search for twitter screen names or display names in the database.
* It is possible to undefine keystrokes in the current keymap in TWBlue. This allows you, for example, to redefine keystrokes completely.
* We have changed our Geocoding service to the Nominatim API from OpenStreetMap. Addresses present in tweets are going to be determined by this service, as the Google Maps API now requires an API key. ([#390](https://github.com/manuelcortez/TWBlue/issues/390))
* Added a limited version of the Twitter's Streaming API: The Streaming API will work only for tweets, and will receive tweets only by people you follow. Protected users are not possible to be streamed. It is possible that during high tweet traffic, the Stream might get disconnected at times, but TWBlue should be capable of detecting this problem and reconnecting the stream again. ([#385](https://github.com/manuelcortez/TWBlue/pull/385)) * Added a limited version of the Twitter's Streaming API: The Streaming API will work only for tweets, and will receive tweets only by people you follow. Protected users are not possible to be streamed. It is possible that during high tweet traffic, the Stream might get disconnected at times, but TWBlue should be capable of detecting this problem and reconnecting the stream again. ([#385](https://github.com/manuelcortez/TWBlue/pull/385))
* Fixed an issue that made TWBlue to not show a dialog when attempting to show a profile for a suspended user. ([#387](https://github.com/manuelcortez/TWBlue/issues/387)) * Fixed an issue that made TWBlue to not show a dialog when attempting to show a profile for a suspended user. ([#387](https://github.com/manuelcortez/TWBlue/issues/387))
* Added support for Twitter audio and videos: Tweets which contains audio or videos will be detected as audio items, and you can playback those with the regular command to play audios. ([#384,](https://github.com/manuelcortez/TWBlue/pull/384)) * Added support for Twitter audio and videos: Tweets which contains audio or videos will be detected as audio items, and you can playback those with the regular command to play audios. ([#384,](https://github.com/manuelcortez/TWBlue/pull/384))

View File

@@ -9,7 +9,7 @@ oauthlib
requests-oauthlib requests-oauthlib
requests-toolbelt requests-toolbelt
pypubsub pypubsub
pygeocoder geopy
arrow arrow
python-dateutil python-dateutil
futures futures
@@ -25,6 +25,7 @@ urllib3
youtube-dl youtube-dl
python-vlc python-vlc
pypiwin32 pypiwin32
pywin32
certifi certifi
backports.functools_lru_cache backports.functools_lru_cache
cx_freeze cx_freeze
@@ -32,6 +33,20 @@ tweepy
twitter-text-parser twitter-text-parser
pyenchant pyenchant
sqlitedict sqlitedict
cx-Logging
h11
h2
hpack
hstspreload
httpcore
httpx
hyperframe
rfc3986
sniffio
attrs
importlib-metadata
numpy
pillow
git+https://github.com/accessibleapps/libloader git+https://github.com/accessibleapps/libloader
git+https://github.com/accessibleapps/platform_utils git+https://github.com/accessibleapps/platform_utils
git+https://github.com/accessibleapps/accessible_output2 git+https://github.com/accessibleapps/accessible_output2

View File

@@ -15,10 +15,10 @@ SetCompressor /solid lzma
SetDatablockOptimize on SetDatablockOptimize on
VIAddVersionKey ProductName "TWBlue Snapshot version" VIAddVersionKey ProductName "TWBlue Snapshot version"
VIAddVersionKey LegalCopyright "Copyright 2014-2021 Manuel Cortéz." VIAddVersionKey LegalCopyright "Copyright 2014-2021 Manuel Cortéz."
VIAddVersionKey ProductVersion "7" VIAddVersionKey ProductVersion "9"
VIAddVersionKey FileVersion "7" VIAddVersionKey FileVersion "9"
VIProductVersion "7.0.0.0" VIProductVersion "9.0.0.0"
VIFileVersion "7.0.0.0" VIFileVersion "9.0.0.0"
!insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_WELCOME
!define MUI_LICENSEPAGE_RADIOBUTTONS !define MUI_LICENSEPAGE_RADIOBUTTONS
!insertmacro MUI_PAGE_LICENSE "license.txt" !insertmacro MUI_PAGE_LICENSE "license.txt"
@@ -72,8 +72,8 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "D
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "7" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "9"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "http://twblue.es" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "https://twblue.es"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1

View File

@@ -49,3 +49,5 @@ braille_reporting = boolean(default=True)
speech_reporting = boolean(default=True) speech_reporting = boolean(default=True)
[filters] [filters]
[user-aliases]

View File

@@ -9,7 +9,7 @@ if snapshot == False:
update_url = 'https://twblue.es/updates/stable.php' update_url = 'https://twblue.es/updates/stable.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json' mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json'
else: else:
version = "7" version = "9"
update_url = 'https://twblue.es/updates/snapshot.php' update_url = 'https://twblue.es/updates/snapshot.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/snapshots.json' mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/snapshots.json'
authors = ["Manuel Cortéz", "José Manuel Delicado"] authors = ["Manuel Cortéz", "José Manuel Delicado"]

View File

@@ -237,7 +237,7 @@ class BaseBuffer(base.Buffer):
items_db = self.session.db[self.name] items_db = self.session.db[self.name]
self.session.add_users_from_results(items) self.session.add_users_from_results(items)
for i in items: for i in items:
if utils.is_allowed(i, self.session.settings, self.name) == True and utils.find_item(i.id, self.session.db[self.name]) == None: if utils.is_allowed(i, self.session.settings, self.name) == True and utils.find_item(i, self.session.db[self.name]) == None:
i = reduce.reduce_tweet(i) i = reduce.reduce_tweet(i)
i = self.session.check_quoted_status(i) i = self.session.check_quoted_status(i)
i = self.session.check_long_tweet(i) i = self.session.check_long_tweet(i)
@@ -644,8 +644,12 @@ class BaseBuffer(base.Buffer):
original_tweet.text = utils.find_urls_in_text(original_tweet.text, original_tweet.entities) original_tweet.text = utils.find_urls_in_text(original_tweet.text, original_tweet.entities)
return compose.compose_quoted_tweet(quoted_tweet, original_tweet, self.session.db, self.session.settings["general"]["relative_times"]) return compose.compose_quoted_tweet(quoted_tweet, original_tweet, self.session.db, self.session.settings["general"]["relative_times"])
def open_in_browser(self, *args, **kwargs): def get_item_url(self):
tweet = self.get_tweet() tweet = self.get_tweet()
output.speak(_(u"Opening item in web browser..."))
url = "https://twitter.com/{screen_name}/status/{tweet_id}".format(screen_name=self.session.get_user(tweet.user).screen_name, tweet_id=tweet.id) url = "https://twitter.com/{screen_name}/status/{tweet_id}".format(screen_name=self.session.get_user(tweet.user).screen_name, tweet_id=tweet.id)
return url
def open_in_browser(self, *args, **kwargs):
url = self.get_item_url()
output.speak(_(u"Opening item in web browser..."))
webbrowser.open(url) webbrowser.open(url)

View File

@@ -252,8 +252,7 @@ class PeopleBuffer(base.BaseBuffer):
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False: elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_(u"{0} new followers.").format(number_of_items)) output.speak(_(u"{0} new followers.").format(number_of_items))
def open_in_browser(self, *args, **kwargs): def get_item_url(self, *args, **kwargs):
tweet = self.get_tweet() tweet = self.get_tweet()
output.speak(_(u"Opening item in web browser..."))
url = "https://twitter.com/{screen_name}".format(screen_name=tweet.screen_name) url = "https://twitter.com/{screen_name}".format(screen_name=tweet.screen_name)
webbrowser.open(url) return url

View File

@@ -35,17 +35,16 @@ from mysc.repeating_timer import RepeatingTimer
from mysc import restart from mysc import restart
import config import config
import widgetUtils import widgetUtils
import pygeocoder
from pygeolib import GeocoderError
import logging import logging
import webbrowser import webbrowser
from geopy.geocoders import Nominatim
from mysc import localization from mysc import localization
import os import os
import languageHandler import languageHandler
log = logging.getLogger("mainController") log = logging.getLogger("mainController")
geocoder = pygeocoder.Geocoder() geocoder = Nominatim(user_agent="TWBlue")
class Controller(object): class Controller(object):
@@ -185,6 +184,7 @@ class Controller(object):
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.report_error, self.view.reportError) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.report_error, self.view.reportError)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_documentation, self.view.doc) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_documentation, self.view.doc)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_changelog, self.view.changelog) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_changelog, self.view.changelog)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_alias, self.view.addAlias)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_to_list, self.view.addToList) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_to_list, self.view.addToList)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_from_list, self.view.removeFromList) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_from_list, self.view.removeFromList)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.update_buffer, self.view.update_buffer) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.update_buffer, self.view.update_buffer)
@@ -440,7 +440,7 @@ class Controller(object):
log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,)) log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,))
return return
elif dlg.get("users") == True: elif dlg.get("users") == True:
search = buffers.twitter.SearchBuffer(self.view.nb, "search_users", "%s-searchUser" % (term,), buffer.session, buffer.session.db["user_name"], bufferType=None, sound="search_updated.ogg", q=term) search = buffers.twitter.SearchPeopleBuffer(self.view.nb, "search_users", "%s-searchUser" % (term,), buffer.session, buffer.session.db["user_name"], bufferType=None, sound="search_updated.ogg", q=term)
search.start_stream(mandatory=True) search.start_stream(mandatory=True)
pos=self.view.search("searches", buffer.session.db["user_name"]) pos=self.view.search("searches", buffer.session.db["user_name"])
self.insert_buffer(search, pos) self.insert_buffer(search, pos)
@@ -754,6 +754,26 @@ class Controller(object):
users = utils.get_all_users(tweet, buff.session) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "report") u = userActionsController.userActionsController(buff, users, "report")
def add_alias(self, *args, **kwargs):
buff = self.get_best_buffer()
if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet()
if buff.type == "people":
users = [tweet.screen_name]
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.addAliasDialog(_("Add an user alias"), users)
if dlg.get_response() == widgetUtils.OK:
user, alias = dlg.get_user()
if user == "" or alias == "":
return
user_id = buff.session.get_user_by_screen_name(user)
buff.session.settings["user-aliases"][str(user_id)] = alias
buff.session.settings.write()
output.speak(_("Alias has been set correctly for {}.").format(user))
def post_tweet(self, event=None): def post_tweet(self, event=None):
buffer = self.get_best_buffer() buffer = self.get_best_buffer()
buffer.post_status() buffer.post_status()
@@ -810,7 +830,7 @@ class Controller(object):
return return
elif buffer.type == "baseBuffer" or buffer.type == "favourites_timeline" or buffer.type == "list" or buffer.type == "search": elif buffer.type == "baseBuffer" or buffer.type == "favourites_timeline" or buffer.type == "list" or buffer.type == "search":
tweet, tweetsList = buffer.get_full_tweet() tweet, tweetsList = buffer.get_full_tweet()
msg = messages.viewTweet(tweet, tweetsList, utc_offset=buffer.session.db["utc_offset"]) msg = messages.viewTweet(tweet, tweetsList, utc_offset=buffer.session.db["utc_offset"], item_url=buffer.get_item_url())
elif buffer.type == "dm": elif buffer.type == "dm":
non_tweet = buffer.get_formatted_message() non_tweet = buffer.get_formatted_message()
item = buffer.get_right_tweet() item = buffer.get_right_tweet()
@@ -818,8 +838,11 @@ class Controller(object):
date = original_date.shift(seconds=buffer.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage()) date = original_date.shift(seconds=buffer.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
msg = messages.viewTweet(non_tweet, [], False, date=date) msg = messages.viewTweet(non_tweet, [], False, date=date)
else: else:
item_url = ""
if hasattr(buffer, "get_item_url"):
item_url = buffer.get_item_url()
non_tweet = buffer.get_formatted_message() non_tweet = buffer.get_formatted_message()
msg = messages.viewTweet(non_tweet, [], False) msg = messages.viewTweet(non_tweet, [], False, item_url=item_url)
def open_in_browser(self, *args, **kwargs): def open_in_browser(self, *args, **kwargs):
buffer = self.get_current_buffer() buffer = self.get_current_buffer()
@@ -977,19 +1000,17 @@ class Controller(object):
if tweet.coordinates != None: if tweet.coordinates != None:
x = tweet.coordinates["coordinates"][0] x = tweet.coordinates["coordinates"][0]
y = tweet.coordinates["coordinates"][1] y = tweet.coordinates["coordinates"][1]
address = geocoder.reverse_geocode(y, x, language = languageHandler.curLang) address = geocoder.reverse("{}, {}".format(y, x), language = languageHandler.curLang)
if event == None: output.speak(address[0].__str__()) if event == None: output.speak(address.address)
else: self.view.show_address(address[0].__str__()) else: self.view.show_address(address.address)
else: else:
output.speak(_(u"There are no coordinates in this tweet")) output.speak(_(u"There are no coordinates in this tweet"))
except GeocoderError:
output.speak(_(u"There are no results for the coordinates in this tweet"))
except ValueError: except ValueError:
output.speak(_(u"Error decoding coordinates. Try again later.")) output.speak(_(u"Error decoding coordinates. Try again later."))
except KeyError: # except KeyError:
pass # pass
except AttributeError: except AttributeError:
pass output.speak(_("Unable to find address in OpenStreetMap."))
def view_reverse_geocode(self, event=None): def view_reverse_geocode(self, event=None):
try: try:
@@ -1244,14 +1265,17 @@ class Controller(object):
keymap = {} keymap = {}
for i in config.keymap["keymap"]: for i in config.keymap["keymap"]:
if hasattr(self, i): if hasattr(self, i):
if config.keymap["keymap"][i] != "":
keymap[config.keymap["keymap"][i]] = getattr(self, i) keymap[config.keymap["keymap"][i]] = getattr(self, i)
return keymap return keymap
def register_invisible_keyboard_shorcuts(self, keymap): def register_invisible_keyboard_shorcuts(self, keymap):
if config.changed_keymap: if config.changed_keymap:
commonMessageDialogs.changed_keymap() commonMessageDialogs.changed_keymap()
# Make sure we pass a keymap without undefined keystrokes.
new_keymap = {key: keymap[key] for key in keymap.keys() if keymap[key] != ""}
self.keyboard_handler = WXKeyboardHandler(self.view) self.keyboard_handler = WXKeyboardHandler(self.view)
self.keyboard_handler.register_keys(keymap) self.keyboard_handler.register_keys(new_keymap)
def unregister_invisible_keyboard_shorcuts(self, keymap): def unregister_invisible_keyboard_shorcuts(self, keymap):
try: try:

View File

@@ -102,7 +102,7 @@ class basicTweet(object):
else: else:
self.message.disable_button("shortenButton") self.message.disable_button("shortenButton")
self.message.disable_button("unshortenButton") self.message.disable_button("unshortenButton")
if self.message.get("long_tweet") == False: if self.message.get("long_tweet") == False and hasattr(self, "max"):
text = self.message.get_text() text = self.message.get_text()
results = parse_tweet(text) results = parse_tweet(text)
self.message.set_title(_(u"%s - %s of %d characters") % (self.title, results.weightedLength, self.max)) self.message.set_title(_(u"%s - %s of %d characters") % (self.title, results.weightedLength, self.max))
@@ -205,7 +205,7 @@ class dm(basicTweet):
c.show_menu("dm") c.show_menu("dm")
class viewTweet(basicTweet): class viewTweet(basicTweet):
def __init__(self, tweet, tweetList, is_tweet=True, utc_offset=0, date=""): 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. """ 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.
param tweet: A dictionary that represents a full tweet or a string for non-tweets. param tweet: A dictionary that represents a full tweet or a string for non-tweets.
param tweetList: If is_tweet is set to True, this could be a list of quoted tweets. param tweetList: If is_tweet is set to True, this could be a list of quoted tweets.
@@ -273,6 +273,10 @@ class viewTweet(basicTweet):
text = tweet text = tweet
self.message = message.viewNonTweet(text, date) self.message = message.viewNonTweet(text, date)
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) 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) widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
if self.contain_urls() == True: if self.contain_urls() == True:
self.message.enable_button("unshortenButton") self.message.enable_button("unshortenButton")
@@ -290,3 +294,8 @@ class viewTweet(basicTweet):
if "https://twitter.com/" in i: if "https://twitter.com/" in i:
text = text.replace(i, "\n") text = text.replace(i, "\n")
return text return text
def share(self, *args, **kwargs):
if hasattr(self, "item_url"):
output.copy(self.item_url)
output.speak(_("Link copied to clipboard."))

View File

@@ -21,7 +21,7 @@ class storage(object):
return self.cursor.fetchall() return self.cursor.fetchall()
def get_users(self, term): def get_users(self, term):
self.cursor.execute("""SELECT * FROM users WHERE user LIKE ?""", ('{}%'.format(term),)) self.cursor.execute("""SELECT * FROM users WHERE UPPER(user) LIKE :term OR UPPER(name) LIKE :term""", {"term": "%{}%".format(term.upper())})
return self.cursor.fetchall() return self.cursor.fetchall()
def set_user(self, screen_name, user_name, from_a_buffer): def set_user(self, screen_name, user_name, from_a_buffer):

View File

@@ -35,3 +35,4 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -54,3 +54,4 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -55,3 +55,4 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+alt+shift+u") update_buffer = string(default="control+alt+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -0,0 +1,58 @@
[info]
name = string(default="Windows 11")
desc = string(default="A keymap with remapped modifiers for Windows 11 compatibility.")
author = string(default="Bill Jesús <galorasd@gmail.com>")
[keymap]
up = string(default="control+alt+win+up")
down = string(default="control+alt+win+down")
left = string(default="control+alt+win+left")
right = string(default="control+alt+win+right")
next_account = string(default="control+alt+win+shift+right")
previous_account = string(default="control+alt+win+shift+left")
open_conversation = string(default="control+alt+win+c")
show_hide = string(default="control+win+w")
post_tweet = string(default="alt+win+n")
post_reply = string(default="control+win+r")
post_retweet = string(default="alt+win+shift+r")
send_dm = string(default="alt+win+shift+d")
toggle_like = string(default="control+alt+win+f")
follow = string(default="alt+win+shift+s")
user_details = string(default="alt+win+shift+n")
view_item = string(default="alt+win+v")
exit = string(default="alt+win+f4")
open_timeline = string(default="alt+win+i")
remove_buffer = string(default="alt+win+shift+i")
url = string(default="alt+win+return")
audio = string(default="alt+shift+win+return")
volume_up = string(default="control+alt+win+shift+up")
go_home = string(default="control+alt+win+home")
volume_down = string(default="control+alt+win+shift+down")
go_end = string(default="control+alt+win+end")
go_page_up = string(default="control+win+pageup")
go_page_down = string(default="control+win+pagedown")
update_profile = string(default="alt+win+p")
delete = string(default="alt+win+delete")
clear_buffer = string(default="alt+win+shift+delete")
repeat_item = string(default="control+alt+win+space")
copy_to_clipboard = string(default="alt+win+shift+c")
add_to_list = string(default="alt+win+a")
remove_from_list = string(default="alt+win+shift+a")
toggle_buffer_mute = string(default="alt+win+shift+m")
toggle_session_mute = string(default="control+alt+win+m")
toggle_autoread = string(default="alt+win+e")
search = string(default="alt+win+-")
edit_keystrokes = string(default="alt+win+k")
view_user_lists = string(default="alt+win+l")
get_more_items = string(default="alt+win+pageup")
reverse_geocode = string(default="control+win+g")
view_reverse_geocode = string(default="alt+win+shift+g")
get_trending_topics = string(default="control+win+t")
check_for_updates = string(default="alt+win+u")
list_manager = string(default="alt+win+shift+l")
configuration = string(default="control+win+alt+o")
accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+alt+shift+u")
ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -56,3 +56,4 @@ configuration = string(default="control+win+o")
accountConfiguration = string(default="control+win+shift+o") accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -57,3 +57,4 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
actions = { actions = {
"up": _(u"Go up in the current buffer"), "up": _(u"Go up in the current buffer"),
"down": _(u"Go down in the current buffer"), "down": _(u"Go down in the current buffer"),
@@ -57,4 +56,6 @@ actions = {
"audio": _(u"Try to play an audio file"), "audio": _(u"Try to play an audio file"),
"update_buffer": _(u"Updates the buffer and retrieves possible lost items there."), "update_buffer": _(u"Updates the buffer and retrieves possible lost items there."),
"ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."), "ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."),
"add_alias": _("Adds an alias to an user"),
} }

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import object
import widgetUtils import widgetUtils
import config import config
from . import wx_ui from . import wx_ui
@@ -18,6 +15,7 @@ class KeystrokeEditor(object):
self.hold_map = self.map.copy() self.hold_map = self.map.copy()
self.dialog.put_keystrokes(constants.actions, self.map) self.dialog.put_keystrokes(constants.actions, self.map)
widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.edit_keystroke) widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.edit_keystroke)
widgetUtils.connect_event(self.dialog.undefine, widgetUtils.BUTTON_PRESSED, self.undefine_keystroke)
widgetUtils.connect_event(self.dialog.execute, widgetUtils.BUTTON_PRESSED, self.execute_action) widgetUtils.connect_event(self.dialog.execute, widgetUtils.BUTTON_PRESSED, self.execute_action)
self.dialog.get_response() self.dialog.get_response()
@@ -33,6 +31,17 @@ class KeystrokeEditor(object):
self.map[action] = new_keystroke self.map[action] = new_keystroke
self.dialog.put_keystrokes(constants.actions, self.map) self.dialog.put_keystrokes(constants.actions, self.map)
def undefine_keystroke(self, *args, **kwargs):
action = self.dialog.actions[self.dialog.get_action()]
keystroke = self.map.get(action)
if keystroke == None:
return
answer = self.dialog.undefine_keystroke_confirmation()
if answer == widgetUtils.YES:
self.map[action] = ""
self.changed = True
self.dialog.put_keystrokes(constants.actions, self.map)
def set_keystroke(self, keystroke, dialog): def set_keystroke(self, keystroke, dialog):
for i in keystroke.split("+"): for i in keystroke.split("+"):
if hasattr(dialog, i): if hasattr(dialog, i):

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import wx import wx
from multiplatform_widgets import widgets from multiplatform_widgets import widgets
from wxUI.dialogs import baseDialog from wxUI.dialogs import baseDialog
@@ -18,6 +17,7 @@ class keystrokeEditorDialog(baseDialog.BaseWXDialog):
firstSizer.Add(self.keys.list, 0, wx.ALL, 5) firstSizer.Add(self.keys.list, 0, wx.ALL, 5)
self.edit = wx.Button(panel, -1, _(u"Edit")) self.edit = wx.Button(panel, -1, _(u"Edit"))
self.edit.SetDefault() self.edit.SetDefault()
self.undefine = wx.Button(panel, -1, _("Undefine keystroke"))
self.execute = wx.Button(panel, -1, _(u"Execute action")) self.execute = wx.Button(panel, -1, _(u"Execute action"))
close = wx.Button(panel, wx.ID_CANCEL, _(u"Close")) close = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
secondSizer = wx.BoxSizer(wx.HORIZONTAL) secondSizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -37,13 +37,18 @@ class keystrokeEditorDialog(baseDialog.BaseWXDialog):
continue continue
action = actions[i] action = actions[i]
self.actions.append(i) self.actions.append(i)
keystroke = keystrokes[i] keystroke = keystrokes.get(i)
if keystroke == "":
keystroke = _("Undefined")
self.keys.insert_item(False, *[action, keystroke]) self.keys.insert_item(False, *[action, keystroke])
self.keys.select_item(selection) self.keys.select_item(selection)
def get_action(self): def get_action(self):
return self.keys.get_selected() return self.keys.get_selected()
def undefine_keystroke_confirmation(self):
return wx.MessageDialog(self, _("Are you sure you want to undefine this keystroke?"), _("Undefine keystroke"), wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION).ShowModal()
class editKeystrokeDialog(baseDialog.BaseWXDialog): class editKeystrokeDialog(baseDialog.BaseWXDialog):
def __init__(self): def __init__(self):
super(editKeystrokeDialog, self).__init__(parent=None, id=-1, title=_(u"Editing keystroke")) super(editKeystrokeDialog, self).__init__(parent=None, id=-1, title=_(u"Editing keystroke"))

Binary file not shown.

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TW Blue 0.85\n" "Project-Id-Version: TW Blue 0.85\n"
"POT-Creation-Date: 2019-03-17 13:34+Hora estndar romance\n" "POT-Creation-Date: 2019-03-17 13:34+Hora estndar romance\n"
"PO-Revision-Date: 2020-10-23 14:30+0300\n" "PO-Revision-Date: 2021-07-05 16:03+0200\n"
"Last-Translator: Artem Plaksin <admin@maniyax.ru>\n" "Last-Translator: Artem Plaksin <admin@maniyax.ru>\n"
"Language-Team: Alexander Jaszyn <a.jaszyn@ya.ru>\n" "Language-Team: Alexander Jaszyn <a.jaszyn@ya.ru>\n"
"Language: ru\n" "Language: ru\n"
@@ -14,7 +14,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 1.8.8\n" "X-Generator: Poedit 3.0\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
@@ -208,14 +208,12 @@ msgstr ""
"личных сообщений вместо этого." "личных сообщений вместо этого."
#: ../src\controller\buffers\twitterBuffers.py:983 #: ../src\controller\buffers\twitterBuffers.py:983
#, fuzzy
msgid "{0} new followers." msgid "{0} new followers."
msgstr "Новый читатель." msgstr "{0} новых читателей."
#: ../src\controller\buffers\twitterBuffers.py:1266 #: ../src\controller\buffers\twitterBuffers.py:1266
#, fuzzy
msgid "This action is not supported in the buffer, yet." msgid "This action is not supported in the buffer, yet."
msgstr "Это действие не поддерживается в данном буфере" msgstr "Это действие пока не поддерживается в буфере."
#: ../src\controller\mainController.py:273 #: ../src\controller\mainController.py:273
msgid "Ready" msgid "Ready"
@@ -314,9 +312,8 @@ msgid "Select the user"
msgstr "Выберите пользователя" msgstr "Выберите пользователя"
#: ../src\controller\mainController.py:809 ../src\controller\messages.py:236 #: ../src\controller\mainController.py:809 ../src\controller\messages.py:236
#, fuzzy
msgid "MMM D, YYYY. H:m" msgid "MMM D, YYYY. H:m"
msgstr "dddd, MMMM D, YYYY H:m:s" msgstr "MMM D, YYYY. H:m"
#: ../src\controller\mainController.py:934 #: ../src\controller\mainController.py:934
msgid "Conversation with {0}" msgid "Conversation with {0}"
@@ -1702,9 +1699,8 @@ msgid "Opens the global settings dialogue"
msgstr "Открыть основные настройки" msgstr "Открыть основные настройки"
#: ../src\keystrokeEditor\constants.py:54 #: ../src\keystrokeEditor\constants.py:54
#, fuzzy
msgid "Opens the list manager" msgid "Opens the list manager"
msgstr "Менеджер Списков" msgstr "Открывает менеджер списков"
#: ../src\keystrokeEditor\constants.py:55 #: ../src\keystrokeEditor\constants.py:55
msgid "Opens the account settings dialogue" msgid "Opens the account settings dialogue"

View File

@@ -39,7 +39,7 @@ def logs_path():
os.mkdir(path) os.mkdir(path)
return path return path
def data_path(app_name='socializer'): def data_path(app_name='TW Blue'):
if platform.system() == "Windows": if platform.system() == "Windows":
data_path = os.path.join(os.getenv("AppData"), app_name) data_path = os.path.join(os.getenv("AppData"), app_name)
else: else:

View File

@@ -45,9 +45,9 @@ def compose_tweet(tweet, db, relative_times, show_screen_names=False, session=No
else: else:
value = "text" value = "text"
if hasattr(tweet, "retweeted_status") and value != "message": if hasattr(tweet, "retweeted_status") and value != "message":
text = StripChars(getattr(tweet.retweeted_status, value)) text = utils.clean_mentions(StripChars(getattr(tweet.retweeted_status, value)))
else: else:
text = StripChars(getattr(tweet, value)) text = utils.clean_mentions(StripChars(getattr(tweet, value)))
if show_screen_names: if show_screen_names:
user = session.get_user(tweet.user).screen_name user = session.get_user(tweet.user).screen_name
else: else:
@@ -111,7 +111,7 @@ def compose_quoted_tweet(quoted_tweet, original_tweet, show_screen_names=False,
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
text = StripChars(getattr(quoted_tweet, value)) text = utils.clean_mentions(StripChars(getattr(quoted_tweet, value)))
if show_screen_names: if show_screen_names:
quoting_user = session.get_user(quoted_tweet.user).screen_name quoting_user = session.get_user(quoted_tweet.user).screen_name
else: else:
@@ -124,9 +124,9 @@ def compose_quoted_tweet(quoted_tweet, original_tweet, show_screen_names=False,
if hasattr(original_tweet, "message"): if hasattr(original_tweet, "message"):
original_text = original_tweet.message original_text = original_tweet.message
elif hasattr(original_tweet, "full_text"): elif hasattr(original_tweet, "full_text"):
original_text = StripChars(original_tweet.full_text) original_text = utils.clean_mentions(StripChars(original_tweet.full_text))
else: else:
original_text = StripChars(original_tweet.text) original_text = utils.clean_mentions(StripChars(original_tweet.text))
quoted_tweet.message = _(u"{0}. Quoted tweet from @{1}: {2}").format( text, original_user, original_text) quoted_tweet.message = _(u"{0}. Quoted tweet from @{1}: {2}").format( text, original_user, original_text)
quoted_tweet = tweets.clear_url(quoted_tweet) quoted_tweet = tweets.clear_url(quoted_tweet)
if hasattr(original_tweet, "entities") and original_tweet.entities.get("urls"): if hasattr(original_tweet, "entities") and original_tweet.entities.get("urls"):

View File

@@ -51,7 +51,7 @@ class Session(base.baseSession):
if i.id < last_id: if i.id < last_id:
log.error("Ignoring an older tweet... Last id: {0}, tweet id: {1}".format(last_id, i.id)) log.error("Ignoring an older tweet... Last id: {0}, tweet id: {1}".format(last_id, i.id))
continue continue
if utils.find_item(i.id, self.db[name]) == None and utils.is_allowed(i, self.settings, name) == True: if utils.find_item(i, self.db[name]) == None and utils.is_allowed(i, self.settings, name) == True:
if i == False: continue if i == False: continue
reduced_object = reduce.reduce_tweet(i) reduced_object = reduce.reduce_tweet(i)
reduced_object = self.check_quoted_status(reduced_object) reduced_object = self.check_quoted_status(reduced_object)
@@ -72,7 +72,7 @@ class Session(base.baseSession):
self.db[name] = [] self.db[name] = []
objects = self.db[name] objects = self.db[name]
for i in data: for i in data:
if utils.find_item(i.id, self.db[name]) == None: if utils.find_item(i, self.db[name]) == None:
if self.settings["general"]["reverse_timelines"] == False: objects.append(i) if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
else: objects.insert(0, i) else: objects.insert(0, i)
num = num+1 num = num+1
@@ -94,12 +94,12 @@ class Session(base.baseSession):
for i in data: for i in data:
# Twitter returns sender_id as str, which must be converted to int in order to match to our user_id object. # Twitter returns sender_id as str, which must be converted to int in order to match to our user_id object.
if int(i.message_create["sender_id"]) == self.db["user_id"]: if int(i.message_create["sender_id"]) == self.db["user_id"]:
if "sent_direct_messages" in self.db and utils.find_item(i.id, self.db["sent_direct_messages"]) == None: if "sent_direct_messages" in self.db and utils.find_item(i, self.db["sent_direct_messages"]) == None:
if self.settings["general"]["reverse_timelines"] == False: sent_objects.append(i) if self.settings["general"]["reverse_timelines"] == False: sent_objects.append(i)
else: sent_objects.insert(0, i) else: sent_objects.insert(0, i)
sent = sent+1 sent = sent+1
else: else:
if utils.find_item(i.id, self.db["direct_messages"]) == None: if utils.find_item(i, self.db["direct_messages"]) == None:
if self.settings["general"]["reverse_timelines"] == False: objects.append(i) if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
else: objects.insert(0, i) else: objects.insert(0, i)
incoming = incoming+1 incoming = incoming+1
@@ -430,9 +430,25 @@ class Session(base.baseSession):
users = self.db["users"] users = self.db["users"]
users[user.id_str] = user users[user.id_str] = user
self.db["users"] = users self.db["users"] = users
user.name = self.get_user_alias(user)
return user return user
else: else:
return self.db["users"][str(id)] user = self.db["users"][str(id)]
user.name = self.get_user_alias(user)
return user
def get_user_alias(self, user):
""" Retrieves an alias for the passed user model, if exists.
@ user Tweepy.models.user: An user object.
"""
aliases = self.settings.get("user-aliases")
if aliases == None:
log.error("Aliases are not defined for this config spec.")
return user.name
user_alias = aliases.get(user.id_str)
if user_alias != None:
return user_alias
return user.name
def get_user_by_screen_name(self, screen_name): def get_user_by_screen_name(self, screen_name):
""" Returns an user identifier associated with a screen_name. """ Returns an user identifier associated with a screen_name.
@@ -504,11 +520,16 @@ class Session(base.baseSession):
self.db["users"] = users self.db["users"] = users
def start_streaming(self): def start_streaming(self):
self.stream_listener = streaming.StreamListener(twitter_api=self.twitter, user=self.db["user_name"], user_id=self.db["user_id"]) if config.app["app-settings"]["no_streaming"]:
return
self.stream_listener = streaming.StreamListener(twitter_api=self.twitter, user=self.db["user_name"], user_id=self.db["user_id"], muted_users=self.db["muted_users"])
self.stream = streaming.Stream(auth = self.auth, listener=self.stream_listener, chunk_size=1025) self.stream = streaming.Stream(auth = self.auth, listener=self.stream_listener, chunk_size=1025)
self.stream_thread = call_threaded(self.stream.filter, follow=self.stream_listener.users, stall_warnings=True) self.stream_thread = call_threaded(self.stream.filter, follow=self.stream_listener.users, stall_warnings=True)
def stop_streaming(self): def stop_streaming(self):
if config.app["app-settings"]["no_streaming"]:
return
if hasattr(self, "stream"):
self.stream.running = False self.stream.running = False
log.debug("Stream stopped for accounr {}".format(self.db["user_name"])) log.debug("Stream stopped for accounr {}".format(self.db["user_name"]))
@@ -555,6 +576,10 @@ class Session(base.baseSession):
pub.sendMessage("newTweet", data=status, user=self.db["user_name"], _buffers=buffers_to_send) pub.sendMessage("newTweet", data=status, user=self.db["user_name"], _buffers=buffers_to_send)
def check_streams(self): def check_streams(self):
if config.app["app-settings"]["no_streaming"]:
return
if not hasattr(self, "stream"):
return
log.debug("Status of running stream for user {}: {}".format(self.db["user_name"], self.stream.running)) log.debug("Status of running stream for user {}: {}".format(self.db["user_name"], self.stream.running))
if self.stream.running == False: if self.stream.running == False:
self.start_streaming() self.start_streaming()

View File

@@ -14,14 +14,23 @@ log = logging.getLogger("sessions.twitter.streaming")
class StreamListener(tweepy.StreamListener): class StreamListener(tweepy.StreamListener):
def __init__(self, twitter_api, user, user_id, *args, **kwargs): def __init__(self, twitter_api, user, user_id, muted_users=[], *args, **kwargs):
super(StreamListener, self).__init__(*args, **kwargs) super(StreamListener, self).__init__(*args, **kwargs)
log.debug("Starting streaming listener for account {}".format(user))
self.started = False
self.users = []
self.api = twitter_api self.api = twitter_api
self.user = user self.user = user
self.user_id = user_id self.user_id = user_id
self.users = [str(id) for id in self.api.friends_ids()] friends = self.api.friends_ids()
log.debug("Retrieved {} friends to add to the streaming listener.".format(len(friends)))
self.users.append(str(self.user_id)) self.users.append(str(self.user_id))
log.debug("Started streaming object for user {}".format(self.user)) log.debug("Got {} muted users.".format(len(muted_users)))
for user in friends:
if user not in muted_users:
self.users.append(str(user))
self.started = True
log.debug("Streaming listener started with {} users to follow.".format(len(self.users)))
def on_connect(self): def on_connect(self):
pub.sendMessage("streamConnected", user=self.user) pub.sendMessage("streamConnected", user=self.user)

View File

@@ -60,9 +60,13 @@ def find_urls (tweet, twitter_media=False):
urls.append(i) urls.append(i)
return urls return urls
def find_item(id, listItem): def find_item(item, listItems):
for i in range(0, len(listItem)): for i in range(0, len(listItems)):
if listItem[i].id == id: return i if listItems[i].id == item.id:
return i
# Check also retweets.
if hasattr(item, "retweeted_status") and item.retweeted_status.id == listItems[i].id:
return i
return None return None
def find_list(name, lists): def find_list(name, lists):
@@ -121,9 +125,9 @@ def get_all_mentioned(tweet, conf, field="screen_name"):
""" Gets all users that have been mentioned.""" """ Gets all users that have been mentioned."""
results = [] results = []
if hasattr(tweet, "retweeted_status"): if hasattr(tweet, "retweeted_status"):
results.extend(get_all_mentionned(tweet.retweeted_status, conf, field)) results.extend(get_all_mentioned(tweet.retweeted_status, conf, field))
if hasattr(tweet, "quoted_status"): if hasattr(tweet, "quoted_status"):
results.extend(tweet.quoted_status, conf, field) results.extend(get_all_mentioned(tweet.quoted_status, conf, field))
if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"): if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"):
for i in tweet.entities["user_mentions"]: for i in tweet.entities["user_mentions"]:
if i["screen_name"] != conf["user_name"] and i["id_str"] != tweet.user: if i["screen_name"] != conf["user_name"] and i["id_str"] != tweet.user:
@@ -168,7 +172,7 @@ def is_allowed(tweet, settings, buffer_name):
tweet_data = {} tweet_data = {}
if hasattr(tweet, "retweeted_status"): if hasattr(tweet, "retweeted_status"):
tweet_data["retweet"] = True tweet_data["retweet"] = True
if tweet.in_reply_to_status_id != None: if hasattr(tweet, "in_reply_to_status_id"):
tweet_data["reply"] = True tweet_data["reply"] = True
if hasattr(tweet, "quoted_status"): if hasattr(tweet, "quoted_status"):
tweet_data["quote"] = True tweet_data["quote"] = True
@@ -240,3 +244,20 @@ def expand_urls(text, entities):
if url["url"] in text: if url["url"] in text:
text = text.replace(url["url"], url["expanded_url"]) text = text.replace(url["url"], url["expanded_url"])
return text return text
def clean_mentions(text):
new_text = text
mentionned_people = [u for u in re.finditer("(?<=^|(?<=[^a-zA-Z0-9-\.]))@([A-Za-z0-9_]+)", text)]
if len(mentionned_people) <= 2:
return text
end = -2
total_users = 0
for user in mentionned_people:
if abs(user.start()-end) < 3:
new_text = new_text.replace(user.group(0), "")
total_users = total_users+1
end = user.end()
if total_users < 1:
return text
new_text = _("{user_1}, {user_2} and {all_users} more: {text}").format(user_1=mentionned_people[0].group(0), user_2=mentionned_people[1].group(0), all_users=total_users-2, text=new_text)
return new_text

View File

@@ -3,7 +3,7 @@ import sys
import application import application
import platform import platform
import os import os
from cx_Freeze import setup, Executable from cx_Freeze import setup, Executable, winmsvcr
from requests import certs from requests import certs
def get_architecture_files(): def get_architecture_files():
@@ -40,7 +40,7 @@ build_exe_options = dict(
build_exe="dist", build_exe="dist",
optimize=1, optimize=1,
includes=["enchant.tokenize.en"], # This is not handled automatically by cx_freeze. includes=["enchant.tokenize.en"], # This is not handled automatically by cx_freeze.
include_msvcr=True, include_msvcr=False,
replace_paths = [("*", "")], replace_paths = [("*", "")],
include_files=["icon.ico", "conf.defaults", "app-configuration.defaults", "keymaps", "locales", "sounds", "documentation", ("keys/lib", "keys/lib"), find_sound_lib_datafiles(), find_accessible_output2_datafiles()]+get_architecture_files(), include_files=["icon.ico", "conf.defaults", "app-configuration.defaults", "keymaps", "locales", "sounds", "documentation", ("keys/lib", "keys/lib"), find_sound_lib_datafiles(), find_accessible_output2_datafiles()]+get_architecture_files(),
packages=["wxUI"], packages=["wxUI"],
@@ -50,6 +50,8 @@ executables = [
Executable('main.py', base=base, targetName="twblue") Executable('main.py', base=base, targetName="twblue")
] ]
winmsvcr.FILES = ()
winmsvcr.FILES_TO_DUPLICATE = ()
setup(name=application.name, setup(name=application.name,
version=application.version, version=application.version,
description=application.description, description=application.description,

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import str
import wx import wx
import widgetUtils import widgetUtils
@@ -356,6 +354,8 @@ class viewTweet(widgetUtils.BaseDialog):
infoBox.Add(sourceBox, 0, wx.ALL, 5) infoBox.Add(sourceBox, 0, wx.ALL, 5)
mainBox.Add(infoBox, 0, wx.ALL, 5) mainBox.Add(infoBox, 0, wx.ALL, 5)
mainBox.Add(dateBox, 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.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize)
self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize)
self.unshortenButton.Disable() self.unshortenButton.Disable()
@@ -363,6 +363,7 @@ class viewTweet(widgetUtils.BaseDialog):
cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize)
cancelButton.SetDefault() cancelButton.SetDefault()
buttonsBox = wx.BoxSizer(wx.HORIZONTAL) buttonsBox = wx.BoxSizer(wx.HORIZONTAL)
buttonsBox.Add(self.share, 0, wx.ALL, 5)
buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5)
buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5) buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5)
buttonsBox.Add(self.translateButton, 0, wx.ALL, 5) buttonsBox.Add(self.translateButton, 0, wx.ALL, 5)
@@ -429,6 +430,8 @@ class viewNonTweet(widgetUtils.BaseDialog):
dateBox.Add(dateLabel, 0, wx.ALL, 5) dateBox.Add(dateLabel, 0, wx.ALL, 5)
dateBox.Add(date, 0, wx.ALL, 5) dateBox.Add(date, 0, wx.ALL, 5)
mainBox.Add(dateBox, 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.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize)
self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize) self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize)
self.unshortenButton.Disable() self.unshortenButton.Disable()
@@ -436,6 +439,7 @@ class viewNonTweet(widgetUtils.BaseDialog):
cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize) cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize)
cancelButton.SetDefault() cancelButton.SetDefault()
buttonsBox = wx.BoxSizer(wx.HORIZONTAL) buttonsBox = wx.BoxSizer(wx.HORIZONTAL)
buttonsBox.Add(self.share, 0, wx.ALL, 5)
buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5)
buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5) buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5)
buttonsBox.Add(self.translateButton, 0, wx.ALL, 5) buttonsBox.Add(self.translateButton, 0, wx.ALL, 5)
@@ -463,5 +467,5 @@ class viewNonTweet(widgetUtils.BaseDialog):
self.text.SetFocus() self.text.SetFocus()
def enable_button(self, buttonName): def enable_button(self, buttonName):
if getattr(self, buttonName): if hasattr(self, buttonName):
return getattr(self, buttonName).Enable() return getattr(self, buttonName).Enable()

View File

@@ -48,3 +48,36 @@ class selectUserDialog(baseDialog.BaseWXDialog):
def get_user(self): def get_user(self):
return self.cb.GetValue() return self.cb.GetValue()
class addAliasDialog(baseDialog.BaseWXDialog):
def __init__(self, title, users):
super(addAliasDialog, self).__init__(parent=None, id=wx.ID_ANY, title=title)
panel = wx.Panel(self)
userSizer = wx.BoxSizer()
self.cb = wx.ComboBox(panel, -1, choices=users, value=users[0], size=wx.DefaultSize)
self.cb.SetFocus()
self.autocompletion = wx.Button(panel, -1, _(u"&Autocomplete users"))
userSizer.Add(wx.StaticText(panel, -1, _(u"User")), 0, wx.ALL, 5)
userSizer.Add(self.cb, 0, wx.ALL, 5)
userSizer.Add(self.autocompletion, 0, wx.ALL, 5)
aliasSizer = wx.BoxSizer(wx.HORIZONTAL)
aliasLabel = wx.StaticText(panel, wx.ID_ANY, _("Alias"))
self.alias = wx.TextCtrl(panel, wx.ID_ANY)
aliasSizer.Add(aliasLabel, 0, wx.ALL, 5)
aliasSizer.Add(self.alias, 0, wx.ALL, 5)
sizer = wx.BoxSizer(wx.VERTICAL)
ok = wx.Button(panel, wx.ID_OK, _(u"OK"))
ok.SetDefault()
# ok.Bind(wx.EVT_BUTTON, self.onok)
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
btnsizer = wx.BoxSizer()
btnsizer.Add(ok, 0, wx.ALL, 5)
btnsizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(userSizer, 0, wx.ALL, 5)
sizer.Add(aliasSizer, 0, wx.ALL, 5)
sizer.Add(btnsizer, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def get_user(self):
return (self.cb.GetValue(), self.alias.GetValue())

View File

@@ -43,6 +43,7 @@ class mainFrame(wx.Frame):
self.follow = user.Append(wx.ID_ANY, _(u"&Actions...")) self.follow = user.Append(wx.ID_ANY, _(u"&Actions..."))
self.timeline = user.Append(wx.ID_ANY, _(u"&View timeline...")) self.timeline = user.Append(wx.ID_ANY, _(u"&View timeline..."))
self.dm = user.Append(wx.ID_ANY, _(u"Direct me&ssage")) self.dm = user.Append(wx.ID_ANY, _(u"Direct me&ssage"))
self.addAlias = user.Append(wx.ID_ANY, _("Add a&lias"))
self.addToList = user.Append(wx.ID_ANY, _(u"&Add to list")) self.addToList = user.Append(wx.ID_ANY, _(u"&Add to list"))
self.removeFromList = user.Append(wx.ID_ANY, _(u"R&emove from list")) self.removeFromList = user.Append(wx.ID_ANY, _(u"R&emove from list"))
self.viewLists = user.Append(wx.ID_ANY, _(u"&View lists")) self.viewLists = user.Append(wx.ID_ANY, _(u"&View lists"))

View File

@@ -1,7 +1,7 @@
{"current_version": "7", {"current_version": "8",
"description": "Snapshot version.", "description": "Snapshot version.",
"date": "unknown", "date": "unknown",
"downloads": "downloads":
{"Windows32": "https://twblue.es/pubs/twblue_snapshot_x86.zip"}, {"Windows32": "https://twblue.es/pubs/twblue_snapshot_x86.zip",
{"Windows64": "https://twblue.es/pubs/twblue_snapshot_x64.zip"} {"Windows64": "https://twblue.es/pubs/twblue_snapshot_x64.zip"
} }