Compare commits

...

82 Commits

Author SHA1 Message Date
8116d98691 Fix wx to 4.1.1 for now 2022-08-28 05:52:51 -05:00
1b498a99bd Updated version info 2022-08-28 05:00:54 -05:00
1a4e594549 Merge branch 'weblate-twblue-twblue' into 'next-gen'
Translations update from Translations hub

See merge request twblue/twblue!6
2022-08-25 22:00:58 +00:00
zvonimir stanecic
2b0172f02f Translated using Weblate (Croatian)
Currently translated at 92.0% (733 of 796 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/hr/
2022-08-25 13:09:14 -05:00
2d2c84fefe Translated using Weblate (Spanish)
Currently translated at 100.0% (258 of 258 strings)

Translation: TWBlue/changelog
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/changelog/es/
2022-08-25 13:09:14 -05:00
zvonimir stanecic
0bef872070 Translated using Weblate (Croatian)
Currently translated at 88.3% (703 of 796 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/hr/
2022-08-25 13:09:14 -05:00
b233842f5c Translated using Weblate (Spanish)
Currently translated at 100.0% (796 of 796 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/es/
2022-08-25 13:09:14 -05:00
0202b704da Merge branch 'next-gen' of gitlab.mcvsoftware.com:twblue/twblue into next-gen 2022-08-25 13:08:24 -05:00
7d3ac47d55 correct name in some locale folders to make it standard 2022-08-25 12:56:47 -05:00
e2ba61701f Merge branch 'weblate-twblue-twblue' into 'next-gen'
Translations update from Translations hub

See merge request twblue/twblue!5
2022-08-24 16:59:23 +00:00
Weblate
8032f5cf75 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: TWBlue/changelog
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/changelog/
2022-08-24 11:32:59 -05:00
Weblate
96f5d0e426 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: TWBlue/changelog
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/changelog/
2022-08-24 11:32:58 -05:00
0ba71c985d Translated using Weblate (Spanish)
Currently translated at 100.0% (234 of 234 strings)

Translation: TWBlue/changelog
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/changelog/es/
2022-08-24 11:32:58 -05:00
c6cbe3360b Restart streaming in certain changes to followed/muted people 2022-08-24 11:32:06 -05:00
ac80e039b2 Updated template files for documentation translations 2022-08-23 16:27:30 -05:00
2039597098 Removes properly new twitter quote statuses on templating system 2022-08-23 11:51:37 -05:00
74fd4bfadf Removed unneeded code 2022-08-23 11:34:13 -05:00
c374663b7c Merge branch 'weblate-twblue-twblue' into 'next-gen'
Translations update from Translations hub

See merge request twblue/twblue!4
2022-08-22 20:41:21 +00:00
0f3d7d8747 Translated using Weblate (Spanish)
Currently translated at 99.7% (794 of 796 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/es/
2022-08-22 15:40:45 -05:00
0a395af3a9 Updated changelog 2022-08-22 15:40:01 -05:00
José Manuel Delicado
f181c5d1cd Merge pull request #489 from riku22/japanese_translation
Update Japanese translation
2022-08-20 09:39:27 +02:00
riku
204b9ebc02 Update Japanese translation 2022-08-20 07:22:36 +09:00
0a479b55ed Merge branch 'weblate-twblue-twblue' into 'next-gen'
Translations update from Translations hub

See merge request twblue/twblue!3
2022-08-19 15:26:42 +00:00
Weblate
eb0c3a650a Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/
2022-08-18 12:17:12 -05:00
829506acf5 Updated main POT file 2022-08-18 12:05:38 -05:00
14ee87b25a Merge pull request #488 from nidza07/SerbianTranslation
Updated Serbian translation
2022-08-18 10:18:29 -05:00
ebdf2e21bb Merge pull request #487 from CoBC/fr_180822
Update French interface
2022-08-18 10:17:30 -05:00
Nikola Jovic
30a944fc61 Updated Serbian translation 2022-08-18 16:09:26 +02:00
Corentin Bacqué-Cazenave
5c0af2fc01 Update French interface 2022-08-18 15:33:02 +02:00
e470498e56 updated locales to make dates more standard as babel requires it to work properly 2022-08-17 10:55:55 -05:00
fa2d2d9d78 Improved sorting in conversation buffers 2022-08-17 10:09:32 -05:00
b9794806d7 Display error when attempting to open a TL for an unauthorized user. Closes #485 2022-08-16 17:11:50 -05:00
1e686cffba Merge pull request #486 from MCV-Software/autocomplete_redesign
Changes in user autocompletion feature. Closes #466. Closes #368
2022-08-03 11:56:13 -05:00
0f624c7dbe Updated changelog 2022-08-03 11:24:26 -05:00
76a5c960e5 Added user autocompletion functionality to some dialogs. Closes #466 2022-08-03 11:12:23 -05:00
aab8aafefc Introduced user selector controller for implementing user autocompletion in wxUI.dialogs.utils.selectUserDialog 2022-08-03 11:11:31 -05:00
015cf9eca3 Fixed small typo 2022-08-03 10:36:54 -05:00
769436abf7 Updated menu to avoid using deprecated wx methods 2022-08-03 09:53:35 -05:00
b2da25dd61 Updated code on controllers for changes in user autocompletion module 2022-08-03 09:52:09 -05:00
03f59064d5 Report amount of imported users after scan 2022-08-03 09:39:45 -05:00
9afd6774f2 Added a new and custom progress dialog for user scanning 2022-08-03 09:37:18 -05:00
a1929ff1d3 Make sure Show() is called for progress dialog 2022-08-02 09:06:46 -05:00
4b627a13ff Start a small refactor in GUI code 2022-07-29 17:54:34 -05:00
f9f7a32f90 Bind manage button with code to manage autocompletion db 2022-07-29 17:37:28 -05:00
f01ad5abb7 Return on exception 2022-07-29 17:27:48 -05:00
6fcd0274a9 Added exception handling to scan for users 2022-07-29 13:30:43 -05:00
654b34c8e1 Cleaned some unneeded dialogs 2022-07-29 12:11:27 -05:00
02e1793d08 cleaned scan controller and made progress dialog to work 2022-07-29 12:11:03 -05:00
6e80b320c9 Improved documentation in some classes 2022-07-29 12:08:05 -05:00
83c9db573e Added first stage of refactor for autocompletion users 2022-07-08 11:39:14 -05:00
3aee5e8900 Merge pull request #482 from riku22/japanese_translation
Update Japanese translation
2022-07-07 16:27:40 -05:00
riku
a88af08181 Update Japanese translation 2022-07-08 05:19:51 +09:00
79098cc730 Updated changelog 2022-07-07 11:17:57 -05:00
d12e3aa335 Merge pull request #480 from CoBC/fr_070722
Update French interface
2022-07-07 08:56:48 -05:00
Corentin Bacqué-Cazenave
1d8fcf9166 Update Frenchinterface 2022-07-07 15:45:38 +02:00
3702405f8c Merge pull request #479 from CoBC/tr_relationship
Add "Relationship" to translation
2022-07-07 08:41:40 -05:00
7ce55c427e Merge pull request #478 from nidza07/SerbianTranslation
Updated Serbian translation
2022-07-07 08:40:19 -05:00
508b798bee Merge pull request #477 from CoBC/fr_160622
Update french interface
2022-07-07 08:39:37 -05:00
53ac43a8b2 Merge pull request #476 from jeremyp3/issue-420
add shortcut for find dialog in Windows 10 and Windows 11 keymap fix #420
2022-07-07 08:38:18 -05:00
Corentin Bacqué-Cazenave
22b8d91612 Add "Relationship" to translation 2022-06-23 10:27:09 +02:00
Nikola Jovic
4f619759e6 Updated Serbian translation 2022-06-20 14:58:37 +02:00
Corentin Bacqué-Cazenave
0ac6f49379 Update french interface 2022-06-16 08:57:36 +02:00
Jeremyp3
b332902b91 add shortcut for find dialog in Windows 10 and Windows 11 keymap fix #420 2022-06-11 15:45:04 +02:00
a138b8c02e Started to document autocompltion users module 2022-05-26 05:45:46 -05:00
c89dff053d fixed error when sending a tweet to someone in any people buffer 2022-05-26 05:26:02 -05:00
018095752b Merge branch 'next-gen' of github.com:mcv-software/twblue into next-gen 2022-05-21 20:16:03 -05:00
bd90902e3b Fixed alternative updatefile 2022-05-21 20:15:36 -05:00
8bf9c6519e Improve text for direct messages when displayed via the templates system 2022-05-20 12:22:25 -05:00
bed8e26204 Fixed loading of other users lists. Fixes #465 2022-05-13 13:33:10 -05:00
fa9ebea836 Remove issue reporting dialogs as we no longer use mantis 2022-05-13 13:22:52 -05:00
be8d82a65f Merge pull request #471 from MCV-Software/demoji-support
Added setting to hide emojis in usernames
2022-05-13 13:16:02 -05:00
ab979e2623 Added setting to hide emojis in usernames 2022-05-13 13:04:12 -05:00
20ad268e5a Merge pull request #469 from riku22/fix_edit_person_template
Fix edit person template
2022-04-29 08:15:49 -05:00
riku
83c9a7f0f9 Fix edit person template 2022-04-29 21:45:06 +09:00
9d90d91cfd Merge branch 'next-gen' of github.com:mcv-software/twblue into next-gen 2022-04-06 15:13:22 -05:00
bbf1356c89 Fixed an error when parsing retweets containing quoted tweets in streaming API 2022-04-06 15:11:55 -05:00
67d15d8fe1 Merge pull request #462 from jonasmarques/next-gen
Fixed UTF8 display for translation  of reply to dialog
2022-02-25 11:23:26 -06:00
jonasmarques
3bd274db0c Fixed UTF8 display for translation of reply to dialog
The previous translations have a little bug who causes misspronounce in the reply to dialog. This was fixed.
2022-02-25 07:50:08 -03:00
6147ca8658 Merge pull request #460 from nidza07/SerbianTranslation
Updated Serbian translation
2022-02-24 17:13:04 -06:00
Nikola Jovic
7833522b56 Updated Serbian translation 2022-02-25 00:01:32 +01:00
5e47e30bc3 Merge pull request #459 from jonasmarques/next-gen
Updated Brazilian Portuguese Translation
2022-02-24 13:33:35 -06:00
jonasmarques
c6a2ba6a31 Updated Brazilian Portuguese Translation
New entries  to pt_BR  translated. Just recompile it to work  on compiled version.
2022-02-24 16:23:17 -03:00
78 changed files with 74075 additions and 64472 deletions

View File

@@ -1,6 +1,22 @@
TWBlue Changelog
## changes in this version
* the user autocompletion feature has been completely rewritten to be easier to use, particularly for people with many followers/following users:
* In the account settings dialog, there's a button that opens up a new dialog that allows you to "scan" your account in order to add all users from your followers/following list. This process will read your data directly from Twitter and depending in the amount of people you have in your account it might take too many API calls. Please use it with caution. You can, for example, do the process separately for your followers/following people so it will be easier to handle, in case you have a massive amount of people. If TWBlue is unable to complete the scan, you will see an error and will be prompted to try again in 15 minutes, once your API calls have refreshed.
* It is possible to use the user autocompletion functionality in dialogs where you can select an user, for example when adding or removing someone from a list, or displaying lists for someone.
* Implemented a new setting, available in the account settings dialog, that allows to hide emojis in twitter usernames.
* TWBlue should be able to sort conversations in a more logical way. This should make it easier to follow a long thread in Twitter.
* When opening a thread, TWBlue should be able to load the right conversation if the original tweet from where the thread was loaded was a retweet.
* TWBlue will restart the Streaming subsystem every time there are changes to followed, muted or blocked users within the application.
* Fixed error when attempting to mention an user by using the "mention" button in any people buffer. Now tweets should be posted normally.
* Fixed error when loading other user lists. ([#465](https://github.com/MCV-Software/TWBlue/issues/465))
* Fixed an issue that was making TWBlue to display incorrectly some retweets of quoted tweets.
* If TWBlue is unable to open a timeline for someone who has blocked you, this will be reported in a dialog. ([#485,](https://github.com/mcv-software/twblue/issues/485))
* Added "find a string in the currently focused buffer" action into Windows 10 and windows 11 keymap. ([#476](https://github.com/MCV-Software/TWBlue/pull/476))
## changes in version 22.2.23
* We have added Experimental support for templates in the invisible interface. The GUI will remain unchanged for now:
* Each object (tweet, received direct message, sent direct message and people) has its own template in the settings. You can edit those templates from the account settings dialog, in the new "templates" tab.
* Every template is composed of the group of variables you want to display for each object. Each variable will start with a dollar sign ($) and cannot contain spaces or special characters. Templates can include arbitrary text that will not be processed. When editing the example templates, you can get an idea of the variables that are available for each object by using the template editing dialog. When you press enter on a variable from the list of available variables, it will be added to the template automatically. When you try to save a template, TWBlue will warn you if the template is incorrectly formatted or if it includes variables that do not exist in the information provided by objects. It is also possible to return to default values from the same dialog when editing a template.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
wxpython
wxpython==4.1.1
wheel
six
configobj
@@ -48,6 +48,7 @@ importlib-metadata
numpy
pillow
charset-normalizer
demoji
git+https://github.com/accessibleapps/libloader
git+https://github.com/accessibleapps/platform_utils
git+https://github.com/accessibleapps/accessible_output2

View File

@@ -14,6 +14,7 @@ retweet_mode = string(default="ask")
persist_size = integer(default=0)
load_cache_in_memory=boolean(default=True)
show_screen_names = boolean(default=False)
hide_emojis = boolean(default=False)
buffer_order = list(default=list('home','mentions', 'dm', 'sent_dm', 'sent_tweets','favorites','followers','friends','blocks','muted'))
[sound]

View File

@@ -9,6 +9,6 @@ copyright = "Copyright (C) 2013-2022, MCV Software."
description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features."
translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Corentin Bacqué-Cazenave (Français)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"]
url = "https://twblue.es"
report_bugs_url = "https://github.com/manuelcortez/twblue/issues"
report_bugs_url = "https://github.com/mcvsoftware/twblue/issues"
supported_languages = []
version = "11"

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from .base import BaseBuffer

View File

@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
""" Common logic to all buffers in TWBlue."""
import logging
import wx
import output
import sound
import widgetUtils
log = logging.getLogger("controller.buffers.base.base")
class Buffer(object):
""" A basic buffer object. This should be the base class for all other derived buffers."""
def __init__(self, parent=None, function=None, session=None, *args, **kwargs):
"""Inits the main controller for this buffer:
@ parent wx.Treebook object: Container where we will put this buffer.
@ function str or None: function to be called periodically and update items on this buffer.
@ session sessionmanager.session object or None: Session handler for settings, database and data access.
"""
super(Buffer, self).__init__()
self.function = function
# Compose_function will be used to render an object on this buffer. Normally, signature is as follows:
# compose_function(item, db, relative_times, show_screen_names=False, session=None)
# Read more about compose functions in sessions/twitter/compose.py.
self.compose_function = None
self.args = args
self.kwargs = kwargs
# This will be used as a reference to the wx.Panel object wich stores the buffer GUI.
self.buffer = None
# This should countains the account associated to this buffer.
self.account = ""
# This controls whether the start_stream function should be called when starting the program.
self.needs_init = True
# if this is set to False, the buffer will be ignored on the invisible interface.
self.invisible = False
# Control variable, used to track time of execution for calls to start_stream.
self.execution_time = 0
def clear_list(self):
pass
def get_event(self, ev):
""" Catch key presses in the WX interface and generate the corresponding event names."""
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio"
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url"
elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list"
elif ev.GetKeyCode() == wx.WXK_DELETE: event = "destroy_status"
# Raise a Special event when pressed Shift+F10 because Wx==4.1.x does not seems to trigger this by itself.
# See https://github.com/manuelcortez/TWBlue/issues/353
elif ev.GetKeyCode() == wx.WXK_F10 and ev.ShiftDown(): event = "show_menu"
else:
event = None
ev.Skip()
if event != None:
try:
### ToDo: Remove after WX fixes issue #353 in the widgets.
if event == "show_menu":
return self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
getattr(self, event)()
except AttributeError:
pass
def volume_down(self):
""" Decreases volume by 5%"""
if self.session.settings["sound"]["volume"] > 0.0:
if self.session.settings["sound"]["volume"] <= 0.05:
self.session.settings["sound"]["volume"] = 0.0
else:
self.session.settings["sound"]["volume"] -=0.05
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0))
self.session.sound.play("volume_changed.ogg")
self.session.settings.write()
def volume_up(self):
""" Increases volume by 5%."""
if self.session.settings["sound"]["volume"] < 1.0:
if self.session.settings["sound"]["volume"] >= 0.95:
self.session.settings["sound"]["volume"] = 1.0
else:
self.session.settings["sound"]["volume"] +=0.05
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100))
self.session.sound.play("volume_changed.ogg")
self.session.settings.write()
def start_stream(self, mandatory=False, play_sound=True):
pass
def get_more_items(self):
output.speak(_(u"This action is not supported for this buffer"), True)
def put_items_on_list(self, items):
pass
def remove_buffer(self):
return False
def remove_item(self, item):
f = self.buffer.list.get_selected()
self.buffer.list.remove_item(item)
self.buffer.list.select_item(f)
def bind_events(self):
pass
def get_object(self):
return self.buffer
def get_message(self):
pass
def set_list_position(self, reversed=False):
if reversed == False:
self.buffer.list.select_item(-1)
else:
self.buffer.list.select_item(0)
def reply(self):
pass
def send_message(self):
pass
def share_item(self):
pass
def can_share(self):
pass
def destroy_status(self):
pass
def post_status(self, *args, **kwargs):
pass
def save_positions(self):
try:
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
except AttributeError:
pass

View File

@@ -98,7 +98,7 @@ class PeopleBuffer(base.BaseBuffer):
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 = message.get_tweet_data()
call_threaded(self.session.send_tweet, tweet_data)
call_threaded(self.session.send_tweet, *tweet_data)
if hasattr(message.message, "destroy"):
message.message.destroy()

View File

@@ -153,6 +153,7 @@ class ConversationBuffer(SearchBuffer):
results.extend(reply_results)
except TweepyException as e:
log.exception("There was an error attempting to retrieve tweets for Twitter API V1.1, in conversation buffer {}".format(self.name))
results.sort(key=lambda x: x.id)
return results
def get_replies_v1(self, tweet):

View File

@@ -32,7 +32,7 @@ class listsController(object):
return [compose.compose_list(item) for item in self.session.db["lists"]]
def get_user_lists(self, user):
self.lists = self.session.twitter.lists_all(reverse=True, screen_name=user)
self.lists = self.session.twitter.get_lists(reverse=True, screen_name=user)
return [compose.compose_list(item) for item in self.lists]
def create_list(self, *args, **kwargs):

View File

@@ -18,7 +18,7 @@ if system == "Windows":
from . import user
from . import listsController
from . import filterController
# from issueReporter import issueReporter
from . import userSelector
elif system == "Linux":
from gtkUI import (view, commonMessageDialogs)
from sessions.twitter import utils, compose
@@ -135,6 +135,7 @@ class Controller(object):
pub.subscribe(self.manage_unblocked_user, "unblocked-user")
pub.subscribe(self.create_buffer, "createBuffer")
pub.subscribe(self.toggle_share_settings, "toggleShare")
pub.subscribe(self.restart_streaming, "restartStreaming")
if system == "Windows":
pub.subscribe(self.invisible_shorcuts_changed, "invisible-shorcuts-changed")
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.show_hide, menuitem=self.view.show_hide)
@@ -185,7 +186,6 @@ class Controller(object):
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.seekRight, menuitem=self.view.seekRight)
if widgetUtils.toolkit == "wx":
widgetUtils.connect_event(self.view.nb, widgetUtils.NOTEBOOK_PAGE_CHANGED, self.buffer_changed)
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_changelog, self.view.changelog)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_alias, self.view.addAlias)
@@ -520,10 +520,9 @@ class Controller(object):
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user()
else:
selector = userSelector.userSelector(users=users, session_id=buff.session.session_id)
user = selector.get_user()
if user == None:
return
l = listsController.listsController(buff.session, user=user)
@@ -537,10 +536,9 @@ class Controller(object):
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user()
else:
selector = userSelector.userSelector(users=users, session_id=buff.session.session_id)
user = selector.get_user()
if user == None:
return
dlg = dialogs.lists.addUserListDialog()
dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]])
@@ -566,10 +564,9 @@ class Controller(object):
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user()
else:
selector = userSelector.userSelector(users=users, session_id=buff.session.session_id)
user = selector.get_user()
if user == None:
return
dlg = dialogs.lists.removeUserListDialog()
dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]])
@@ -611,9 +608,6 @@ class Controller(object):
buff.session.save_persistent_data()
restart.restart_program()
def report_error(self, *args, **kwargs):
r = issueReporter.reportBug(self.get_best_buffer().session.db["user_name"])
def check_for_updates(self, *args, **kwargs):
update = updater.do_update()
if update == False:
@@ -876,7 +870,7 @@ class Controller(object):
tl = buffers.twitter.BaseBuffer(self.view.nb, "user_timeline", "%s-timeline" % (usr.id_str,), buff.session, buff.session.db["user_name"], bufferType=None, sound="tweet_timeline.ogg", user_id=usr.id_str, include_ext_alt_text=True, tweet_mode="extended")
try:
tl.start_stream(play_sound=False)
except ValueError:
except TweepyException:
commonMessageDialogs.unauthorized()
return
pos=self.view.search("timelines", buff.session.db["user_name"])
@@ -948,10 +942,12 @@ class Controller(object):
def open_conversation(self, *args, **kwargs):
buffer = self.get_current_buffer()
id = buffer.get_right_tweet().id
user = buffer.session.get_user(buffer.get_right_tweet().user).screen_name
search = buffers.twitter.ConversationBuffer(self.view.nb, "search_tweets", "%s-searchterm" % (id,), buffer.session, buffer.session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", since_id=id, q="@{0}".format(user,))
search.tweet = buffer.get_right_tweet()
tweet = buffer.get_right_tweet()
if hasattr(tweet, "retweeted_status") and tweet.retweeted_status != None:
tweet = tweet.retweeted_status
user = buffer.session.get_user(tweet.user).screen_name
search = buffers.twitter.ConversationBuffer(self.view.nb, "search_tweets", "%s-searchterm" % (tweet.id,), buffer.session, buffer.session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", since_id=tweet.id, q="@{0}".format(user,))
search.tweet = tweet
search.start_stream(start=True)
pos=self.view.search("searches", buffer.session.db["user_name"])
self.insert_buffer(search, pos)
@@ -1661,7 +1657,6 @@ class Controller(object):
def toggle_share_settings(self, shareable=True):
self.view.retweet.Enable(shareable)
def check_streams(self):
if self.started == False:
return
@@ -1671,3 +1666,10 @@ class Controller(object):
sessions.sessions[i].check_streams()
except TweepyException: # We shouldn't allow this function to die.
pass
def restart_streaming(self, session):
for s in sessions.sessions:
if sessions.sessions[s].session_id == session:
log.debug("Restarting stream in session %s" % (session))
sessions.sessions[s].stop_streaming()
sessions.sessions[s].start_streaming()

View File

@@ -11,8 +11,9 @@ from pubsub import pub
from twitter_text import parse_tweet
from wxUI.dialogs import twitterDialogs, urlList
from wxUI import commonMessageDialogs
from extra import translator, SpellChecker, autocompletionUsers
from extra import translator, SpellChecker
from extra.AudioUploader import audioUploader
from extra.autocompletionUsers import completion
from sessions.twitter import utils
class basicTweet(object):
@@ -160,7 +161,7 @@ class tweet(basicTweet):
self.text_processor()
def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
c = completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu()
def add_tweet(self, event, update_gui=True, *args, **kwargs):
@@ -269,7 +270,7 @@ class dm(basicTweet):
self.text_processor()
def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
c = completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu("dm")
def text_processor(self, *args, **kwargs):

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import os
import webbrowser
import threading
import logging
import sound_lib
import paths
@@ -16,7 +17,7 @@ from pubsub import pub
from mysc import autostart as autostart_windows
from wxUI.dialogs import configuration
from wxUI import commonMessageDialogs
from extra.autocompletionUsers import settings
from extra.autocompletionUsers import scan, manage
from extra.ocr import OCRSpace
from .editTemplateController import EditTemplate
@@ -142,9 +143,11 @@ class accountSettingsController(globalSettingsController):
def create_config(self):
self.dialog.create_general_account()
widgetUtils.connect_event(self.dialog.general.au, widgetUtils.BUTTON_PRESSED, self.manage_autocomplete)
widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"])
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_tweets_per_call"])
self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"])
rt = self.config["general"]["retweet_mode"]
@@ -228,7 +231,7 @@ class accountSettingsController(globalSettingsController):
self.dialog.templates.sent_dm.SetLabel(_("Edit template for sent direct messages. Current template: {}").format(result))
def edit_person_template(self, *args, **kwargs):
template = self.settings["templates"]["person"]
template = self.config["templates"]["person"]
control = EditTemplate(template=template, type="person")
result = control.run_dialog()
if result != "": # Template has been saved.
@@ -242,6 +245,7 @@ class accountSettingsController(globalSettingsController):
log.debug("Triggered app restart due to change in relative times.")
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
self.config["general"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
@@ -302,8 +306,19 @@ class accountSettingsController(globalSettingsController):
def toggle_state(self,*args,**kwargs):
return self.dialog.buffers.change_selected_item()
def manage_autocomplete(self, *args, **kwargs):
configuration = settings.autocompletionSettings(self.buffer.session.settings, self.buffer, self.window)
def on_autocompletion_scan(self, *args, **kwargs):
configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window)
to_scan = configuration.show_dialog()
if to_scan == True:
configuration.prepare_progress_dialog()
t = threading.Thread(target=configuration.scan)
t.start()
def on_autocompletion_manage(self, *args, **kwargs):
configuration = manage.autocompletionManage(self.buffer.session)
configuration.show_settings()
def add_ignored_client(self, *args, **kwargs):
client = commonMessageDialogs.get_ignored_client()

View File

@@ -107,7 +107,7 @@ class profileController(object):
string = string+ _(u"Protected: %s\n") % (protected)
if hasattr(self, "friendship_status"):
relation = False
friendship = "Relationship: "
friendship = _(u"Relationship: ")
if self.friendship_status[0].following:
friendship += _(u"You follow {0}. ").format(self.data.name,)
relation = True

View File

@@ -4,7 +4,7 @@ import output
from wxUI.dialogs import userActions
from pubsub import pub
from tweepy.errors import TweepyException
from extra import autocompletionUsers
from extra.autocompletionUsers import completion
class userActionsController(object):
def __init__(self, buffer, users=[], default="follow"):
@@ -17,7 +17,7 @@ class userActionsController(object):
self.process_action()
def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.dialog, self.session.session_id)
c = completion.autocompletionUsers(self.dialog, self.session.session_id)
c.show_menu("dm")
def process_action(self):
@@ -29,36 +29,42 @@ class userActionsController(object):
def follow(self, user):
try:
self.session.twitter.create_friendship(screen_name=user )
pub.sendMessage("restartStreaming", session=self.session.session_id)
except TweepyException as err:
output.speak("Error %s" % (str(err)), True)
def unfollow(self, user):
try:
id = self.session.twitter.destroy_friendship(screen_name=user )
pub.sendMessage("restartStreaming", session=self.session.session_id)
except TweepyException as err:
output.speak("Error %s" % (str(err)), True)
def mute(self, user):
try:
id = self.session.twitter.create_mute(screen_name=user )
pub.sendMessage("restartStreaming", session=self.session.session_id)
except TweepyException as err:
output.speak("Error %s" % (str(err)), True)
def unmute(self, user):
try:
id = self.session.twitter.destroy_mute(screen_name=user )
pub.sendMessage("restartStreaming", session=self.session.session_id)
except TweepyException as err:
output.speak("Error %s" % (str(err)), True)
def report(self, user):
try:
id = self.session.twitter.report_spam(screen_name=user )
pub.sendMessage("restartStreaming", session=self.session.session_id)
except TweepyException as err:
output.speak("Error %s" % (str(err)), True)
def block(self, user):
try:
id = self.session.twitter.create_block(screen_name=user )
pub.sendMessage("restartStreaming", session=self.session.session_id)
except TweepyException as err:
output.speak("Error %s" % (str(err)), True)

View File

@@ -2,7 +2,6 @@
import widgetUtils
from pubsub import pub
from wxUI.dialogs import userAliasDialogs
from extra import autocompletionUsers
class userAliasController(object):
def __init__(self, settings):

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
""" Small utility dessigned to select users from the currently focused item or the autocomplete database. """
import wx
import widgetUtils
from wxUI.dialogs import utils
from extra.autocompletionUsers import completion
class userSelector(object):
def __init__(self, users, session_id, title=_("Select user")):
""" Creates a dialog that chooses an user selector, from where users who have the autocomplete database already filled can also use that feature.
:param users: lists of users extracted from the currently focused item.
:type users: list
:param session_id: ID of the session to instantiate autocomplete database.
:type session_id: str
:param title: Title of the user selector dialog.
:type title: str
"""
self.session_id = session_id
self.dlg = utils.selectUserDialog(users=users, title=title)
widgetUtils.connect_event(self.dlg.autocompletion, widgetUtils.BUTTON_PRESSED, self.on_autocomplete_users)
def on_autocomplete_users(self, *args, **kwargs):
""" performs user autocompletion, if configured properly. """
c = completion.autocompletionUsers(self.dlg, self.session_id)
c.show_menu("dm")
def get_user(self):
""" Actually shows the dialog and returns an user if the dialog was accepted, None otherwise.
:rtype: str or None
"""
if self.dlg.ShowModal() == wx.ID_OK:
user = self.dlg.get_user()
else:
user = None
self.dlg.Destroy()
return user

View File

@@ -1,5 +1,2 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
# -*- coding: utf-8 -*-
from . import completion, settings
""" Autocompletion users for TWBlue. This package contains all needed code to support this feature, including automatic addition of users, management and code to show the autocompletion menu when an user is composing a tweet. """

View File

@@ -1,17 +1,36 @@
# -*- coding: utf-8 -*-
""" Module to display the user autocompletion menu in tweet or direct message dialogs. """
import output
from . import storage
from . import wx_menu
class autocompletionUsers(object):
def __init__(self, window, session_id):
""" Class constructor. Displays a menu with users matching the specified pattern for autocompletion.
:param window: A wx control where the menu should be displayed. Normally this is going to be the wx.TextCtrl indicating the tweet's text or direct message recipient.
:type window: wx.Dialog
:param session_id: Session ID which calls this class. We will load the users database from this session.
:type session_id: str.
"""
super(autocompletionUsers, self).__init__()
self.window = window
self.db = storage.storage(session_id)
def show_menu(self, mode="tweet"):
position = self.window.text.GetInsertionPoint()
""" displays a menu with possible users matching the specified pattern.
this menu can be displayed in tweet dialogs or in any other dialog where an username is expected. For tweet dialogs, the string should start with an at symbol (@), otherwise it won't match the pattern.
Of course, users must be already loaded in database before attempting this.
If no users are found, an error message will be spoken.
:param mode: this controls how the dialog will behave. Possible values are 'tweet' and 'dm'. In tweet mode, the matching pattern will be @user (@ is required), while in 'dm' mode the matching pattern will be anything written in the text control.
:type mode: str
"""
if mode == "tweet":
position = self.window.text.GetInsertionPoint()
text = self.window.text.GetValue()
text = text[:position]
try:
@@ -41,7 +60,7 @@ class autocompletionUsers(object):
users = self.db.get_users(pattern)
if len(users) > 0:
menu.append_options(users)
self.window.PopupMenu(menu, self.window.text.GetPosition())
self.window.PopupMenu(menu, self.window.cb.GetPosition())
menu.destroy()
else:
output.speak(_(u"There are no results in your users database"))

View File

@@ -1,15 +1,27 @@
# -*- coding: utf-8 -*-
""" Management of users in the local database for autocompletion. """
import time
import widgetUtils
from tweepy.cursor import Cursor
from tweepy.errors import TweepyException
from . import storage, wx_manage
from wxUI import commonMessageDialogs
from . import storage, wx_manage
class autocompletionManage(object):
def __init__(self, session):
""" class constructor. Manages everything related to user autocompletion.
:param session: Sessiom where the autocompletion management has been requested.
:type session: sessions.base.Session.
"""
super(autocompletionManage, self).__init__()
self.session = session
self.dialog = wx_manage.autocompletionManageDialog()
# Instantiate database so we can perform modifications on it.
self.database = storage.storage(self.session.session_id)
def show_settings(self):
""" display user management dialog and connect events associated to it. """
self.dialog = wx_manage.autocompletionManageDialog()
self.users = self.database.get_all_users()
self.dialog.put_users(self.users)
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add_user)
@@ -17,6 +29,7 @@ class autocompletionManage(object):
self.dialog.get_response()
def update_list(self):
""" update users list in management dialog. This function is normallhy used after we modify the database in any way, so we can reload all users in the autocompletion user management list. """
item = self.dialog.users.get_selected()
self.dialog.users.clear()
self.users = self.database.get_all_users()
@@ -24,9 +37,12 @@ class autocompletionManage(object):
self.dialog.users.select_item(item)
def add_user(self, *args, **kwargs):
""" Add a new Twitter username to the autocompletion database. """
usr = self.dialog.get_user()
if usr == False:
return
# check if user exists.
# ToDo: in case we want to adapt this for other networks we'd need to refactor this check.
try:
data = self.session.twitter.get_user(screen_name=usr)
except TweepyException as e:
@@ -36,7 +52,8 @@ class autocompletionManage(object):
self.database.set_user(data.screen_name, data.name, 0)
self.update_list()
def remove_user(self, ev):
def remove_user(self, *args, **kwargs):
""" Remove focused user from the autocompletion database. """
if commonMessageDialogs.delete_user_from_db() == widgetUtils.YES:
item = self.dialog.users.get_selected()
user = self.users[item]

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
""" Scanning code for autocompletion feature on TWBlue. This module can retrieve user objects from the selected Twitter account automatically. """
import time
import wx
import widgetUtils
import output
from tweepy.cursor import Cursor
from tweepy.errors import TweepyException
from pubsub import pub
from . import wx_scan
from . import manage
from . import storage
class autocompletionScan(object):
def __init__(self, config, buffer, window):
""" Class constructor. This class will take care of scanning the selected Twitter account to populate the database with users automatically upon request.
:param config: Config for the session that will be scanned in search for users.
:type config: dict
:param buffer: home buffer for the focused session.
:type buffer: controller.buffers.twitter.base.baseBuffer
:param window: Main Window of TWBlue.
:type window:wx.Frame
"""
super(autocompletionScan, self).__init__()
self.config = config
self.buffer = buffer
self.window = window
def show_dialog(self):
self.dialog = wx_scan.autocompletionScanDialog()
self.dialog.set("friends", self.config["mysc"]["save_friends_in_autocompletion_db"])
self.dialog.set("followers", self.config["mysc"]["save_followers_in_autocompletion_db"])
if self.dialog.get_response() == widgetUtils.OK:
confirmation = wx_scan.confirm()
return confirmation
def prepare_progress_dialog(self):
self.progress_dialog = wx_scan.autocompletionScanProgressDialog()
# connect method to update progress dialog
pub.subscribe(self.on_update_progress, "on-update-progress")
self.progress_dialog.Show()
def on_update_progress(self, percent):
if percent > 100:
percent = 100
wx.CallAfter(self.progress_dialog.update, percent)
def scan(self):
""" Attempts to add all users selected by current user to the autocomplete database. """
ids = []
self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends")
self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers")
output.speak(_("Updating database... You can close this window now. A message will tell you when the process finishes."))
database = storage.storage(self.buffer.session.session_id)
percent = 0
# Retrieve ids of all following users
if self.dialog.get("friends") == True:
for i in Cursor(self.buffer.session.twitter.get_friend_ids, count=5000).items():
if str(i) not in ids:
ids.append(str(i))
# same step, but for followers.
if self.dialog.get("followers") == True:
try:
for i in Cursor(self.buffer.session.twitter.get_follower_ids, count=5000).items():
if str(i) not in ids:
ids.append(str(i))
except TweepyException:
wx.CallAfter(wx_scan.show_error)
return self.done()
# As next step requires batches of 100s users, let's split our user Ids so we won't break the param rules.
split_users = [ids[i:i + 100] for i in range(0, len(ids), 100)]
# store returned users in this list.
users = []
for z in split_users:
if len(z) == 0:
continue
try:
results = self.buffer.session.twitter.lookup_users(user_id=z)
except TweepyException:
wx.CallAfter(wx_scan.show_error)
return self.done()
users.extend(results)
time.sleep(1)
percent = percent + (100/len(split_users))
pub.sendMessage("on-update-progress", percent=percent)
for user in users:
database.set_user(user.screen_name, user.name, 1)
wx.CallAfter(wx_scan.show_success, len(users))
self.done()
def done(self):
wx.CallAfter(self.progress_dialog.Destroy)
wx.CallAfter(self.dialog.Destroy)
pub.unsubscribe(self.on_update_progress, "on-update-progress")
def execute_at_startup(window, buffer, config):
database = storage.storage(buffer.session.session_id)
if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True:
buffer = window.search_buffer("followers", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 1)
else:
database.remove_by_buffer(1)
if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True:
buffer = window.search_buffer("friends", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 2)
else:
database.remove_by_buffer(2)

View File

@@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
import widgetUtils
import output
from . import wx_settings
from . import manage
from . import storage
from mysc.thread_utils import call_threaded
class autocompletionSettings(object):
def __init__(self, config, buffer, window):
super(autocompletionSettings, self).__init__()
self.config = config
self.buffer = buffer
self.window = window
self.dialog = wx_settings.autocompletionSettingsDialog()
self.dialog.set("friends_buffer", self.config["mysc"]["save_friends_in_autocompletion_db"])
self.dialog.set("followers_buffer", self.config["mysc"]["save_followers_in_autocompletion_db"])
widgetUtils.connect_event(self.dialog.viewList, widgetUtils.BUTTON_PRESSED, self.view_list)
if self.dialog.get_response() == widgetUtils.OK:
call_threaded(self.add_users_to_database)
def add_users_to_database(self):
self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer")
self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer")
output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes."))
database = storage.storage(self.buffer.session.session_id)
if self.dialog.get("followers_buffer") == True:
buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 1)
else:
database.remove_by_buffer(1)
if self.dialog.get("friends_buffer") == True:
buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 2)
else:
database.remove_by_buffer(2)
wx_settings.show_success_dialog()
self.dialog.destroy()
def view_list(self, ev):
q = manage.autocompletionManage(self.buffer.session)
def execute_at_startup(window, buffer, config):
database = storage.storage(buffer.session.session_id)
if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True:
buffer = window.search_buffer("followers", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 1)
else:
database.remove_by_buffer(1)
if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True:
buffer = window.search_buffer("friends", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 2)
else:
database.remove_by_buffer(2)

View File

@@ -11,7 +11,7 @@ class menu(wx.Menu):
def append_options(self, options):
for i in options:
item = wx.MenuItem(self, wx.ID_ANY, "%s (@%s)" % (i[1], i[0]))
self.AppendItem(item)
self.Append(item)
self.Bind(wx.EVT_MENU, lambda evt, temp=i[0]: self.select_text(evt, temp), item)
def select_text(self, ev, text):

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
import application
class autocompletionScanDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionScanDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.followers = wx.CheckBox(panel, -1, _("Add followers to database"))
self.friends = wx.CheckBox(panel, -1, _("Add friends to database"))
sizer.Add(self.followers, 0, wx.ALL, 5)
sizer.Add(self.friends, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
class autocompletionScanProgressDialog(widgetUtils.BaseDialog):
def __init__(self, *args, **kwargs):
super(autocompletionScanProgressDialog, self).__init__(parent=None, id=wx.ID_ANY, title=_("Updating autocompletion database"), *args, **kwargs)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.progress_bar = wx.Gauge(parent=panel)
sizer.Add(self.progress_bar)
panel.SetSizerAndFit(sizer)
def update(self, percent):
self.progress_bar.SetValue(percent)
def confirm():
with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. If this process ends with no error, you will be redirected back to the account settings dialog. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result:
if result.ShowModal() == wx.ID_YES:
return True
return False
def show_success(users):
with wx.MessageDialog(None, _("TWBlue has imported {} users successfully.").format(users), _("Done")) as dlg:
dlg.ShowModal()
def show_error():
with wx.MessageDialog(None, _("Error adding users from Twitter. Please try again in about 15 minutes."), _("Error"), style=wx.ICON_ERROR) as dlg:
dlg.ShowModal()

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
import application
class autocompletionSettingsDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionSettingsDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.followers_buffer = wx.CheckBox(panel, -1, _(u"Add users from followers buffer"))
self.friends_buffer = wx.CheckBox(panel, -1, _(u"Add users from friends buffer"))
sizer.Add(self.followers_buffer, 0, wx.ALL, 5)
sizer.Add(self.friends_buffer, 0, wx.ALL, 5)
self.viewList = wx.Button(panel, -1, _(u"Manage database..."))
sizer.Add(self.viewList, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def show_success_dialog():
wx.MessageDialog(None, _(u"{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK).ShowModal()

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
from __future__ import unicode_literals
categories = ["General"]
reproducibilities = ["always", "sometimes", "random", "have not tried", "unable to duplicate"]
severities = ["block", "crash", "major", "minor", "tweak", "text", "trivial", "feature"]

View File

@@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
from __future__ import unicode_literals
from builtins import object
import keys
import wx
import wx_ui
import widgetUtils
import application
from suds.client import Client
import constants
class reportBug(object):
def __init__(self, user_name):
self.user_name = user_name
self.categories = [_(u"General")]
self.reproducibilities = [_(u"always"), _(u"sometimes"), _(u"random"), _(u"have not tried"), _(u"unable to duplicate")]
self.severities = [_(u"block"), _(u"crash"), _(u"major"), _(u"minor"), _(u"tweak"), _(u"text"), _(u"trivial"), _(u"feature")]
self.dialog = wx_ui.reportBugDialog(self.categories, self.reproducibilities, self.severities)
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
self.dialog.get_response()
def send(self, *args, **kwargs):
if self.dialog.get("summary") == "" or self.dialog.get("description") == "":
self.dialog.no_filled()
return
if self.dialog.get("agree") == False:
self.dialog.no_checkbox()
return
try:
client = Client(application.report_bugs_url)
issue = client.factory.create('IssueData')
issue.project.name = application.name
issue.project.id = 0
issue.summary = self.dialog.get("summary"),
issue.description = "Reported by @%s on version %s (snapshot = %s)\n\n" % (self.user_name, application.version, application.snapshot) + self.dialog.get("description")
# to do: Create getters for category, severity and reproducibility in wx_UI.
issue.category = constants.categories[self.dialog.category.GetSelection()]
issue.reproducibility.name = constants.reproducibilities[self.dialog.reproducibility.GetSelection()]
issue.severity.name = constants.severities[self.dialog.severity.GetSelection()]
issue.priority.name = "normal"
issue.view_state.name = "public"
issue.resolution.name = "open"
issue.projection.name = "none"
issue.eta.name = "eta"
issue.status.name = "new"
id = client.service.mc_issue_add(keys.keyring.get("bts_user"), keys.keyring.get("bts_password"), issue)
self.dialog.success(id)
except:
self.dialog.error()

View File

@@ -1,95 +0,0 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
from __future__ import unicode_literals
import wx
import widgetUtils
import application
class reportBugDialog(widgetUtils.BaseDialog):
def __init__(self, categories, reproducibilities, severities):
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
self.SetTitle(_(u"Report an error"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
categoryLabel = wx.StaticText(panel, -1, _(u"Select a category"), size=wx.DefaultSize)
self.category = wx.ComboBox(panel, -1, choices=categories, style=wx.CB_READONLY)
self.category.SetSelection(0)
categoryB = wx.BoxSizer(wx.HORIZONTAL)
categoryB.Add(categoryLabel, 0, wx.ALL, 5)
categoryB.Add(self.category, 0, wx.ALL, 5)
self.category.SetFocus()
sizer.Add(categoryB, 0, wx.ALL, 5)
summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize)
self.summary = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.summary)
dc.SetFont(self.summary.GetFont())
self.summary.SetSize(dc.GetTextExtent("a"*80))
summaryB = wx.BoxSizer(wx.HORIZONTAL)
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
summaryB.Add(self.summary, 0, wx.ALL, 5)
sizer.Add(summaryB, 0, wx.ALL, 5)
descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize)
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
dc = wx.WindowDC(self.description)
dc.SetFont(self.description.GetFont())
(x, y, z) = dc.GetMultiLineTextExtent("0"*2000)
self.description.SetSize((x, y))
descBox = wx.BoxSizer(wx.HORIZONTAL)
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
descBox.Add(self.description, 0, wx.ALL, 5)
sizer.Add(descBox, 0, wx.ALL, 5)
reproducibilityLabel = wx.StaticText(panel, -1, _(u"how often does this bug happen?"), size=wx.DefaultSize)
self.reproducibility = wx.ComboBox(panel, -1, choices=reproducibilities, style=wx.CB_READONLY)
self.reproducibility.SetSelection(3)
reprB = wx.BoxSizer(wx.HORIZONTAL)
reprB.Add(reproducibilityLabel, 0, wx.ALL, 5)
reprB.Add(self.reproducibility, 0, wx.ALL, 5)
sizer.Add(reprB, 0, wx.ALL, 5)
severityLabel = wx.StaticText(panel, -1, _(u"Select the importance that you think this bug has"))
self.severity = wx.ComboBox(panel, -1, choices=severities, style=wx.CB_READONLY)
self.severity.SetSelection(3)
severityB = wx.BoxSizer(wx.HORIZONTAL)
severityB.Add(severityLabel, 0, wx.ALL, 5)
severityB.Add(self.severity, 0, wx.ALL, 5)
sizer.Add(severityB, 0, wx.ALL, 5)
self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my Twitter username to contact me and fix the bug quickly").format(application.name,))
self.agree.SetValue(False)
sizer.Add(self.agree, 0, wx.ALL, 5)
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
self.ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ok, 0, wx.ALL, 5)
btnBox.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def no_filled(self):
wx.MessageDialog(self, _(u"You must fill out both fields"), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
def no_checkbox(self):
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your twitter username to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
def success(self, id):
wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal()
self.EndModal(wx.ID_OK)
def error(self):
wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal()
self.EndModal(wx.ID_CANCEL)

View File

@@ -55,4 +55,5 @@ 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="")
add_alias=string(default="")
find = string(default="control+win+{")

View File

@@ -55,4 +55,5 @@ 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="")
add_alias=string(default="")
find = string(default="control+win+{")

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
""" A cross platform notification system.
Under Linux, the wx.NotificationMessage does not show a notification on the taskbar, so we decided to use dbus for showing notifications for linux and wx for Windows."""
from __future__ import absolute_import
from __future__ import unicode_literals
import platform
notify = None
def setup():
global notify
if platform.system() == "Windows":
from . import windows
notify = windows.notification()
elif platform.system() == "Linux":
from . import linux
notify = linux.notification()
def send(title, text):
global notify
if not notify or notify is None:
setup()
notify.notify(title, text)

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import object
import dbus
import application
class notifications(object):
"""Supports notifications on Linux.
"""
def __init__(self):
super(notifications, self).__init__()
self.item = "org.freedesktop.Notifications"
self.path = "/org/freedesktop/Notifications"
self.interface = "org.freedesktop.Notifications"
self.app_name = application.name
self.id_num_to_replace = 0
self.icon = "/usr/share/icons/Tango/32x32/status/sunny.png"
def notify(self, title="", text=""):
actions_list = ''
hint = ''
time = 5000 # Use seconds x 1000
bus = dbus.SessionBus()
notif = bus.get_object(self.item, self.path)
notify = dbus.Interface(notif, self.interface)
notify.Notify(self.app_name, self.id_num_to_replace, self.icon, title, text, actions_list, hint, time)

View File

@@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import object
import wx
class notification(object):
def notify(self, title, text):
wx.NotificationMessage(title, text).Show()

View File

@@ -5,6 +5,7 @@ import time
import logging
import webbrowser
import wx
import demoji
import config
import output
import application
@@ -472,11 +473,16 @@ class Session(base.baseSession):
aliases = self.settings.get("user-aliases")
if aliases == None:
log.error("Aliases are not defined for this config spec.")
return user.name
return self.demoji_user(user.name)
user_alias = aliases.get(user.id_str)
if user_alias != None:
return user_alias
return user.name
return self.demoji_user(user.name)
def demoji_user(self, name):
if self.settings["general"]["hide_emojis"] == True:
return demoji.replace(name, "")
return name
def get_user_by_screen_name(self, screen_name):
""" Returns an user identifier associated with a screen_name.
@@ -570,11 +576,13 @@ class Session(base.baseSession):
# the Streaming API sends non-extended tweets with an optional parameter "extended_tweets" which contains full_text and other data.
# so we have to make sure we check it before processing the normal status.
# As usual, we handle also quotes and retweets at first.
if hasattr(status, "retweeted_status") and hasattr(status.retweeted_status, "quoted_status") and status.retweeted_status.quoted_status.truncated:
status.retweeted_status.quoted_status._json = {**status.retweeted_status.quoted_status._json, **status.retweeted_status.quoted_status._json["extended_tweet"]}
if hasattr(status, "retweeted_status") and hasattr(status.retweeted_status, "extended_tweet"):
status.retweeted_status._json = {**status.retweeted_status._json, **status.retweeted_status._json["extended_tweet"]}
# compose.compose_tweet requires the parent tweet to have a full_text field, so we have to add it to retweets here.
status._json["full_text"] = status._json["text"]
if hasattr(status, "quoted_status") and hasattr(status.quoted_status, "extended_tweet"):
elif hasattr(status, "quoted_status") and hasattr(status.quoted_status, "extended_tweet"):
status.quoted_status._json = {**status.quoted_status._json, **status.quoted_status._json["extended_tweet"]}
if status.truncated:
status._json = {**status._json, **status._json["extended_tweet"]}
@@ -595,7 +603,7 @@ class Session(base.baseSession):
num = self.order_buffer(buffer, [status])
if num == 0:
buffers_to_send.remove(buffer)
# However, we have to do the "reduce and change" process here because the status we sent to the db is going to be a different object that the one sent to database.
# However, we have to do the "reduce and change" process here because the status we sent to the db is going to be a different object that the one sent to controller.
status = reduce.reduce_tweet(status)
status = self.check_quoted_status(status)
status = self.check_long_tweet(status)

View File

@@ -36,7 +36,7 @@ def process_text(tweet):
# Replace URLS for extended version of those.
if hasattr(tweet, "entities"):
text = utils.expand_urls(text, tweet.entities)
text = re.sub(r"https://twitter.com/\w+/status/\d+", "", text)
text = re.sub(r"https://twitter.com/\w+/status/\S+", "", text)
return text
def process_image_descriptions(entities):
@@ -114,7 +114,7 @@ def render_dm(dm, template, session, relative_times=False, offset_seconds=0):
"""
global dm_variables
available_data = dict()
available_data.update(text=utils.expand_urls(dm.message_create["message_data"]["text"], dm.message_create["message_data"]["entities"]))
available_data.update(text=utils.expand_urls(utils.StripChars(dm.message_create["message_data"]["text"]), dm.message_create["message_data"]["entities"]))
# Let's remove the last 3 digits in the timestamp string.
# Twitter sends their "epoch" timestamp with 3 digits for milliseconds and arrow doesn't like it.
original_date = arrow.get(int(dm.created_timestamp))

View File

@@ -99,8 +99,13 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog):
def __init__(self, parent):
super(generalAccount, self).__init__(parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.au = wx.Button(self, wx.ID_ANY, _(u"Autocompletion settings..."))
sizer.Add(self.au, 0, wx.ALL, 5)
userAutocompletionBox = wx.StaticBox(self, label=_("User autocompletion settings"))
self.userAutocompletionScan = wx.Button(self, wx.ID_ANY, _("Scan account and add friends and followers to the user autocompletion database"))
self.userAutocompletionManage = wx.Button(self, wx.ID_ANY, _("Manage autocompletion database"))
autocompletionSizer = wx.StaticBoxSizer(userAutocompletionBox, wx.HORIZONTAL)
autocompletionSizer.Add(self.userAutocompletionScan, 0, wx.ALL, 5)
autocompletionSizer.Add(self.userAutocompletionManage, 0, wx.ALL, 5)
sizer.Add(autocompletionSizer, 0, wx.ALL, 5)
self.relative_time = wx.CheckBox(self, wx.ID_ANY, _(U"Relative timestamps"))
sizer.Add(self.relative_time, 0, wx.ALL, 5)
tweetsPerCallBox = wx.BoxSizer(wx.HORIZONTAL)
@@ -120,6 +125,8 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog):
sizer.Add(rMode, 0, wx.ALL, 5)
self.show_screen_names = wx.CheckBox(self, wx.ID_ANY, _(U"Show screen names instead of full names"))
sizer.Add(self.show_screen_names, 0, wx.ALL, 5)
self.hide_emojis = wx.CheckBox(self, wx.ID_ANY, _("hide emojis in usernames"))
sizer.Add(self.hide_emojis, 0, wx.ALL, 5)
PersistSizeLabel = wx.StaticText(self, -1, _(u"Number of items per buffer to cache in database (0 to disable caching, blank for unlimited)"))
self.persist_size = wx.TextCtrl(self, -1)
sizer.Add(PersistSizeLabel, 0, wx.ALL, 5)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{"current_version": "2021.02.23",
"description": "Snapshot version.",
{"current_version": "2022.8.28",
"description": "Added support for creating threads, upload videos and polls to Twitter.",
"date": "unknown",
"downloads":
{"Windows32": "https://twblue.es/pubs/twblue_snapshot_x86.zip",
{"Windows64": "https://twblue.es/pubs/twblue_snapshot_x64.zip"
{"Windows32": "https://twblue.es/pubs/twblue_x86.zip",
"Windows64": "https://twblue.es/pubs/twblue_x64.zip"}
}