mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2025-08-26 18:09:21 +00:00
Compare commits
86 Commits
new-docume
...
v2023.2.3
Author | SHA1 | Date | |
---|---|---|---|
7d7a9d72c4 | |||
fcfbae4964 | |||
aca51a2fb9 | |||
b4ea6ffcbe | |||
f69af7aaa1 | |||
a8c5fc8589 | |||
f87ced817f | |||
3be01013f4 | |||
fd176f92d3 | |||
97d4fea563 | |||
b35f2e0fed | |||
b3851cde95 | |||
4b232d527c | |||
12b4c8ac23 | |||
d89c5150f8 | |||
d17e9ecdac | |||
76d0866780 | |||
![]() |
706616717e | ||
7c47d6171a | |||
![]() |
b743d7af09 | ||
d4219f1705 | |||
![]() |
e25d007149 | ||
![]() |
fcb8edbda2 | ||
![]() |
a6ca588115 | ||
18a7a42b5a | |||
460cea702b | |||
b14c77b730 | |||
1e5c7512e4 | |||
d76dbe318c | |||
![]() |
4d4901b029 | ||
![]() |
07128d2e4a | ||
c45ba5e705 | |||
![]() |
795cb33efc | ||
98bcb9d279 | |||
![]() |
06cbe0a3b5 | ||
e4f2793aaf | |||
43ae43ce26 | |||
![]() |
7082a5f3ec | ||
c278fba4c7 | |||
cfc8221825 | |||
![]() |
32c1ed225e | ||
![]() |
d3b15fcefa | ||
7ec96c47d6 | |||
97812ec8b0 | |||
d1ca3c9fb2 | |||
![]() |
492352bc27 | ||
![]() |
6328c252f7 | ||
![]() |
e1a46f338a | ||
![]() |
f7e09a05b2 | ||
![]() |
c7116916ba | ||
![]() |
f1fbe858e9 | ||
![]() |
fc5a1060be | ||
![]() |
5951276033 | ||
![]() |
2d761c423f | ||
![]() |
434e2878a7 | ||
![]() |
89cdba5910 | ||
![]() |
e9a885784f | ||
![]() |
3a968e49aa | ||
![]() |
c6417962a9 | ||
![]() |
2f55eca575 | ||
![]() |
1cd66e7f10 | ||
![]() |
0ddb4e6f32 | ||
![]() |
39aac0a3e7 | ||
![]() |
45ab6d953b | ||
![]() |
fdd0d566ad | ||
![]() |
c606fedda5 | ||
9b0ecdf928 | |||
![]() |
f5e1ff39be | ||
![]() |
5dff35fd02 | ||
![]() |
a5104fd76a | ||
![]() |
423b63e486 | ||
![]() |
25c6db7dd8 | ||
ca40103df7 | |||
250b248d25 | |||
efd11b90fb | |||
2a90e7be25 | |||
32a86f1bb4 | |||
b0fa59cc01 | |||
b8647c29ea | |||
1eb9aefbf1 | |||
d4ebfac317 | |||
3680349b59 | |||
ec68c7ccae | |||
4bf155b421 | |||
e63479a261 | |||
ae0dcc7b21 |
@@ -120,7 +120,7 @@ twblueWin7:
|
|||||||
- cd ..
|
- cd ..
|
||||||
- move src/twblue.zip artifacts/twblue_windows7_x86.zip
|
- move src/twblue.zip artifacts/twblue_windows7_x86.zip
|
||||||
only:
|
only:
|
||||||
- next-gen
|
- tags
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- artifacts
|
- artifacts
|
||||||
|
40
README.md
40
README.md
@@ -1,21 +1,11 @@
|
|||||||
TWBlue -
|
TWBlue
|
||||||
======
|
======
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/manuelcortez/twblue)
|
[](https://ci.appveyor.com/project/manuelcortez/twblue)
|
||||||
|
|
||||||
TW Blue is an app designed to use Twitter simply and efficiently while using minimal system resources.
|
TWBlue is a free and open source application that allows you to interact with the main features of Twitter and mastodon from the comfort of a windows software, with 2 different interfaces specially designed for screen reader users.
|
||||||
With this app you’ll have access to twitter features such as:
|
|
||||||
|
|
||||||
* Create, reply to, like, retweet and delete tweets,
|
See [TWBlue's webpage](https://twblue.es) for more details.
|
||||||
* Send and delete direct messages,
|
|
||||||
* See your friends and followers,
|
|
||||||
* Follow, unfollow, block and report users as spam,
|
|
||||||
* Open a user’s timeline, which will allow you to get that user’s tweets separately,
|
|
||||||
* Open URLs when attached to a tweet or direct message,
|
|
||||||
* Play audio tweets
|
|
||||||
* and more!
|
|
||||||
|
|
||||||
See [TWBlue's webpage](http://twblue.es) for more details.
|
|
||||||
|
|
||||||
## Running TWBlue from source
|
## Running TWBlue from source
|
||||||
|
|
||||||
@@ -83,15 +73,7 @@ This dependency has been built using pure basic 4.61. Its source can be found at
|
|||||||
|
|
||||||
* [NSIS,](http://nsis.sourceforge.net/) version 3.04
|
* [NSIS,](http://nsis.sourceforge.net/) version 3.04
|
||||||
|
|
||||||
#### Dependencies required to build the portableApps.com format archive
|
#### Dependencies to make the spell checker multilingual
|
||||||
|
|
||||||
* [NSIS Portable,](http://portableapps.com/apps/development/nsis_portable) version 3.03
|
|
||||||
* [PortableApps.com Launcher,](http://portableapps.com/apps/development/portableapps.com_launcher) version 2.2.1
|
|
||||||
* [PortableApps.com Installer,](http://portableapps.com/apps/development/portableapps.com_installer) version 3.5.11
|
|
||||||
|
|
||||||
Important! Install these 3 apps into the same folder, otherwise you won't be able to build the pa.c version. For example: D:\portableApps\NSISPortable, D:\PortableApps\PortableApps.com installer, ...
|
|
||||||
|
|
||||||
#### Dependencies to make the spell checker multilingual ####
|
|
||||||
|
|
||||||
In order to add the support for spell checking in more languages than english you need to add some additional dictionaries to pyenchant. These are located on the dictionaries folder under windows-dependencies. Simply copy them to the share/enchant/myspell folder located in your enchant installation. They will be automatically copied when building a binary version.
|
In order to add the support for spell checking in more languages than english you need to add some additional dictionaries to pyenchant. These are located on the dictionaries folder under windows-dependencies. Simply copy them to the share/enchant/myspell folder located in your enchant installation. They will be automatically copied when building a binary version.
|
||||||
|
|
||||||
@@ -136,16 +118,8 @@ If you want to install TWBlue on your computer, you must create the installer fi
|
|||||||
|
|
||||||
### How to generate a translation template
|
### How to generate a translation template
|
||||||
|
|
||||||
Run the gen_pot.bat file, located in the tools directory. Your python installation must be in your path environment variable. The pot file will appear in the tools directory.
|
To manage translations in TWBlue, you can install the [Babel package.](https://pypi.org/project/Babel/) You can extract message catalogs and generate the main template file with the following command:
|
||||||
|
|
||||||
### How to build the portableApps.com archive
|
pybabel extract -o twblue.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "MCV software" --input-dirs ..\src
|
||||||
|
|
||||||
If you want to have TWBlue on your PortableApps.com platform, follow these steps:
|
|
||||||
|
|
||||||
* Navigate to the src directory, and create a binary version for x86: C:\python37\python setup.py build
|
|
||||||
* Move the dist directory to the misc\pa.c format\app folder in this repo, and rename it to twblue
|
|
||||||
* Repeat these steps with Python for x64: C:\python37x64\python setup.py build
|
|
||||||
* Move the new dist directory to the misc\pa.c format\app folder, and rename it to twblue64
|
|
||||||
* Run the PortableApps.com Launcher Generator, and follow the wizard. Choose the pa.c format folder and continue to generate the launcher. If the wizard is completed, you will see a file named TWBlue portable.exe inside the pa.c format folder.
|
|
||||||
* Run the PortableApps.com Installer, and follow the wizard. As in the above step, choose the pa.c format folder. When it completes, you will see a file named TWBluePortable_x.y.paf.exe inside the misc folder, where x.y is the version number.
|
|
||||||
|
|
||||||
|
Take into account, though, that we use [weblate](https://weblate.mcvsoftware.com) to track translation work for TWBlue. If you wish to be part of our translation team, please open an issue so we can create an account for you in Weblate.
|
@@ -2,6 +2,26 @@ TWBlue Changelog
|
|||||||
|
|
||||||
## changes in this version
|
## changes in this version
|
||||||
|
|
||||||
|
In this version, TWBlue will no longer support Twitter sessions starting on February 9, due to Twitter's policies prohibiting third-party clients, in addition to the shutdown of the free access to the Twitter API. All Twitter sessions that are active on TWBlue will stop working as of February 9, when the free API access will finally be shut down. It will not be possible to display or add Twitter sessions from the Session manager. From the TWBlue team, we will continue working to improve our support for Mastodon instances and add other social networks in the near future. If you want to keep in touch with the project, you can follow us in our mastodon account, at [@twblue@maaw.social.](https://maaw.social/@twblue)
|
||||||
|
|
||||||
|
* In the graphical interface, TWBlue will update menu items, in the menu bar, depending on whether you are focusing a Twitter or Mastodon session. This makes it possible for TWBlue to display the correct terms in each social network. Take into account that there might be unavailable items for the currently active session.
|
||||||
|
* in the keystroke editor for the invisible interface, TWBlue displays the available shortcuts for the currently active session. Descriptions of those keystrokes are also different for Twitter and mastodon sessions to use correct terms for both networks.
|
||||||
|
* In the invisible interface, TWBlue will skip sessions that have not been started when using the keyboard shortcut to switch between different accounts.
|
||||||
|
* Fixed a bug when deleting a session in the session manager dialog. Sessions can now be deleted correctly.
|
||||||
|
* Mastodon:
|
||||||
|
* Added basic support to notifications buffer. This buffer shows mastodon notifications in real time. Every notification is attached to a kind of object (posts, users, relationships or polls). At the moment, the only supported action for notification is dismissing, which allows you to remove the notification from the buffer (take into account, though, that mention notifications will remove also the mention in its corresponding buffer, due to the way TWBlue reads mentions from mastodon instances).
|
||||||
|
* Fixed an issue that was preventing TWBlue to create more than one user timeline during startup.
|
||||||
|
* Fixed getting more items in mentions buffer. ([#508](https://github.com/mcv-software/twblue/issues/508))
|
||||||
|
* TWBlue will display properly new paragraphs in mastodon posts.
|
||||||
|
* In the session manager, Mastodon sessions are now displayed including the instance to avoid confusion.
|
||||||
|
* TWBlue will now read default visibility preferences when posting new statuses, and display sensitive content. These preferences can be set on the mastodon instance, in the account's preferences section. If you wish to change TWBlue's behavior and have it not read those preferences from your instance, but instead set the default public visibility and hide sensitive content, you can uncheck the Read preferences from instance checkbox in the account options.
|
||||||
|
* If a mastodon instance is not active or there are errors during login, TWBlue will report it in the log file and will continue with other sessions.
|
||||||
|
* When replying to someone in a public post, TWBlue will default to "unlisted" as its visibility setting. This is done so replies will not clutter local and federated timelines. This setting might be changed when writing the reply, though. ([#504,](https://github.com/MCV-Software/TWBlue/issues/504))
|
||||||
|
* TWBlue uses its own user agent in Mastodon sessions, so it will be easier to identify the client for instance admins.
|
||||||
|
* TWBlue will check if the streaming API endpoints are available before attempting to start Streaming for the current session. Before, TWBlue caused load issues in misconfigured mastodon instances where the streaming API were not available.
|
||||||
|
|
||||||
|
## Changes in version 2022.12.13
|
||||||
|
|
||||||
* per popular request, We will generate a 32-bit portable version of TWBlue available for Windows 7 operating systems. This version will not be supported in our automatic updater, so in case of using such version, you would need to download it manually every time there is a new update. TWBlue will continue to be available for Windows 7 as long as it is possible to build it using Python 3.7.
|
* per popular request, We will generate a 32-bit portable version of TWBlue available for Windows 7 operating systems. This version will not be supported in our automatic updater, so in case of using such version, you would need to download it manually every time there is a new update. TWBlue will continue to be available for Windows 7 as long as it is possible to build it using Python 3.7.
|
||||||
* Fixed a couple of bugs that were making TWBlue unable to be opened in some computers, related to our translator module and some COM objects handled incorrectly.
|
* Fixed a couple of bugs that were making TWBlue unable to be opened in some computers, related to our translator module and some COM objects handled incorrectly.
|
||||||
* Fixed an issue that was making TWBlue unable to open in certain computers due to errors related to Win32 API'S.
|
* Fixed an issue that was making TWBlue unable to open in certain computers due to errors related to Win32 API'S.
|
||||||
|
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
BIN
doc/locales/sr/LC_MESSAGES/twblue-changelog.mo
Normal file
BIN
doc/locales/sr/LC_MESSAGES/twblue-changelog.mo
Normal file
Binary file not shown.
1945
doc/locales/sr/LC_MESSAGES/twblue-changelog.po
Normal file
1945
doc/locales/sr/LC_MESSAGES/twblue-changelog.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,16 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# Make date check for feb 9.
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
end_of_twitter = datetime.datetime(2023, 2, 9)
|
||||||
|
twitter_support_enabled = True
|
||||||
|
if now >= end_of_twitter:
|
||||||
|
twitter_support_enabled = False
|
||||||
name = 'TWBlue'
|
name = 'TWBlue'
|
||||||
short_name='twblue'
|
short_name='twblue'
|
||||||
update_url = 'https://twblue.es/updates/updates.php'
|
update_url = 'https://twblue.es/updates/updates.php'
|
||||||
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/updates.json'
|
mirror_update_url = 'https://raw.githubusercontent.com/mcv-software/TWBlue/next-gen/updates/updates.json'
|
||||||
authors = ["Manuel Cortéz", "José Manuel Delicado"]
|
authors = ["Manuel Cortéz", "José Manuel Delicado"]
|
||||||
authorEmail = "manuel@manuelcortez.net"
|
authorEmail = "manuel@manuelcortez.net"
|
||||||
copyright = "Copyright (C) 2013-2022, MCV Software."
|
copyright = "Copyright (C) 2013-2022, MCV Software."
|
||||||
|
@@ -2,4 +2,5 @@
|
|||||||
from .base import BaseBuffer
|
from .base import BaseBuffer
|
||||||
from .mentions import MentionsBuffer
|
from .mentions import MentionsBuffer
|
||||||
from .conversations import ConversationBuffer, ConversationListBuffer
|
from .conversations import ConversationBuffer, ConversationListBuffer
|
||||||
from .users import UserBuffer
|
from .users import UserBuffer
|
||||||
|
from .notifications import NotificationsBuffer
|
@@ -9,6 +9,7 @@ import config
|
|||||||
import sound
|
import sound
|
||||||
import languageHandler
|
import languageHandler
|
||||||
import logging
|
import logging
|
||||||
|
from mastodon import MastodonNotFoundError
|
||||||
from audio_services import youtube_utils
|
from audio_services import youtube_utils
|
||||||
from controller.buffers.base import base
|
from controller.buffers.base import base
|
||||||
from controller.mastodon import messages
|
from controller.mastodon import messages
|
||||||
@@ -72,14 +73,22 @@ class BaseBuffer(base.Buffer):
|
|||||||
post.message.destroy()
|
post.message.destroy()
|
||||||
|
|
||||||
def get_formatted_message(self):
|
def get_formatted_message(self):
|
||||||
return self.compose_function(self.get_item(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])[1]
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
|
return self.compose_function(self.get_item(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)[1]
|
||||||
|
|
||||||
def get_message(self):
|
def get_message(self):
|
||||||
post = self.get_item()
|
post = self.get_item()
|
||||||
if post == None:
|
if post == None:
|
||||||
return
|
return
|
||||||
template = self.session.settings["templates"]["post"]
|
template = self.session.settings["templates"]["post"]
|
||||||
|
# If template is set to hide sensitive media by default, let's change it according to user preferences.
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"] == True:
|
||||||
|
if self.session.expand_spoilers == True and "$safe_text" in template:
|
||||||
|
template = template.replace("$safe_text", "$text")
|
||||||
|
elif self.session.expand_spoilers == False and "$text" in template:
|
||||||
|
template = template.replace("$text", "$safe_text")
|
||||||
t = templates.render_post(post, template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
t = templates.render_post(post, template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@@ -124,7 +133,10 @@ class BaseBuffer(base.Buffer):
|
|||||||
else:
|
else:
|
||||||
post = self.session.db[self.name][0]
|
post = self.session.db[self.name][0]
|
||||||
output.speak(_("New post in {0}").format(self.get_buffer_name()))
|
output.speak(_("New post in {0}").format(self.get_buffer_name()))
|
||||||
output.speak(" ".join(self.compose_function(post, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])))
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
|
output.speak(" ".join(self.compose_function(post, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)))
|
||||||
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(_("{0} new posts in {1}.").format(number_of_items, self.get_buffer_name()))
|
output.speak(_("{0} new posts in {1}.").format(number_of_items, self.get_buffer_name()))
|
||||||
|
|
||||||
@@ -150,13 +162,16 @@ class BaseBuffer(base.Buffer):
|
|||||||
self.session.db[self.name] = items_db
|
self.session.db[self.name] = items_db
|
||||||
selection = self.buffer.list.get_selected()
|
selection = self.buffer.list.get_selected()
|
||||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||||
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||||
for i in elements:
|
for i in elements:
|
||||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.insert_item(True, *post)
|
self.buffer.list.insert_item(True, *post)
|
||||||
else:
|
else:
|
||||||
for i in elements:
|
for i in elements:
|
||||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.insert_item(False, *post)
|
self.buffer.list.insert_item(False, *post)
|
||||||
self.buffer.list.select_item(selection)
|
self.buffer.list.select_item(selection)
|
||||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||||
@@ -185,27 +200,33 @@ class BaseBuffer(base.Buffer):
|
|||||||
if number_of_items == 0 and self.session.settings["general"]["persist_size"] == 0: return
|
if number_of_items == 0 and self.session.settings["general"]["persist_size"] == 0: return
|
||||||
log.debug("The list contains %d items " % (self.buffer.list.get_count(),))
|
log.debug("The list contains %d items " % (self.buffer.list.get_count(),))
|
||||||
log.debug("Putting %d items on the list" % (number_of_items,))
|
log.debug("Putting %d items on the list" % (number_of_items,))
|
||||||
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
if self.buffer.list.get_count() == 0:
|
if self.buffer.list.get_count() == 0:
|
||||||
for i in list_to_use:
|
for i in list_to_use:
|
||||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.insert_item(False, *post)
|
self.buffer.list.insert_item(False, *post)
|
||||||
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
|
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
|
||||||
elif self.buffer.list.get_count() > 0 and number_of_items > 0:
|
elif self.buffer.list.get_count() > 0 and number_of_items > 0:
|
||||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||||
items = list_to_use[len(list_to_use)-number_of_items:]
|
items = list_to_use[len(list_to_use)-number_of_items:]
|
||||||
for i in items:
|
for i in items:
|
||||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.insert_item(False, *post)
|
self.buffer.list.insert_item(False, *post)
|
||||||
else:
|
else:
|
||||||
items = list_to_use[0:number_of_items]
|
items = list_to_use[0:number_of_items]
|
||||||
items.reverse()
|
items.reverse()
|
||||||
for i in items:
|
for i in items:
|
||||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.insert_item(True, *post)
|
self.buffer.list.insert_item(True, *post)
|
||||||
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
|
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
|
||||||
|
|
||||||
def add_new_item(self, item):
|
def add_new_item(self, item):
|
||||||
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
|
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||||
self.buffer.list.insert_item(False, *post)
|
self.buffer.list.insert_item(False, *post)
|
||||||
else:
|
else:
|
||||||
@@ -214,7 +235,10 @@ class BaseBuffer(base.Buffer):
|
|||||||
output.speak(" ".join(post[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
|
output.speak(" ".join(post[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
|
||||||
|
|
||||||
def update_item(self, item, position):
|
def update_item(self, item, position):
|
||||||
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
|
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.list.SetItem(position, 1, post[1])
|
self.buffer.list.list.SetItem(position, 1, post[1])
|
||||||
|
|
||||||
def bind_events(self):
|
def bind_events(self):
|
||||||
@@ -298,6 +322,9 @@ class BaseBuffer(base.Buffer):
|
|||||||
else:
|
else:
|
||||||
title = _("Reply to {}").format(item.account.username)
|
title = _("Reply to {}").format(item.account.username)
|
||||||
caption = _("Write your reply here")
|
caption = _("Write your reply here")
|
||||||
|
# Set unlisted by default, so we will not clutter other user's buffers with replies.
|
||||||
|
# see https://github.com/MCV-Software/TWBlue/issues/504
|
||||||
|
visibility = "unlisted"
|
||||||
if item.reblog != None:
|
if item.reblog != None:
|
||||||
users = ["@{} ".format(user.acct) for user in item.reblog.mentions if user.id != self.session.db["user_id"]]
|
users = ["@{} ".format(user.acct) for user in item.reblog.mentions if user.id != self.session.db["user_id"]]
|
||||||
if item.reblog.account.acct != item.account.acct and "@{} ".format(item.reblog.account.acct) not in users:
|
if item.reblog.account.acct != item.account.acct and "@{} ".format(item.reblog.account.acct) not in users:
|
||||||
@@ -350,7 +377,7 @@ class BaseBuffer(base.Buffer):
|
|||||||
|
|
||||||
def share_item(self, *args, **kwargs):
|
def share_item(self, *args, **kwargs):
|
||||||
if self.can_share() == False:
|
if self.can_share() == False:
|
||||||
return output.speak(_("This action is not supported on conversation posts."))
|
return output.speak(_("This action is not supported on conversations."))
|
||||||
post = self.get_item()
|
post = self.get_item()
|
||||||
id = post.id
|
id = post.id
|
||||||
if self.session.settings["general"]["boost_mode"] == "ask":
|
if self.session.settings["general"]["boost_mode"] == "ask":
|
||||||
@@ -439,6 +466,7 @@ class BaseBuffer(base.Buffer):
|
|||||||
self.buffer.list.remove_item(index)
|
self.buffer.list.remove_item(index)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.session.sound.play("error.ogg")
|
self.session.sound.play("error.ogg")
|
||||||
|
log.exception("")
|
||||||
self.session.db[self.name] = items
|
self.session.db[self.name] = items
|
||||||
|
|
||||||
def user_details(self):
|
def user_details(self):
|
||||||
@@ -472,7 +500,11 @@ class BaseBuffer(base.Buffer):
|
|||||||
item = self.get_item()
|
item = self.get_item()
|
||||||
if item.reblog != None:
|
if item.reblog != None:
|
||||||
item = item.reblog
|
item = item.reblog
|
||||||
item = self.session.api.status(item.id)
|
try:
|
||||||
|
item = self.session.api.status(item.id)
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
output.speak(_("No status found with that ID"))
|
||||||
|
return
|
||||||
if item.favourited == False:
|
if item.favourited == False:
|
||||||
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
|
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
|
||||||
else:
|
else:
|
||||||
@@ -482,7 +514,11 @@ class BaseBuffer(base.Buffer):
|
|||||||
item = self.get_item()
|
item = self.get_item()
|
||||||
if item.reblog != None:
|
if item.reblog != None:
|
||||||
item = item.reblog
|
item = item.reblog
|
||||||
item = self.session.api.status(item.id)
|
try:
|
||||||
|
item = self.session.api.status(item.id)
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
output.speak(_("No status found with that ID"))
|
||||||
|
return
|
||||||
if item.bookmarked == False:
|
if item.bookmarked == False:
|
||||||
call_threaded(self.session.api_call, call_name="status_bookmark", preexec_message=_("Adding to bookmarks..."), _sound="favourite.ogg", id=item.id)
|
call_threaded(self.session.api_call, call_name="status_bookmark", preexec_message=_("Adding to bookmarks..."), _sound="favourite.ogg", id=item.id)
|
||||||
else:
|
else:
|
||||||
@@ -491,7 +527,11 @@ class BaseBuffer(base.Buffer):
|
|||||||
def view_item(self):
|
def view_item(self):
|
||||||
post = self.get_item()
|
post = self.get_item()
|
||||||
# Update object so we can retrieve newer stats
|
# Update object so we can retrieve newer stats
|
||||||
post = self.session.api.status(id=post.id)
|
try:
|
||||||
|
post = self.session.api.status(id=post.id)
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
output.speak(_("No status found with that ID"))
|
||||||
|
return
|
||||||
# print(post)
|
# print(post)
|
||||||
msg = messages.viewPost(post, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url())
|
msg = messages.viewPost(post, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url())
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import logging
|
|||||||
import wx
|
import wx
|
||||||
import widgetUtils
|
import widgetUtils
|
||||||
import output
|
import output
|
||||||
|
from mastodon import MastodonNotFoundError
|
||||||
from controller.mastodon import messages
|
from controller.mastodon import messages
|
||||||
from controller.buffers.mastodon.base import BaseBuffer
|
from controller.buffers.mastodon.base import BaseBuffer
|
||||||
from mysc.thread_utils import call_threaded
|
from mysc.thread_utils import call_threaded
|
||||||
@@ -200,7 +201,11 @@ class ConversationBuffer(BaseBuffer):
|
|||||||
self.execution_time = current_time
|
self.execution_time = current_time
|
||||||
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
|
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
|
||||||
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||||
self.post = self.session.api.status(id=self.post.id)
|
try:
|
||||||
|
self.post = self.session.api.status(id=self.post.id)
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
output.speak(_("No status found with that ID"))
|
||||||
|
return
|
||||||
# toDo: Implement reverse timelines properly here.
|
# toDo: Implement reverse timelines properly here.
|
||||||
try:
|
try:
|
||||||
results = []
|
results = []
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import output
|
||||||
from controller.buffers.mastodon.base import BaseBuffer
|
from controller.buffers.mastodon.base import BaseBuffer
|
||||||
from sessions.mastodon import utils
|
from sessions.mastodon import utils
|
||||||
|
|
||||||
@@ -8,6 +9,11 @@ log = logging.getLogger("controller.buffers.mastodon.mentions")
|
|||||||
|
|
||||||
class MentionsBuffer(BaseBuffer):
|
class MentionsBuffer(BaseBuffer):
|
||||||
|
|
||||||
|
def get_item(self):
|
||||||
|
index = self.buffer.list.get_selected()
|
||||||
|
if index > -1 and self.session.db.get(self.name) != None and len(self.session.db[self.name]) > index:
|
||||||
|
return self.session.db[self.name][index]["status"]
|
||||||
|
|
||||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||||
@@ -17,13 +23,12 @@ class MentionsBuffer(BaseBuffer):
|
|||||||
count = self.session.settings["general"]["max_posts_per_call"]
|
count = self.session.settings["general"]["max_posts_per_call"]
|
||||||
min_id = None
|
min_id = None
|
||||||
try:
|
try:
|
||||||
items = getattr(self.session.api, self.function)(min_id=min_id, limit=count, exclude_types=["follow", "favourite", "reblog", "poll", "follow_request"], *self.args, **self.kwargs)
|
items = getattr(self.session.api, self.function)(min_id=min_id, limit=count, types=["mention"], *self.args, **self.kwargs)
|
||||||
items.reverse()
|
items.reverse()
|
||||||
results = [item.status for item in items if item.get("status") and item.type == "mention"]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Error %s" % (str(e)))
|
log.exception("Error %s" % (str(e)))
|
||||||
return
|
return
|
||||||
number_of_items = self.session.order_buffer(self.name, results)
|
number_of_items = self.session.order_buffer(self.name, items)
|
||||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||||
self.put_items_on_list(number_of_items)
|
self.put_items_on_list(number_of_items)
|
||||||
if number_of_items > 0 and self.name != "sent_posts" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
|
if number_of_items > 0 and self.name != "sent_posts" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
|
||||||
@@ -40,11 +45,11 @@ class MentionsBuffer(BaseBuffer):
|
|||||||
else:
|
else:
|
||||||
max_id = self.session.db[self.name][-1].id
|
max_id = self.session.db[self.name][-1].id
|
||||||
try:
|
try:
|
||||||
items = getattr(self.session.api, self.function)(max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], exclude_types=["follow", "favourite", "reblog", "poll", "follow_request"], *self.args, **self.kwargs)
|
items = getattr(self.session.api, self.function)(max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], types=["mention"], *self.args, **self.kwargs)
|
||||||
items = [item.status for item in items if item.get("status") and item.type == "mention"]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Error %s" % (str(e)))
|
log.exception("Error %s" % (str(e)))
|
||||||
return
|
return
|
||||||
|
print(items)
|
||||||
items_db = self.session.db[self.name]
|
items_db = self.session.db[self.name]
|
||||||
for i in items:
|
for i in items:
|
||||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||||
@@ -56,13 +61,55 @@ class MentionsBuffer(BaseBuffer):
|
|||||||
self.session.db[self.name] = items_db
|
self.session.db[self.name] = items_db
|
||||||
selection = self.buffer.list.get_selected()
|
selection = self.buffer.list.get_selected()
|
||||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||||
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||||
for i in elements:
|
for i in elements:
|
||||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
post = self.compose_function(i.status, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.insert_item(True, *post)
|
self.buffer.list.insert_item(True, *post)
|
||||||
else:
|
else:
|
||||||
for i in elements:
|
for i in elements:
|
||||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
post = self.compose_function(i.status, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
self.buffer.list.insert_item(False, *post)
|
self.buffer.list.insert_item(False, *post)
|
||||||
self.buffer.list.select_item(selection)
|
self.buffer.list.select_item(selection)
|
||||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||||
|
|
||||||
|
def put_items_on_list(self, number_of_items):
|
||||||
|
list_to_use = self.session.db[self.name]
|
||||||
|
if number_of_items == 0 and self.session.settings["general"]["persist_size"] == 0: return
|
||||||
|
log.debug("The list contains %d items " % (self.buffer.list.get_count(),))
|
||||||
|
log.debug("Putting %d items on the list" % (number_of_items,))
|
||||||
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
|
if self.buffer.list.get_count() == 0:
|
||||||
|
for i in list_to_use:
|
||||||
|
post = self.compose_function(i.status, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
|
self.buffer.list.insert_item(False, *post)
|
||||||
|
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
|
||||||
|
elif self.buffer.list.get_count() > 0 and number_of_items > 0:
|
||||||
|
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||||
|
items = list_to_use[len(list_to_use)-number_of_items:]
|
||||||
|
for i in items:
|
||||||
|
post = self.compose_function(i.status, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
|
self.buffer.list.insert_item(False, *post)
|
||||||
|
else:
|
||||||
|
items = list_to_use[0:number_of_items]
|
||||||
|
items.reverse()
|
||||||
|
for i in items:
|
||||||
|
post = self.compose_function(i.status, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
|
self.buffer.list.insert_item(True, *post)
|
||||||
|
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
|
||||||
|
|
||||||
|
def add_new_item(self, item):
|
||||||
|
safe = True
|
||||||
|
if self.session.settings["general"]["read_preferences_from_instance"]:
|
||||||
|
safe = self.session.expand_spoilers == False
|
||||||
|
post = self.compose_function(item.status, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
|
||||||
|
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||||
|
self.buffer.list.insert_item(False, *post)
|
||||||
|
else:
|
||||||
|
self.buffer.list.insert_item(True, *post)
|
||||||
|
if 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(" ".join(post[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
|
||||||
|
63
src/controller/buffers/mastodon/notifications.py
Normal file
63
src/controller/buffers/mastodon/notifications.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import widgetUtils
|
||||||
|
import output
|
||||||
|
from controller.buffers.mastodon.base import BaseBuffer
|
||||||
|
from sessions.mastodon import compose, templates
|
||||||
|
from wxUI import buffers
|
||||||
|
from wxUI.dialogs.mastodon import dialogs as mastodon_dialogs
|
||||||
|
|
||||||
|
log = logging.getLogger("controller.buffers.mastodon.notifications")
|
||||||
|
|
||||||
|
class NotificationsBuffer(BaseBuffer):
|
||||||
|
|
||||||
|
def get_message(self):
|
||||||
|
notification = self.get_item()
|
||||||
|
if notification == None:
|
||||||
|
return
|
||||||
|
template = self.session.settings["templates"]["notification"]
|
||||||
|
post_template = self.session.settings["templates"]["post"]
|
||||||
|
t = templates.render_notification(notification, template, post_template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
||||||
|
return t
|
||||||
|
|
||||||
|
def create_buffer(self, parent, name):
|
||||||
|
self.buffer = buffers.mastodon.notificationsPanel(parent, name)
|
||||||
|
|
||||||
|
def onFocus(self, *args, **kwargs):
|
||||||
|
item = self.get_item()
|
||||||
|
if self.session.settings["general"]["relative_times"] == True:
|
||||||
|
original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at)
|
||||||
|
ts = original_date.humanize(locale=languageHandler.getLanguage())
|
||||||
|
self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 1, ts)
|
||||||
|
|
||||||
|
def bind_events(self):
|
||||||
|
widgetUtils.connect_event(self.buffer.list.list, widgetUtils.KEYPRESS, self.get_event)
|
||||||
|
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.post)
|
||||||
|
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.destroy_status, self.buffer.dismiss)
|
||||||
|
|
||||||
|
def fav(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unfav(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def can_share(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def destroy_status(self, *args, **kwargs):
|
||||||
|
index = self.buffer.list.get_selected()
|
||||||
|
item = self.session.db[self.name][index]
|
||||||
|
answer = mastodon_dialogs.delete_notification_dialog()
|
||||||
|
if answer == False:
|
||||||
|
return
|
||||||
|
items = self.session.db[self.name]
|
||||||
|
try:
|
||||||
|
self.session.api.notifications_dismiss(id=item.id)
|
||||||
|
items.pop(index)
|
||||||
|
self.buffer.list.remove_item(index)
|
||||||
|
output.speak(_("Notification dismissed."))
|
||||||
|
except Exception as e:
|
||||||
|
self.session.sound.play("error.ogg")
|
||||||
|
log.exception("")
|
||||||
|
self.session.db[self.name] = items
|
@@ -152,7 +152,7 @@ class Controller(object):
|
|||||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.edit_keystrokes, menuitem=self.view.keystroke_editor)
|
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.edit_keystrokes, menuitem=self.view.keystroke_editor)
|
||||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.post_tweet, self.view.compose)
|
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.post_tweet, self.view.compose)
|
||||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.post_reply, self.view.reply)
|
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.post_reply, self.view.reply)
|
||||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.post_retweet, self.view.retweet)
|
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.post_retweet, self.view.share)
|
||||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_to_favourites, self.view.fav)
|
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_to_favourites, self.view.fav)
|
||||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_from_favourites, self.view.unfav)
|
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_from_favourites, self.view.unfav)
|
||||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_item, self.view.view)
|
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_item, self.view.view)
|
||||||
@@ -234,6 +234,8 @@ class Controller(object):
|
|||||||
self.accounts = []
|
self.accounts = []
|
||||||
# This saves the current account (important in invisible mode)
|
# This saves the current account (important in invisible mode)
|
||||||
self.current_account = ""
|
self.current_account = ""
|
||||||
|
# this saves current menu bar layout.
|
||||||
|
self.menubar_current_handler = ""
|
||||||
# Handlers are special objects as they manage the mapping of available features and events in different social networks.
|
# Handlers are special objects as they manage the mapping of available features and events in different social networks.
|
||||||
self.handlers = dict()
|
self.handlers = dict()
|
||||||
self.view.prepare()
|
self.view.prepare()
|
||||||
@@ -284,6 +286,9 @@ class Controller(object):
|
|||||||
self.started = True
|
self.started = True
|
||||||
self.streams_checker_function = RepeatingTimer(60, self.check_streams)
|
self.streams_checker_function = RepeatingTimer(60, self.check_streams)
|
||||||
self.streams_checker_function.start()
|
self.streams_checker_function.start()
|
||||||
|
if len(self.accounts) > 0:
|
||||||
|
b = self.get_first_buffer(self.accounts[0])
|
||||||
|
self.update_menus(handler=self.get_handler(b.session.type))
|
||||||
|
|
||||||
def create_ignored_session_buffer(self, session):
|
def create_ignored_session_buffer(self, session):
|
||||||
pub.sendMessage("core.create_account", name=session.get_name(), session_id=session.session_id)
|
pub.sendMessage("core.create_account", name=session.get_name(), session_id=session.session_id)
|
||||||
@@ -300,7 +305,6 @@ class Controller(object):
|
|||||||
session.start_streaming()
|
session.start_streaming()
|
||||||
|
|
||||||
def create_account_buffer(self, name, session_id, logged=False):
|
def create_account_buffer(self, name, session_id, logged=False):
|
||||||
self.accounts.append(name)
|
|
||||||
account = buffers.base.AccountBuffer(self.view.nb, name, name, session_id)
|
account = buffers.base.AccountBuffer(self.view.nb, name, name, session_id)
|
||||||
if logged == False:
|
if logged == False:
|
||||||
account.logged = logged
|
account.logged = logged
|
||||||
@@ -429,7 +433,8 @@ class Controller(object):
|
|||||||
output.speak("Unable to seek.",True)
|
output.speak("Unable to seek.",True)
|
||||||
|
|
||||||
def edit_keystrokes(self, *args, **kwargs):
|
def edit_keystrokes(self, *args, **kwargs):
|
||||||
editor = keystrokeEditor.KeystrokeEditor()
|
buffer = self.get_best_buffer()
|
||||||
|
editor = keystrokeEditor.KeystrokeEditor(buffer.session.type)
|
||||||
if editor.changed == True:
|
if editor.changed == True:
|
||||||
config.keymap.write()
|
config.keymap.write()
|
||||||
register = False
|
register = False
|
||||||
@@ -676,8 +681,15 @@ class Controller(object):
|
|||||||
|
|
||||||
def buffer_changed(self, *args, **kwargs):
|
def buffer_changed(self, *args, **kwargs):
|
||||||
buffer = self.get_current_buffer()
|
buffer = self.get_current_buffer()
|
||||||
if buffer.account != self.current_account:
|
old_account = self.current_account
|
||||||
|
new_account = buffer.account
|
||||||
|
if new_account != old_account:
|
||||||
self.current_account = buffer.account
|
self.current_account = buffer.account
|
||||||
|
new_first_buffer = self.get_first_buffer(new_account)
|
||||||
|
if new_first_buffer != None and new_first_buffer.session.type != self.menubar_current_handler:
|
||||||
|
handler = self.get_handler(new_first_buffer.session.type)
|
||||||
|
self.menubar_current_handler = new_first_buffer.session.type
|
||||||
|
self.update_menus(handler)
|
||||||
if not hasattr(buffer, "session") or buffer.session == None:
|
if not hasattr(buffer, "session") or buffer.session == None:
|
||||||
return
|
return
|
||||||
muted = autoread = False
|
muted = autoread = False
|
||||||
@@ -688,6 +700,19 @@ class Controller(object):
|
|||||||
self.view.check_menuitem("mute_buffer", muted)
|
self.view.check_menuitem("mute_buffer", muted)
|
||||||
self.view.check_menuitem("autoread", autoread)
|
self.view.check_menuitem("autoread", autoread)
|
||||||
|
|
||||||
|
def update_menus(self, handler):
|
||||||
|
if hasattr(handler, "menus"):
|
||||||
|
for m in list(handler.menus.keys()):
|
||||||
|
if hasattr(self.view, m):
|
||||||
|
menu_item = getattr(self.view, m)
|
||||||
|
if handler.menus[m] == None:
|
||||||
|
menu_item.Enable(False)
|
||||||
|
else:
|
||||||
|
menu_item.Enable(True)
|
||||||
|
menu_item.SetItemLabel(handler.menus[m])
|
||||||
|
if hasattr(handler, "item_menu"):
|
||||||
|
self.view.menubar.SetMenuLabel(1, handler.item_menu)
|
||||||
|
|
||||||
def fix_wrong_buffer(self):
|
def fix_wrong_buffer(self):
|
||||||
buf = self.get_best_buffer()
|
buf = self.get_best_buffer()
|
||||||
if buf == None:
|
if buf == None:
|
||||||
@@ -1057,8 +1082,9 @@ class Controller(object):
|
|||||||
for i in sm.removed_sessions:
|
for i in sm.removed_sessions:
|
||||||
if sessions.sessions[i].logged == True:
|
if sessions.sessions[i].logged == True:
|
||||||
self.logout_account(sessions.sessions[i].session_id)
|
self.logout_account(sessions.sessions[i].session_id)
|
||||||
self.destroy_buffer(sessions.sessions[i].settings["twitter"]["user_name"], sessions.sessions[i].settings["twitter"]["user_name"])
|
self.destroy_buffer(sessions.sessions[i].get_name(), sessions.sessions[i].get_name())
|
||||||
self.accounts.remove(sessions.sessions[i].settings["twitter"]["user_name"])
|
if sessions.sessions[i].get_name() in self.accounts:
|
||||||
|
self.accounts.remove(sessions.sessions[i].get_name())
|
||||||
sessions.sessions.pop(i)
|
sessions.sessions.pop(i)
|
||||||
|
|
||||||
def update_profile(self, *args, **kwargs):
|
def update_profile(self, *args, **kwargs):
|
||||||
@@ -1208,7 +1234,7 @@ class Controller(object):
|
|||||||
self.notify(buffer.session, sound_to_play)
|
self.notify(buffer.session, sound_to_play)
|
||||||
|
|
||||||
def toggle_share_settings(self, shareable=True):
|
def toggle_share_settings(self, shareable=True):
|
||||||
self.view.retweet.Enable(shareable)
|
self.view.share.Enable(shareable)
|
||||||
|
|
||||||
def check_streams(self):
|
def check_streams(self):
|
||||||
if self.started == False:
|
if self.started == False:
|
||||||
@@ -1239,6 +1265,7 @@ class Controller(object):
|
|||||||
elif buff == "direct_messages": sound_to_play = "dm_received.ogg"
|
elif buff == "direct_messages": sound_to_play = "dm_received.ogg"
|
||||||
elif buff == "sent": sound_to_play = "tweet_send.ogg"
|
elif buff == "sent": sound_to_play = "tweet_send.ogg"
|
||||||
elif buff == "followers" or buff == "following": sound_to_play = "update_followers.ogg"
|
elif buff == "followers" or buff == "following": sound_to_play = "update_followers.ogg"
|
||||||
|
elif buff == "notifications": sound_to_play = "new_event.ogg"
|
||||||
elif "timeline" in buff: sound_to_play = "tweet_timeline.ogg"
|
elif "timeline" in buff: sound_to_play = "tweet_timeline.ogg"
|
||||||
else: sound_to_play = None
|
else: sound_to_play = None
|
||||||
if sound_to_play != None and buff not in buffer.session.settings["other_buffers"]["muted_buffers"]:
|
if sound_to_play != None and buff not in buffer.session.settings["other_buffers"]["muted_buffers"]:
|
||||||
|
@@ -16,10 +16,47 @@ class Handler(object):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Handler, self).__init__()
|
super(Handler, self).__init__()
|
||||||
|
# Structure to hold names for menu bar items.
|
||||||
|
# empty names mean the item will be Disabled.
|
||||||
|
self.menus = dict(
|
||||||
|
# In application menu.
|
||||||
|
updateProfile=None,
|
||||||
|
menuitem_search=_("&Search"),
|
||||||
|
lists=None,
|
||||||
|
manageAliases=None,
|
||||||
|
# In item menu.
|
||||||
|
compose=_("&Post"),
|
||||||
|
reply=_("Re&ply"),
|
||||||
|
share=_("&Boost"),
|
||||||
|
fav=_("&Add to favorites"),
|
||||||
|
unfav=_("Remove from favorites"),
|
||||||
|
view=_("&Show post"),
|
||||||
|
view_coordinates=None,
|
||||||
|
view_conversation=_("View conversa&tion"),
|
||||||
|
ocr=None,
|
||||||
|
delete=_("&Delete"),
|
||||||
|
# In user menu.
|
||||||
|
follow=_("&Actions..."),
|
||||||
|
timeline=_("&View timeline..."),
|
||||||
|
dm=_("Direct me&ssage"),
|
||||||
|
addAlias=None,
|
||||||
|
addToList=None,
|
||||||
|
removeFromList=None,
|
||||||
|
viewLists=None,
|
||||||
|
details=None,
|
||||||
|
favs=None,
|
||||||
|
# In buffer Menu.
|
||||||
|
trends=None,
|
||||||
|
filter=None,
|
||||||
|
manage_filters=None
|
||||||
|
)
|
||||||
|
# Name for the "tweet" menu in the menu bar.
|
||||||
|
self.item_menu = _("&Post")
|
||||||
|
|
||||||
def create_buffers(self, session, createAccounts=True, controller=None):
|
def create_buffers(self, session, createAccounts=True, controller=None):
|
||||||
session.get_user_info()
|
session.get_user_info()
|
||||||
name = session.get_name()
|
name = session.get_name()
|
||||||
|
controller.accounts.append(name)
|
||||||
if createAccounts == True:
|
if createAccounts == True:
|
||||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
||||||
root_position =controller.view.search(name, name)
|
root_position =controller.view.search(name, name)
|
||||||
@@ -48,14 +85,16 @@ class Handler(object):
|
|||||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Muted users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="mutes", name="muted", sessionObject=session, account=name))
|
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Muted users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="mutes", name="muted", sessionObject=session, account=name))
|
||||||
elif i == 'blocked':
|
elif i == 'blocked':
|
||||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Blocked users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="blocks", name="blocked", sessionObject=session, account=name))
|
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Blocked users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="blocks", name="blocked", sessionObject=session, account=name))
|
||||||
|
elif i == 'notifications':
|
||||||
|
pub.sendMessage("createBuffer", buffer_type="NotificationsBuffer", session_type=session.type, buffer_title=_("Notifications"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_notification", function="notifications", name="notifications", sessionObject=session, account=name))
|
||||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="timelines", account=name))
|
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="timelines", account=name))
|
||||||
timelines_position =controller.view.search("timelines", name)
|
timelines_position =controller.view.search("timelines", name)
|
||||||
for i in session.settings["other_buffers"]["timelines"]:
|
for i in session.settings["other_buffers"]["timelines"]:
|
||||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=i, parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline".format(i), sessionObject=session, account=name, sound="tweet_timeline.ogg", id=i))
|
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Timeline for {}").format(i), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="{}-timeline".format(i), sessionObject=session, account=name, sound="tweet_timeline.ogg", id=i))
|
||||||
for i in session.settings["other_buffers"]["followers_timelines"]:
|
for i in session.settings["other_buffers"]["followers_timelines"]:
|
||||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Followers for {}").format(i), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (i,), sessionObject=session, account=name, sound="new_event.ogg", id=i))
|
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Followers for {}").format(i), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="{}-followers".format(i,), sessionObject=session, account=name, sound="new_event.ogg", id=i))
|
||||||
for i in session.settings["other_buffers"]["following_timelines"]:
|
for i in session.settings["other_buffers"]["following_timelines"]:
|
||||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Following for {}").format(i), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-following" % (i,), sessionObject=session, account=name, sound="new_event.ogg", id=i))
|
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Following for {}").format(i), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="{}-following".format(i,), sessionObject=session, account=name, sound="new_event.ogg", id=i))
|
||||||
# pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Lists"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="lists", name))
|
# pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Lists"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="lists", name))
|
||||||
# lists_position =controller.view.search("lists", session.db["user_name"])
|
# lists_position =controller.view.search("lists", session.db["user_name"])
|
||||||
# for i in session.settings["other_buffers"]["lists"]:
|
# for i in session.settings["other_buffers"]["lists"]:
|
||||||
@@ -138,45 +177,45 @@ class Handler(object):
|
|||||||
users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]]
|
users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]]
|
||||||
if item.account.acct not in users and item.account.id != buffer.session.db["user_id"]:
|
if item.account.acct not in users and item.account.id != buffer.session.db["user_id"]:
|
||||||
users.insert(0, item.account.acct)
|
users.insert(0, item.account.acct)
|
||||||
u = userActions.UserTimeline(buffer.session, users)
|
u = userActions.UserTimeline(buffer.session, users)
|
||||||
if u.dialog.ShowModal() == wx.ID_OK:
|
if u.dialog.ShowModal() == wx.ID_OK:
|
||||||
action = u.process_action()
|
action = u.process_action()
|
||||||
if action == None:
|
if action == None:
|
||||||
return
|
return
|
||||||
user = u.user
|
user = u.user
|
||||||
if action == "posts":
|
if action == "posts":
|
||||||
if user.statuses_count == 0:
|
if user.statuses_count == 0:
|
||||||
dialogs.no_posts()
|
dialogs.no_posts()
|
||||||
return
|
return
|
||||||
if user.id in buffer.session.settings["other_buffers"]["timelines"]:
|
if user.id in buffer.session.settings["other_buffers"]["timelines"]:
|
||||||
commonMessageDialogs.timeline_exist()
|
commonMessageDialogs.timeline_exist()
|
||||||
return
|
return
|
||||||
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
||||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id))
|
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id))
|
||||||
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
|
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
|
||||||
buffer.session.sound.play("create_timeline.ogg")
|
buffer.session.sound.play("create_timeline.ogg")
|
||||||
elif action == "followers":
|
elif action == "followers":
|
||||||
if user.followers_count == 0:
|
if user.followers_count == 0:
|
||||||
dialogs.no_followers()
|
dialogs.no_followers()
|
||||||
return
|
return
|
||||||
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
|
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
|
||||||
commonMessageDialogs.timeline_exist()
|
commonMessageDialogs.timeline_exist()
|
||||||
return
|
return
|
||||||
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
||||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
|
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
|
||||||
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
|
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
|
||||||
buffer.session.sound.play("create_timeline.ogg")
|
buffer.session.sound.play("create_timeline.ogg")
|
||||||
elif action == "following":
|
elif action == "following":
|
||||||
if user.following_count == 0:
|
if user.following_count == 0:
|
||||||
dialogs.no_following()
|
dialogs.no_following()
|
||||||
return
|
return
|
||||||
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]:
|
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]:
|
||||||
commonMessageDialogs.timeline_exist()
|
commonMessageDialogs.timeline_exist()
|
||||||
return
|
return
|
||||||
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
||||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
|
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
|
||||||
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id)
|
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id)
|
||||||
buffer.session.sound.play("create_timeline.ogg")
|
buffer.session.sound.play("create_timeline.ogg")
|
||||||
buffer.session.settings.write()
|
buffer.session.settings.write()
|
||||||
|
|
||||||
def account_settings(self, buffer, controller):
|
def account_settings(self, buffer, controller):
|
||||||
|
@@ -185,6 +185,11 @@ class post(messages.basicTweet):
|
|||||||
visibility_settings = ["public", "unlisted", "private", "direct"]
|
visibility_settings = ["public", "unlisted", "private", "direct"]
|
||||||
return visibility_settings[self.message.visibility.GetSelection()]
|
return visibility_settings[self.message.visibility.GetSelection()]
|
||||||
|
|
||||||
|
def set_visibility(self, setting):
|
||||||
|
visibility_settings = ["public", "unlisted", "private", "direct"]
|
||||||
|
visibility_setting = visibility_settings.index(setting)
|
||||||
|
self.message.visibility.SetSelection(setting)
|
||||||
|
|
||||||
class viewPost(post):
|
class viewPost(post):
|
||||||
def __init__(self, post, offset_hours=0, date="", item_url=""):
|
def __init__(self, post, offset_hours=0, date="", item_url=""):
|
||||||
if post.reblog != None:
|
if post.reblog != None:
|
||||||
|
@@ -32,6 +32,7 @@ class accountSettingsController(globalSettingsController):
|
|||||||
# widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
|
# 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)
|
# 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", "relative_time", self.config["general"]["relative_times"])
|
||||||
|
self.dialog.set_value("general", "read_preferences_from_instance", self.config["general"]["read_preferences_from_instance"])
|
||||||
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
|
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", "hide_emojis", self.config["general"]["hide_emojis"])
|
||||||
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_posts_per_call"])
|
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_posts_per_call"])
|
||||||
@@ -111,6 +112,7 @@ class accountSettingsController(globalSettingsController):
|
|||||||
self.needs_restart = True
|
self.needs_restart = True
|
||||||
log.debug("Triggered app restart due to change in relative times.")
|
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"]["relative_times"] = self.dialog.get_value("general", "relative_time")
|
||||||
|
self.config["general"]["read_preferences_from_instance"] = self.dialog.get_value("general", "read_preferences_from_instance")
|
||||||
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
|
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"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
|
||||||
self.config["general"]["max_posts_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
|
self.config["general"]["max_posts_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
|
||||||
|
@@ -16,10 +16,47 @@ class Handler(object):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Handler, self).__init__()
|
super(Handler, self).__init__()
|
||||||
|
# Structure to hold names for menu bar items.
|
||||||
|
# empty names mean the item will be Disabled.
|
||||||
|
self.menus = dict(
|
||||||
|
# In application menu.
|
||||||
|
updateProfile=_("&Update profile"),
|
||||||
|
menuitem_search=_("&Search"),
|
||||||
|
lists=_("&Lists manager"),
|
||||||
|
manageAliases=_("Manage user aliases"),
|
||||||
|
# In Item Menu.
|
||||||
|
compose=_("&Tweet"),
|
||||||
|
reply=_("Re&ply"),
|
||||||
|
share=_("&Retweet"),
|
||||||
|
fav=_("&Like"),
|
||||||
|
unfav=_("&Unlike"),
|
||||||
|
view=_("&Show tweet"),
|
||||||
|
view_coordinates=_("View &address"),
|
||||||
|
view_conversation=_("View conversa&tion"),
|
||||||
|
ocr=_("Read text in picture"),
|
||||||
|
delete=_("&Delete"),
|
||||||
|
# In user menu.
|
||||||
|
follow=_("&Actions..."),
|
||||||
|
timeline=_("&View timeline..."),
|
||||||
|
dm=_("Direct me&ssage"),
|
||||||
|
addAlias=_("Add a&lias"),
|
||||||
|
addToList=_("&Add to list"),
|
||||||
|
removeFromList=_("R&emove from list"),
|
||||||
|
viewLists=_("&View lists"),
|
||||||
|
details=_("Show user &profile"),
|
||||||
|
favs=_("View likes"),
|
||||||
|
# In buffer menu.
|
||||||
|
trends=_("New &trending topics buffer..."),
|
||||||
|
filter=_("Create a &filter"),
|
||||||
|
manage_filters=_("&Manage filters"),
|
||||||
|
)
|
||||||
|
# Name for the "tweet" menu in the menu bar.
|
||||||
|
self.item_menu = _("&Tweet")
|
||||||
|
|
||||||
def create_buffers(self, session, createAccounts=True, controller=None):
|
def create_buffers(self, session, createAccounts=True, controller=None):
|
||||||
session.get_user_info()
|
session.get_user_info()
|
||||||
name = session.get_name()
|
name = session.get_name()
|
||||||
|
controller.accounts.append(name)
|
||||||
if createAccounts == True:
|
if createAccounts == True:
|
||||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
||||||
root_position =controller.view.search(name, name)
|
root_position =controller.view.search(name, name)
|
||||||
|
@@ -1,3 +1 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
from .keystrokeEditor import KeystrokeEditor
|
from .keystrokeEditor import KeystrokeEditor
|
||||||
|
1
src/keystrokeEditor/actions/__init__.py
Normal file
1
src/keystrokeEditor/actions/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import twitter, mastodon
|
57
src/keystrokeEditor/actions/mastodon.py
Normal file
57
src/keystrokeEditor/actions/mastodon.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
actions = {
|
||||||
|
"up": _(u"Go up in the current buffer"),
|
||||||
|
"down": _(u"Go down in the current buffer"),
|
||||||
|
"left": _(u"Go to the previous buffer"),
|
||||||
|
"right": _(u"Go to the next buffer"),
|
||||||
|
"next_account": _(u"Focus the next session"),
|
||||||
|
"previous_account": _(u"Focus the previous session"),
|
||||||
|
"show_hide": _(u"Show or hide the GUI"),
|
||||||
|
"post_tweet": _("Make a new post"),
|
||||||
|
"post_reply": _(u"Reply"),
|
||||||
|
"post_retweet": _(u"Boost"),
|
||||||
|
"send_dm": _(u"Send direct message"),
|
||||||
|
"add_to_favourites": _("Add post to favorites"),
|
||||||
|
"remove_from_favourites": _(u"Remove post from favorites"),
|
||||||
|
"toggle_like": _("Add/remove post from favorites"),
|
||||||
|
"follow": _(u"Open the user actions dialogue"),
|
||||||
|
# "user_details": _(u"See user details"),
|
||||||
|
"view_item": _(u"Show post"),
|
||||||
|
"exit": _(u"Quit"),
|
||||||
|
"open_timeline": _(u"Open user timeline"),
|
||||||
|
"remove_buffer": _(u"Destroy buffer"),
|
||||||
|
"interact": _(u"Interact with the currently focused post."),
|
||||||
|
"url": _(u"Open URL"),
|
||||||
|
"open_in_browser": _(u"View in browser"),
|
||||||
|
"volume_up": _(u"Increase volume by 5%"),
|
||||||
|
"volume_down": _(u"Decrease volume by 5%"),
|
||||||
|
"go_home": _(u"Jump to the first element of a buffer"),
|
||||||
|
"go_end": _(u"Jump to the last element of the current buffer"),
|
||||||
|
"go_page_up": _(u"Jump 20 elements up in the current buffer"),
|
||||||
|
"go_page_down": _(u"Jump 20 elements down in the current buffer"),
|
||||||
|
# "update_profile": _(u"Edit profile"),
|
||||||
|
"delete": _("Delete post"),
|
||||||
|
"clear_buffer": _(u"Empty the current buffer"),
|
||||||
|
"repeat_item": _(u"Repeat last item"),
|
||||||
|
"copy_to_clipboard": _(u"Copy to clipboard"),
|
||||||
|
# "add_to_list": _(u"Add to list"),
|
||||||
|
# "remove_from_list": _(u"Remove from list"),
|
||||||
|
"toggle_buffer_mute": _(u"Mute/unmute the active buffer"),
|
||||||
|
"toggle_session_mute": _(u"Mute/unmute the current session"),
|
||||||
|
"toggle_autoread": _(u"toggle the automatic reading of incoming tweets in the active buffer"),
|
||||||
|
"search": _(u"Search on instance"),
|
||||||
|
"find": _(u"Find a string in the currently focused buffer"),
|
||||||
|
"edit_keystrokes": _(u"Show the keystroke editor"),
|
||||||
|
# "view_user_lists": _(u"Show lists for a specified user"),
|
||||||
|
"get_more_items": _(u"load previous items"),
|
||||||
|
# "get_trending_topics": _(u"Create a trending topics buffer"),
|
||||||
|
"open_conversation": _(u"View conversation"),
|
||||||
|
"check_for_updates": _(u"Check and download updates"),
|
||||||
|
"configuration": _(u"Opens the global settings dialogue"),
|
||||||
|
# "list_manager": _(u"Opens the list manager"),
|
||||||
|
"accountConfiguration": _(u"Opens the account settings dialogue"),
|
||||||
|
"audio": _(u"Try to play a media file"),
|
||||||
|
"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."),
|
||||||
|
# "add_alias": _("Adds an alias to an user"),
|
||||||
|
}
|
@@ -53,7 +53,7 @@ actions = {
|
|||||||
"configuration": _(u"Opens the global settings dialogue"),
|
"configuration": _(u"Opens the global settings dialogue"),
|
||||||
"list_manager": _(u"Opens the list manager"),
|
"list_manager": _(u"Opens the list manager"),
|
||||||
"accountConfiguration": _(u"Opens the account settings dialogue"),
|
"accountConfiguration": _(u"Opens the account settings dialogue"),
|
||||||
"audio": _(u"Try to play an audio file"),
|
"audio": _(u"Try to play a media 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"),
|
"add_alias": _("Adds an alias to an user"),
|
@@ -2,18 +2,19 @@
|
|||||||
import widgetUtils
|
import widgetUtils
|
||||||
import config
|
import config
|
||||||
from . import wx_ui
|
from . import wx_ui
|
||||||
from . import constants
|
from . import actions
|
||||||
from pubsub import pub
|
from pubsub import pub
|
||||||
|
|
||||||
class KeystrokeEditor(object):
|
class KeystrokeEditor(object):
|
||||||
def __init__(self):
|
def __init__(self, session_type="twitter"):
|
||||||
super(KeystrokeEditor, self).__init__()
|
super(KeystrokeEditor, self).__init__()
|
||||||
|
self.actions = getattr(actions, session_type).actions
|
||||||
self.changed = False # Change it if the keyboard shorcuts are reassigned.
|
self.changed = False # Change it if the keyboard shorcuts are reassigned.
|
||||||
self.dialog = wx_ui.keystrokeEditorDialog()
|
self.dialog = wx_ui.keystrokeEditorDialog()
|
||||||
self.map = config.keymap["keymap"]
|
self.map = config.keymap["keymap"]
|
||||||
# we need to copy the keymap before modify it, for unregistering the old keystrokes if is needed.
|
# we need to copy the keymap before modify it, for unregistering the old keystrokes if is needed.
|
||||||
self.hold_map = self.map.copy()
|
self.hold_map = self.map.copy()
|
||||||
self.dialog.put_keystrokes(constants.actions, self.map)
|
self.dialog.put_keystrokes(self.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.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)
|
||||||
@@ -29,7 +30,7 @@ class KeystrokeEditor(object):
|
|||||||
if new_keystroke != self.map[action]:
|
if new_keystroke != self.map[action]:
|
||||||
self.changed = True
|
self.changed = True
|
||||||
self.map[action] = new_keystroke
|
self.map[action] = new_keystroke
|
||||||
self.dialog.put_keystrokes(constants.actions, self.map)
|
self.dialog.put_keystrokes(self.actions, self.map)
|
||||||
|
|
||||||
def undefine_keystroke(self, *args, **kwargs):
|
def undefine_keystroke(self, *args, **kwargs):
|
||||||
action = self.dialog.actions[self.dialog.get_action()]
|
action = self.dialog.actions[self.dialog.get_action()]
|
||||||
@@ -40,7 +41,7 @@ class KeystrokeEditor(object):
|
|||||||
if answer == widgetUtils.YES:
|
if answer == widgetUtils.YES:
|
||||||
self.map[action] = ""
|
self.map[action] = ""
|
||||||
self.changed = True
|
self.changed = True
|
||||||
self.dialog.put_keystrokes(constants.actions, self.map)
|
self.dialog.put_keystrokes(self.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("+"):
|
||||||
|
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
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
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
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
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
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
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
@@ -6,6 +6,7 @@ ignored_clients = list(default=list())
|
|||||||
|
|
||||||
[general]
|
[general]
|
||||||
relative_times = boolean(default=True)
|
relative_times = boolean(default=True)
|
||||||
|
read_preferences_from_instance = boolean(default=True)
|
||||||
max_posts_per_call = integer(default=40)
|
max_posts_per_call = integer(default=40)
|
||||||
reverse_timelines = boolean(default=False)
|
reverse_timelines = boolean(default=False)
|
||||||
persist_size = integer(default=0)
|
persist_size = integer(default=0)
|
||||||
@@ -48,6 +49,7 @@ speech_reporting = boolean(default=True)
|
|||||||
post = string(default="$display_name, $safe_text $image_descriptions $date. $visibility. $source")
|
post = string(default="$display_name, $safe_text $image_descriptions $date. $visibility. $source")
|
||||||
person = string(default="$display_name (@$screen_name). $followers followers, $following following, $posts posts. Joined $created_at.")
|
person = string(default="$display_name (@$screen_name). $followers followers, $following following, $posts posts. Joined $created_at.")
|
||||||
conversation = string(default="Conversation with $users. Last message: $last_post")
|
conversation = string(default="Conversation with $users. Last message: $last_post")
|
||||||
|
notification = string(default="$display_name $text, $date")
|
||||||
|
|
||||||
[filters]
|
[filters]
|
||||||
|
|
||||||
|
@@ -3,12 +3,14 @@
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import shutil
|
||||||
import widgetUtils
|
import widgetUtils
|
||||||
import sessions
|
import sessions
|
||||||
import output
|
import output
|
||||||
import paths
|
import paths
|
||||||
import config_utils
|
import config_utils
|
||||||
import config
|
import config
|
||||||
|
import application
|
||||||
from pubsub import pub
|
from pubsub import pub
|
||||||
from tweepy.errors import TweepyException
|
from tweepy.errors import TweepyException
|
||||||
from controller import settings
|
from controller import settings
|
||||||
@@ -65,12 +67,14 @@ class sessionManagerController(object):
|
|||||||
os.exception("Exception thrown while removing malformed session")
|
os.exception("Exception thrown while removing malformed session")
|
||||||
continue
|
continue
|
||||||
if config_test.get("twitter") != None:
|
if config_test.get("twitter") != None:
|
||||||
|
if application.twitter_support_enabled == False:
|
||||||
|
continue
|
||||||
name = _("{account_name} (Twitter)").format(account_name=config_test["twitter"]["user_name"])
|
name = _("{account_name} (Twitter)").format(account_name=config_test["twitter"]["user_name"])
|
||||||
if config_test["twitter"]["user_key"] != "" and config_test["twitter"]["user_secret"] != "":
|
if config_test["twitter"]["user_key"] != "" and config_test["twitter"]["user_secret"] != "":
|
||||||
sessionsList.append(name)
|
sessionsList.append(name)
|
||||||
self.sessions.append(dict(type="twitter", id=i))
|
self.sessions.append(dict(type="twitter", id=i))
|
||||||
elif config_test.get("mastodon") != None:
|
elif config_test.get("mastodon") != None:
|
||||||
name = _("{account_name} (Mastodon)").format(account_name=config_test["mastodon"]["user_name"])
|
name = _("{account_name}@{instance} (Mastodon)").format(account_name=config_test["mastodon"]["user_name"], instance=config_test["mastodon"]["instance"].replace("https://", ""))
|
||||||
if config_test["mastodon"]["instance"] != "" and config_test["mastodon"]["access_token"] != "":
|
if config_test["mastodon"]["instance"] != "" and config_test["mastodon"]["access_token"] != "":
|
||||||
sessionsList.append(name)
|
sessionsList.append(name)
|
||||||
self.sessions.append(dict(type="mastodon", id=i))
|
self.sessions.append(dict(type="mastodon", id=i))
|
||||||
@@ -98,6 +102,8 @@ class sessionManagerController(object):
|
|||||||
continue
|
continue
|
||||||
# Create the session object based in session type.
|
# Create the session object based in session type.
|
||||||
if i.get("type") == "twitter":
|
if i.get("type") == "twitter":
|
||||||
|
if application.twitter_support_enabled == False:
|
||||||
|
continue
|
||||||
s = TwitterSession.Session(i.get("id"))
|
s = TwitterSession.Session(i.get("id"))
|
||||||
elif i.get("type") == "mastodon":
|
elif i.get("type") == "mastodon":
|
||||||
s = MastodonSession.Session(i.get("id"))
|
s = MastodonSession.Session(i.get("id"))
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
""" Base GUI (Wx) class for the Session manager module."""
|
""" Base GUI (Wx) class for the Session manager module."""
|
||||||
import wx
|
import wx
|
||||||
|
import application
|
||||||
from pubsub import pub
|
from pubsub import pub
|
||||||
from multiplatform_widgets import widgets
|
from multiplatform_widgets import widgets
|
||||||
|
|
||||||
@@ -50,9 +51,10 @@ class sessionManagerWindow(wx.Dialog):
|
|||||||
|
|
||||||
def on_new_account(self, *args, **kwargs):
|
def on_new_account(self, *args, **kwargs):
|
||||||
menu = wx.Menu()
|
menu = wx.Menu()
|
||||||
twitter = menu.Append(wx.ID_ANY, _("Twitter"))
|
if application.twitter_support_enabled:
|
||||||
|
twitter = menu.Append(wx.ID_ANY, _("Twitter"))
|
||||||
|
menu.Bind(wx.EVT_MENU, self.on_new_twitter_account, twitter)
|
||||||
mastodon = menu.Append(wx.ID_ANY, _("Mastodon"))
|
mastodon = menu.Append(wx.ID_ANY, _("Mastodon"))
|
||||||
menu.Bind(wx.EVT_MENU, self.on_new_twitter_account, twitter)
|
|
||||||
menu.Bind(wx.EVT_MENU, self.on_new_mastodon_account, mastodon)
|
menu.Bind(wx.EVT_MENU, self.on_new_mastodon_account, mastodon)
|
||||||
self.PopupMenu(menu, self.new.GetPosition())
|
self.PopupMenu(menu, self.new.GetPosition())
|
||||||
|
|
||||||
@@ -64,6 +66,8 @@ class sessionManagerWindow(wx.Dialog):
|
|||||||
pub.sendMessage("sessionmanager.new_account", type="mastodon")
|
pub.sendMessage("sessionmanager.new_account", type="mastodon")
|
||||||
|
|
||||||
def on_new_twitter_account(self, *args, **kwargs):
|
def on_new_twitter_account(self, *args, **kwargs):
|
||||||
|
if application.twitter_support_enabled == False:
|
||||||
|
return
|
||||||
dlg = wx.MessageDialog(self, _(u"The request to authorize your Twitter account will be opened in your browser. You only need to do this once. Would you like to continue?"), _(u"Authorization"), wx.YES_NO)
|
dlg = wx.MessageDialog(self, _(u"The request to authorize your Twitter account will be opened in your browser. You only need to do this once. Would you like to continue?"), _(u"Authorization"), wx.YES_NO)
|
||||||
response = dlg.ShowModal()
|
response = dlg.ShowModal()
|
||||||
dlg.Destroy()
|
dlg.Destroy()
|
||||||
|
@@ -3,7 +3,7 @@ import arrow
|
|||||||
import languageHandler
|
import languageHandler
|
||||||
from . import utils, templates
|
from . import utils, templates
|
||||||
|
|
||||||
def compose_post(post, db, relative_times, show_screen_names):
|
def compose_post(post, db, relative_times, show_screen_names, safe=True):
|
||||||
if show_screen_names == False:
|
if show_screen_names == False:
|
||||||
user = post.account.get("display_name")
|
user = post.account.get("display_name")
|
||||||
if user == "":
|
if user == "":
|
||||||
@@ -16,9 +16,9 @@ def compose_post(post, db, relative_times, show_screen_names):
|
|||||||
else:
|
else:
|
||||||
ts = original_date.shift(hours=db["utc_offset"]).format(_("dddd, MMMM D, YYYY H:m"), locale=languageHandler.curLang[:2])
|
ts = original_date.shift(hours=db["utc_offset"]).format(_("dddd, MMMM D, YYYY H:m"), locale=languageHandler.curLang[:2])
|
||||||
if post.reblog != None:
|
if post.reblog != None:
|
||||||
text = _("Boosted from @{}: {}").format(post.reblog.account.acct, templates.process_text(post.reblog))
|
text = _("Boosted from @{}: {}").format(post.reblog.account.acct, templates.process_text(post.reblog, safe=safe))
|
||||||
else:
|
else:
|
||||||
text = templates.process_text(post)
|
text = templates.process_text(post, safe=safe)
|
||||||
source = post.get("application", "")
|
source = post.get("application", "")
|
||||||
# "" means remote user, None for legacy apps so we should cover both sides.
|
# "" means remote user, None for legacy apps so we should cover both sides.
|
||||||
if source != None and source != "":
|
if source != None and source != "":
|
||||||
@@ -27,7 +27,7 @@ def compose_post(post, db, relative_times, show_screen_names):
|
|||||||
source = ""
|
source = ""
|
||||||
return [user+", ", text, ts+", ", source]
|
return [user+", ", text, ts+", ", source]
|
||||||
|
|
||||||
def compose_user(user, db, relative_times=True, show_screen_names=False):
|
def compose_user(user, db, relative_times=True, show_screen_names=False, safe=False):
|
||||||
original_date = arrow.get(user.created_at)
|
original_date = arrow.get(user.created_at)
|
||||||
if relative_times:
|
if relative_times:
|
||||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||||
@@ -38,7 +38,7 @@ def compose_user(user, db, relative_times=True, show_screen_names=False):
|
|||||||
name = user.get("username")
|
name = user.get("username")
|
||||||
return [_("%s (@%s). %s followers, %s following, %s posts. Joined %s") % (name, user.acct, user.followers_count, user.following_count, user.statuses_count, ts)]
|
return [_("%s (@%s). %s followers, %s following, %s posts. Joined %s") % (name, user.acct, user.followers_count, user.following_count, user.statuses_count, ts)]
|
||||||
|
|
||||||
def compose_conversation(conversation, db, relative_times, show_screen_names):
|
def compose_conversation(conversation, db, relative_times, show_screen_names, safe=False):
|
||||||
users = []
|
users = []
|
||||||
for account in conversation.accounts:
|
for account in conversation.accounts:
|
||||||
if account.display_name != "":
|
if account.display_name != "":
|
||||||
@@ -49,3 +49,32 @@ def compose_conversation(conversation, db, relative_times, show_screen_names):
|
|||||||
last_post = compose_post(conversation.last_status, db, relative_times, show_screen_names)
|
last_post = compose_post(conversation.last_status, db, relative_times, show_screen_names)
|
||||||
text = _("Last message from {}: {}").format(last_post[0], last_post[1])
|
text = _("Last message from {}: {}").format(last_post[0], last_post[1])
|
||||||
return [users, text, last_post[-2], last_post[-1]]
|
return [users, text, last_post[-2], last_post[-1]]
|
||||||
|
|
||||||
|
def compose_notification(notification, db, relative_times, show_screen_names, safe=False):
|
||||||
|
if show_screen_names == False:
|
||||||
|
user = notification.account.get("display_name")
|
||||||
|
if user == "":
|
||||||
|
user = notification.account.get("username")
|
||||||
|
else:
|
||||||
|
user = notification.account.get("acct")
|
||||||
|
original_date = arrow.get(notification.created_at)
|
||||||
|
if relative_times:
|
||||||
|
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||||
|
else:
|
||||||
|
ts = original_date.shift(hours=db["utc_offset"]).format(_("dddd, MMMM D, YYYY H:m"), locale=languageHandler.curLang[:2])
|
||||||
|
text = "Unknown: %r" % (notification)
|
||||||
|
if notification.type == "status":
|
||||||
|
text = _("{username} has posted: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
|
||||||
|
elif notification.type == "mention":
|
||||||
|
text = _("{username} has mentionned you: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
|
||||||
|
elif notification.type == "reblog":
|
||||||
|
text = _("{username} has boosted: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
|
||||||
|
elif notification.type == "favourite":
|
||||||
|
text = _("{username} has added to favorites: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
|
||||||
|
elif notification.type == "follow":
|
||||||
|
text = _("{username} has followed you.").format(username=user)
|
||||||
|
elif notification.type == "poll":
|
||||||
|
text = _("A poll in which you have voted has expired: {status}").format(status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
|
||||||
|
elif notification.type == "follow_request":
|
||||||
|
text = _("{username} wants to follow you.").format(username=user)
|
||||||
|
return [user, text, ts]
|
@@ -15,7 +15,6 @@ from pubsub import pub
|
|||||||
from mysc.thread_utils import call_threaded
|
from mysc.thread_utils import call_threaded
|
||||||
from sessions import base
|
from sessions import base
|
||||||
from sessions.mastodon import utils, streaming
|
from sessions.mastodon import utils, streaming
|
||||||
from .wxUI import authorisationDialog
|
|
||||||
|
|
||||||
log = logging.getLogger("sessions.mastodonSession")
|
log = logging.getLogger("sessions.mastodonSession")
|
||||||
|
|
||||||
@@ -30,6 +29,8 @@ class Session(base.baseSession):
|
|||||||
self.type = "mastodon"
|
self.type = "mastodon"
|
||||||
self.db["pagination_info"] = dict()
|
self.db["pagination_info"] = dict()
|
||||||
self.char_limit = 500
|
self.char_limit = 500
|
||||||
|
self.post_visibility = "public"
|
||||||
|
self.expand_spoilers = False
|
||||||
pub.subscribe(self.on_status, "mastodon.status_received")
|
pub.subscribe(self.on_status, "mastodon.status_received")
|
||||||
pub.subscribe(self.on_status_updated, "mastodon.status_updated")
|
pub.subscribe(self.on_status_updated, "mastodon.status_updated")
|
||||||
pub.subscribe(self.on_notification, "mastodon.notification_received")
|
pub.subscribe(self.on_notification, "mastodon.notification_received")
|
||||||
@@ -38,7 +39,7 @@ class Session(base.baseSession):
|
|||||||
if self.settings["mastodon"]["access_token"] != None and self.settings["mastodon"]["instance"] != None:
|
if self.settings["mastodon"]["access_token"] != None and self.settings["mastodon"]["instance"] != None:
|
||||||
try:
|
try:
|
||||||
log.debug("Logging in to Mastodon instance {}...".format(self.settings["mastodon"]["instance"]))
|
log.debug("Logging in to Mastodon instance {}...".format(self.settings["mastodon"]["instance"]))
|
||||||
self.api = mastodon.Mastodon(access_token=self.settings["mastodon"]["access_token"], api_base_url=self.settings["mastodon"]["instance"], mastodon_version=MASTODON_VERSION)
|
self.api = mastodon.Mastodon(access_token=self.settings["mastodon"]["access_token"], api_base_url=self.settings["mastodon"]["instance"], mastodon_version=MASTODON_VERSION, user_agent="TWBlue/{}".format(application.version))
|
||||||
if verify_credentials == True:
|
if verify_credentials == True:
|
||||||
credentials = self.api.account_verify_credentials()
|
credentials = self.api.account_verify_credentials()
|
||||||
self.db["user_name"] = credentials["username"]
|
self.db["user_name"] = credentials["username"]
|
||||||
@@ -47,8 +48,8 @@ class Session(base.baseSession):
|
|||||||
self.logged = True
|
self.logged = True
|
||||||
log.debug("Logged.")
|
log.debug("Logged.")
|
||||||
self.counter = 0
|
self.counter = 0
|
||||||
except IOError:
|
except MastodonError:
|
||||||
log.error("The login attempt failed.")
|
log.exception("The login attempt failed.")
|
||||||
self.logged = False
|
self.logged = False
|
||||||
else:
|
else:
|
||||||
self.logged = False
|
self.logged = False
|
||||||
@@ -99,14 +100,18 @@ class Session(base.baseSession):
|
|||||||
offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
|
offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
|
||||||
offset = offset / 60 / 60 * -1
|
offset = offset / 60 / 60 * -1
|
||||||
self.db["utc_offset"] = offset
|
self.db["utc_offset"] = offset
|
||||||
|
instance = self.api.instance()
|
||||||
if len(self.supported_languages) == 0:
|
if len(self.supported_languages) == 0:
|
||||||
self.supported_languages = self.api.instance().languages
|
self.supported_languages = instance.languages
|
||||||
self.get_lists()
|
self.get_lists()
|
||||||
self.get_muted_users()
|
self.get_muted_users()
|
||||||
# determine instance custom characters limit.
|
# determine instance custom characters limit.
|
||||||
instance = self.api.instance()
|
|
||||||
if hasattr(instance, "configuration") and hasattr(instance.configuration, "statuses") and hasattr(instance.configuration.statuses, "max_characters"):
|
if hasattr(instance, "configuration") and hasattr(instance.configuration, "statuses") and hasattr(instance.configuration.statuses, "max_characters"):
|
||||||
self.char_limit = instance.configuration.statuses.max_characters
|
self.char_limit = instance.configuration.statuses.max_characters
|
||||||
|
# User preferences for some things.
|
||||||
|
preferences = self.api.preferences()
|
||||||
|
self.post_visibility = preferences.get("posting:default:visibility")
|
||||||
|
self.expand_spoilers = preferences.get("reading:expand:spoilers")
|
||||||
self.settings.write()
|
self.settings.write()
|
||||||
|
|
||||||
def get_lists(self):
|
def get_lists(self):
|
||||||
@@ -221,8 +226,14 @@ class Session(base.baseSession):
|
|||||||
if config.app["app-settings"]["no_streaming"]:
|
if config.app["app-settings"]["no_streaming"]:
|
||||||
return
|
return
|
||||||
listener = streaming.StreamListener(session_name=self.get_name(), user_id=self.db["user_id"])
|
listener = streaming.StreamListener(session_name=self.get_name(), user_id=self.db["user_id"])
|
||||||
self.user_stream = self.api.stream_user(listener, run_async=True)
|
try:
|
||||||
self.direct_stream = self.api.stream_direct(listener, run_async=True)
|
stream_healthy = self.api.stream_healthy()
|
||||||
|
if stream_healthy == True:
|
||||||
|
self.user_stream = self.api.stream_user(listener, run_async=True)
|
||||||
|
self.direct_stream = self.api.stream_direct(listener, run_async=True)
|
||||||
|
log.debug("Started streams for session {}.".format(self.get_name()))
|
||||||
|
except Exception as e:
|
||||||
|
log.exception("Detected streaming unhealthy in {} session.".format(self.get_name()))
|
||||||
|
|
||||||
def stop_streaming(self):
|
def stop_streaming(self):
|
||||||
if config.app["app-settings"]["no_streaming"]:
|
if config.app["app-settings"]["no_streaming"]:
|
||||||
@@ -279,7 +290,7 @@ class Session(base.baseSession):
|
|||||||
obj = None
|
obj = None
|
||||||
if notification.type == "mention":
|
if notification.type == "mention":
|
||||||
buffers = ["mentions"]
|
buffers = ["mentions"]
|
||||||
obj = notification.status
|
obj = notification
|
||||||
elif notification.type == "follow":
|
elif notification.type == "follow":
|
||||||
buffers = ["followers"]
|
buffers = ["followers"]
|
||||||
obj = notification.account
|
obj = notification.account
|
||||||
@@ -287,4 +298,8 @@ class Session(base.baseSession):
|
|||||||
num = self.order_buffer(b, [obj])
|
num = self.order_buffer(b, [obj])
|
||||||
if num == 0:
|
if num == 0:
|
||||||
buffers.remove(b)
|
buffers.remove(b)
|
||||||
pub.sendMessage("mastodon.new_item", session_name=self.get_name(), item=obj, _buffers=buffers)
|
pub.sendMessage("mastodon.new_item", session_name=self.get_name(), item=obj, _buffers=buffers)
|
||||||
|
# Now, add notification to its buffer.
|
||||||
|
num = self.order_buffer("notifications", [notification])
|
||||||
|
if num > 0:
|
||||||
|
pub.sendMessage("mastodon.new_item", session_name=self.get_name(), item=notification, _buffers=["notifications"])
|
@@ -12,11 +12,13 @@ from . import utils, compose
|
|||||||
post_variables = ["date", "display_name", "screen_name", "source", "lang", "safe_text", "text", "image_descriptions", "visibility"]
|
post_variables = ["date", "display_name", "screen_name", "source", "lang", "safe_text", "text", "image_descriptions", "visibility"]
|
||||||
person_variables = ["display_name", "screen_name", "description", "followers", "following", "favorites", "posts", "created_at"]
|
person_variables = ["display_name", "screen_name", "description", "followers", "following", "favorites", "posts", "created_at"]
|
||||||
conversation_variables = ["users", "last_post"]
|
conversation_variables = ["users", "last_post"]
|
||||||
|
notification_variables = ["display_name", "screen_name", "text", "date"]
|
||||||
|
|
||||||
# Default, translatable templates.
|
# Default, translatable templates.
|
||||||
post_default_template = _("$display_name, $text $image_descriptions $date. $source")
|
post_default_template = _("$display_name, $text $image_descriptions $date. $source")
|
||||||
dm_sent_default_template = _("Dm to $recipient_display_name, $text $date")
|
dm_sent_default_template = _("Dm to $recipient_display_name, $text $date")
|
||||||
person_default_template = _("$display_name (@$screen_name). $followers followers, $following following, $posts posts. Joined $created_at.")
|
person_default_template = _("$display_name (@$screen_name). $followers followers, $following following, $posts posts. Joined $created_at.")
|
||||||
|
notification_default_template = _("$display_name $text, $date")
|
||||||
|
|
||||||
def process_date(field, relative_times=True, offset_hours=0):
|
def process_date(field, relative_times=True, offset_hours=0):
|
||||||
original_date = arrow.get(field)
|
original_date = arrow.get(field)
|
||||||
@@ -134,4 +136,46 @@ def render_conversation(conversation, template, post_template, relative_times=Fa
|
|||||||
available_data = dict(users=users, last_post=last_post)
|
available_data = dict(users=users, last_post=last_post)
|
||||||
result = Template(_(template)).safe_substitute(**available_data)
|
result = Template(_(template)).safe_substitute(**available_data)
|
||||||
result = remove_unneeded_variables(result, conversation_variables)
|
result = remove_unneeded_variables(result, conversation_variables)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def render_notification(notification, template, post_template, relative_times=False, offset_hours=0):
|
||||||
|
""" Renders any given notification according to the passed template.
|
||||||
|
Available data for notifications will be stored in the following variables:
|
||||||
|
$date: Creation date.
|
||||||
|
$display_name: User profile name.
|
||||||
|
$screen_name: User screen name, this is the same name used to reference the user in Twitter.
|
||||||
|
$text: Notification text, describing the action.
|
||||||
|
"""
|
||||||
|
global notification_variables
|
||||||
|
available_data = dict()
|
||||||
|
created_at = process_date(notification.created_at, relative_times, offset_hours)
|
||||||
|
available_data.update(date=created_at)
|
||||||
|
# user.
|
||||||
|
display_name = notification.account.display_name
|
||||||
|
if display_name == "":
|
||||||
|
display_name = notification.account.username
|
||||||
|
available_data.update(display_name=display_name, screen_name=notification.account.acct)
|
||||||
|
text = "Unknown: %r" % (notification)
|
||||||
|
# Remove date from status, so it won't be rendered twice.
|
||||||
|
post_template = post_template.replace("$date", "")
|
||||||
|
if notification.type == "status":
|
||||||
|
text = _("has posted: {status}").format(status=render_post(notification.status, post_template, relative_times, offset_hours))
|
||||||
|
elif notification.type == "mention":
|
||||||
|
text = _("has mentionned you: {status}").format(status=render_post(notification.status, post_template, relative_times, offset_hours))
|
||||||
|
elif notification.type == "reblog":
|
||||||
|
text = _("has boosted: {status}").format(status=render_post(notification.status, post_template, relative_times, offset_hours))
|
||||||
|
elif notification.type == "favourite":
|
||||||
|
text = _("has added to favorites: {status}").format(status=render_post(notification.status, post_template, relative_times, offset_hours))
|
||||||
|
elif notification.type == "update":
|
||||||
|
text = _("has updated a status: {status}").format(status=render_post(notification.status, post_template, relative_times, offset_hours))
|
||||||
|
elif notification.type == "follow":
|
||||||
|
text = _("has followed you.")
|
||||||
|
elif notification.type == "poll":
|
||||||
|
text = _("A poll in which you have voted has expired: {status}").format(status=render_post(notification.status, post_template, relative_times, offset_hours))
|
||||||
|
elif notification.type == "follow_request":
|
||||||
|
text = _("wants to follow you.")
|
||||||
|
available_data.update(text=text)
|
||||||
|
result = Template(_(template)).safe_substitute(**available_data)
|
||||||
|
result = remove_unneeded_variables(result, post_variables)
|
||||||
|
result = result.replace(" . ", "")
|
||||||
|
return result
|
||||||
|
@@ -5,12 +5,19 @@ url_re = re.compile('<a\s*href=[\'|"](.*?)[\'"].*?>')
|
|||||||
|
|
||||||
class HTMLFilter(HTMLParser):
|
class HTMLFilter(HTMLParser):
|
||||||
text = ""
|
text = ""
|
||||||
|
first_paragraph = True
|
||||||
|
|
||||||
def handle_data(self, data):
|
def handle_data(self, data):
|
||||||
self.text += data
|
self.text += data
|
||||||
|
|
||||||
def handle_starttag(self, tag, attrs):
|
def handle_starttag(self, tag, attrs):
|
||||||
if tag == "br":
|
if tag == "br":
|
||||||
self.text = self.text+"\n"
|
self.text = self.text+"\n"
|
||||||
|
elif tag == "p":
|
||||||
|
if self.first_paragraph:
|
||||||
|
self.first_paragraph = False
|
||||||
|
else:
|
||||||
|
self.text = self.text+"\n\n"
|
||||||
|
|
||||||
def html_filter(data):
|
def html_filter(data):
|
||||||
f = HTMLFilter()
|
f = HTMLFilter()
|
||||||
|
@@ -1,35 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import wx
|
|
||||||
|
|
||||||
class authorisationDialog(wx.Dialog):
|
|
||||||
def __init__(self):
|
|
||||||
super(authorisationDialog, self).__init__(parent=None, title=_(u"Authorising account..."))
|
|
||||||
panel = wx.Panel(self)
|
|
||||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
||||||
static1 = wx.StaticText(panel, wx.NewId(), _("URL of mastodon instance:"))
|
|
||||||
self.instance = wx.TextCtrl(panel, -1)
|
|
||||||
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
sizer1.Add(static1, 0, wx.ALL, 5)
|
|
||||||
sizer1.Add(self.instance, 0, wx.ALL, 5)
|
|
||||||
sizer.Add(sizer1, 0, wx.ALL, 5)
|
|
||||||
static2 = wx.StaticText(panel, wx.NewId(), _("Email address:"))
|
|
||||||
self.email = wx.TextCtrl(panel, -1)
|
|
||||||
sizer2 = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
sizer2.Add(static2, 0, wx.ALL, 5)
|
|
||||||
sizer2.Add(self.email, 0, wx.ALL, 5)
|
|
||||||
sizer.Add(sizer2, 0, wx.ALL, 5)
|
|
||||||
static3 = wx.StaticText(panel, wx.NewId(), _("Password:"))
|
|
||||||
self.password = wx.TextCtrl(panel, -1)
|
|
||||||
sizer3 = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
sizer3.Add(static3, 0, wx.ALL, 5)
|
|
||||||
sizer3.Add(self.password, 0, wx.ALL, 5)
|
|
||||||
sizer.Add(sizer3, 0, wx.ALL, 5)
|
|
||||||
self.ok = wx.Button(panel, wx.ID_OK)
|
|
||||||
self.cancel = wx.Button(panel, wx.ID_CANCEL)
|
|
||||||
sizer4 = wx.BoxSizer(wx.HORIZONTAL)
|
|
||||||
sizer4.Add(self.ok, 0, wx.ALL, 5)
|
|
||||||
sizer4.Add(self.cancel, 0, wx.ALL, 5)
|
|
||||||
sizer.Add(sizer4, 0, wx.ALL, 5)
|
|
||||||
panel.SetSizer(sizer)
|
|
||||||
min = sizer.CalcMin()
|
|
||||||
self.SetClientSize(min)
|
|
@@ -80,11 +80,11 @@ def render_tweet(tweet, template, session, relative_times=False, offset_seconds=
|
|||||||
available_data.update(source=tweet.source)
|
available_data.update(source=tweet.source)
|
||||||
if hasattr(tweet, "retweeted_status"):
|
if hasattr(tweet, "retweeted_status"):
|
||||||
if hasattr(tweet.retweeted_status, "quoted_status"):
|
if hasattr(tweet.retweeted_status, "quoted_status"):
|
||||||
text = "RT @{}: {} Quote from @{}: {}".format(session.get_user(tweet.retweeted_status.user).screen_name, process_text(tweet.retweeted_status), session.get_user(tweet.retweeted_status.quoted_status.user).screen_name, process_text(tweet.retweeted_status.quoted_status))
|
text = _("RT @{}: {} Quote from @{}: {}").format(session.get_user(tweet.retweeted_status.user).screen_name, process_text(tweet.retweeted_status), session.get_user(tweet.retweeted_status.quoted_status.user).screen_name, process_text(tweet.retweeted_status.quoted_status))
|
||||||
else:
|
else:
|
||||||
text = "RT @{}: {}".format(session.get_user(tweet.retweeted_status.user).screen_name, process_text(tweet.retweeted_status))
|
text = _("RT @{}: {}").format(session.get_user(tweet.retweeted_status.user).screen_name, process_text(tweet.retweeted_status))
|
||||||
elif hasattr(tweet, "quoted_status"):
|
elif hasattr(tweet, "quoted_status"):
|
||||||
text = "{} Quote from @{}: {}".format(process_text(tweet), session.get_user(tweet.quoted_status.user).screen_name, process_text(tweet.quoted_status))
|
text = _("{} Quote from @{}: {}").format(process_text(tweet), session.get_user(tweet.quoted_status.user).screen_name, process_text(tweet.quoted_status))
|
||||||
else:
|
else:
|
||||||
text = process_text(tweet)
|
text = process_text(tweet)
|
||||||
available_data.update(lang=tweet.lang, text=text)
|
available_data.update(lang=tweet.lang, text=text)
|
||||||
|
@@ -30,7 +30,7 @@ def progress_callback(total_downloaded, total_size):
|
|||||||
if total_downloaded == total_size:
|
if total_downloaded == total_size:
|
||||||
progress_dialog.Destroy()
|
progress_dialog.Destroy()
|
||||||
else:
|
else:
|
||||||
progress_dialog.Update(old_div((total_downloaded*100),total_size), _(u"Updating... %s of %s") % (str(utils.convert_bytes(total_downloaded)), str(utils.convert_bytes(total_size))))
|
progress_dialog.Update(int((total_downloaded*100)/total_size), _(u"Updating... %s of %s") % (str(utils.convert_bytes(total_downloaded)), str(utils.convert_bytes(total_size))))
|
||||||
|
|
||||||
def update_finished():
|
def update_finished():
|
||||||
ms = wx.MessageDialog(None, _(u"The update has been downloaded and installed successfully. Press OK to continue."), _(u"Done!")).ShowModal()
|
ms = wx.MessageDialog(None, _(u"The update has been downloaded and installed successfully. Press OK to continue."), _(u"Done!")).ShowModal()
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from .base import basePanel
|
from .base import basePanel
|
||||||
from .conversationList import conversationListPanel
|
from .conversationList import conversationListPanel
|
||||||
|
from .notifications import notificationsPanel
|
||||||
from .user import userPanel
|
from .user import userPanel
|
39
src/wxUI/buffers/mastodon/notifications.py
Normal file
39
src/wxUI/buffers/mastodon/notifications.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import wx
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
|
||||||
|
class notificationsPanel(wx.Panel):
|
||||||
|
|
||||||
|
def set_focus_function(self, f):
|
||||||
|
self.list.list.Bind(wx.EVT_LIST_ITEM_FOCUSED, f)
|
||||||
|
|
||||||
|
def create_list(self):
|
||||||
|
self.list = widgets.list(self, _("Text"), _("Date"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VRULES)
|
||||||
|
self.list.set_windows_size(0, 320)
|
||||||
|
self.list.set_windows_size(2, 110)
|
||||||
|
self.list.set_size()
|
||||||
|
|
||||||
|
def __init__(self, parent, name):
|
||||||
|
super(notificationsPanel, self).__init__(parent)
|
||||||
|
self.name = name
|
||||||
|
self.type = "baseBuffer"
|
||||||
|
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.create_list()
|
||||||
|
self.post = wx.Button(self, -1, _("Post"))
|
||||||
|
self.dismiss = wx.Button(self, -1, _("Dismiss"))
|
||||||
|
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnSizer.Add(self.post, 0, wx.ALL, 5)
|
||||||
|
btnSizer.Add(self.dismiss, 0, wx.ALL, 5)
|
||||||
|
self.sizer.Add(btnSizer, 0, wx.ALL, 5)
|
||||||
|
self.sizer.Add(self.list.list, 0, wx.ALL|wx.EXPAND, 5)
|
||||||
|
self.SetSizer(self.sizer)
|
||||||
|
self.SetClientSize(self.sizer.CalcMin())
|
||||||
|
|
||||||
|
def set_position(self, reversed=False):
|
||||||
|
if reversed == False:
|
||||||
|
self.list.select_item(self.list.get_count()-1)
|
||||||
|
else:
|
||||||
|
self.list.select_item(0)
|
||||||
|
|
||||||
|
def set_focus_in_list(self):
|
||||||
|
self.list.list.SetFocus()
|
@@ -22,6 +22,8 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog):
|
|||||||
sizer.Add(autocompletionSizer, 0, wx.ALL, 5)
|
sizer.Add(autocompletionSizer, 0, wx.ALL, 5)
|
||||||
self.relative_time = wx.CheckBox(self, wx.ID_ANY, _("Relative timestamps"))
|
self.relative_time = wx.CheckBox(self, wx.ID_ANY, _("Relative timestamps"))
|
||||||
sizer.Add(self.relative_time, 0, wx.ALL, 5)
|
sizer.Add(self.relative_time, 0, wx.ALL, 5)
|
||||||
|
self.read_preferences_from_instance = wx.CheckBox(self, wx.ID_ANY, _("Read preferences from instance (default visibility when publishing and displaying sensitive content)"))
|
||||||
|
sizer.Add(self.read_preferences_from_instance, 0, wx.ALL, 5)
|
||||||
itemsPerCallBox = wx.BoxSizer(wx.HORIZONTAL)
|
itemsPerCallBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
itemsPerCallBox.Add(wx.StaticText(self, -1, _("Items on each API call")), 0, wx.ALL, 5)
|
itemsPerCallBox.Add(wx.StaticText(self, -1, _("Items on each API call")), 0, wx.ALL, 5)
|
||||||
self.itemsPerApiCall = wx.SpinCtrl(self, wx.ID_ANY)
|
self.itemsPerApiCall = wx.SpinCtrl(self, wx.ID_ANY)
|
||||||
|
@@ -18,6 +18,14 @@ def delete_post_dialog():
|
|||||||
dlg.Destroy()
|
dlg.Destroy()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def delete_notification_dialog():
|
||||||
|
result = False
|
||||||
|
dlg = wx.MessageDialog(None, _("Are you sure you want to dismiss this notification? If you dismiss a mention notification, it also disappears from your mentions buffer. The post is not going to be deleted from the instance, though."), _("Dismiss"), wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
result = True
|
||||||
|
dlg.Destroy()
|
||||||
|
return result
|
||||||
|
|
||||||
def clear_list():
|
def clear_list():
|
||||||
result = False
|
result = False
|
||||||
dlg = wx.MessageDialog(None, _("Do you really want to empty this buffer? It's items will be removed from the list but not from the instance"), _(u"Empty buffer"), wx.ICON_QUESTION|wx.YES_NO)
|
dlg = wx.MessageDialog(None, _("Do you really want to empty this buffer? It's items will be removed from the list but not from the instance"), _(u"Empty buffer"), wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
@@ -43,13 +43,13 @@ class Post(wx.Dialog):
|
|||||||
main_sizer.Add(post_actions_sizer, 1, wx.EXPAND, 0)
|
main_sizer.Add(post_actions_sizer, 1, wx.EXPAND, 0)
|
||||||
visibility_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
visibility_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
post_actions_sizer.Add(visibility_sizer, 1, wx.EXPAND, 0)
|
post_actions_sizer.Add(visibility_sizer, 1, wx.EXPAND, 0)
|
||||||
label_1 = wx.StaticText(self, wx.ID_ANY, _("Visibility"))
|
label_1 = wx.StaticText(self, wx.ID_ANY, _("&Visibility"))
|
||||||
visibility_sizer.Add(label_1, 0, 0, 0)
|
visibility_sizer.Add(label_1, 0, 0, 0)
|
||||||
self.visibility = wx.ComboBox(self, wx.ID_ANY, choices=[_("Public"), _("Not listed"), _("Followers only"), _("Direct")], style=wx.CB_DROPDOWN | wx.CB_READONLY | wx.CB_SIMPLE)
|
self.visibility = wx.ComboBox(self, wx.ID_ANY, choices=[_("Public"), _("Not listed"), _("Followers only"), _("Direct")], style=wx.CB_DROPDOWN | wx.CB_READONLY | wx.CB_SIMPLE)
|
||||||
self.visibility.SetSelection(0)
|
self.visibility.SetSelection(0)
|
||||||
visibility_sizer.Add(self.visibility, 0, 0, 0)
|
visibility_sizer.Add(self.visibility, 0, 0, 0)
|
||||||
self.add = wx.Button(self, wx.ID_ANY, _("A&dd"))
|
self.add = wx.Button(self, wx.ID_ANY, _("A&dd"))
|
||||||
self.sensitive = wx.CheckBox(self, wx.ID_ANY, _("Sensitive content"))
|
self.sensitive = wx.CheckBox(self, wx.ID_ANY, _("S&ensitive content"))
|
||||||
self.sensitive.SetValue(False)
|
self.sensitive.SetValue(False)
|
||||||
self.sensitive.Bind(wx.EVT_CHECKBOX, self.on_sensitivity_changed)
|
self.sensitive.Bind(wx.EVT_CHECKBOX, self.on_sensitivity_changed)
|
||||||
main_sizer.Add(self.sensitive, 0, wx.ALL, 5)
|
main_sizer.Add(self.sensitive, 0, wx.ALL, 5)
|
||||||
|
134
src/wxUI/view.py
134
src/wxUI/view.py
@@ -1,6 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
from builtins import range
|
|
||||||
import wx
|
import wx
|
||||||
import wx.adv
|
import wx.adv
|
||||||
import application
|
import application
|
||||||
@@ -11,88 +9,88 @@ class mainFrame(wx.Frame):
|
|||||||
### MENU
|
### MENU
|
||||||
def makeMenus(self):
|
def makeMenus(self):
|
||||||
""" Creates, bind and returns the menu bar for the application. Also in this function, the accel table is created."""
|
""" Creates, bind and returns the menu bar for the application. Also in this function, the accel table is created."""
|
||||||
menuBar = wx.MenuBar()
|
self.menubar = wx.MenuBar()
|
||||||
|
|
||||||
# Application menu
|
# Application menu
|
||||||
app = wx.Menu()
|
self.menubar_application = wx.Menu()
|
||||||
self.manage_accounts = app.Append(wx.ID_ANY, _(u"&Manage accounts"))
|
self.manage_accounts = self.menubar_application.Append(wx.ID_ANY, _(u"&Manage accounts"))
|
||||||
self.updateProfile = app.Append(wx.ID_ANY, _(u"&Update profile"))
|
self.updateProfile = self.menubar_application.Append(wx.ID_ANY, _("&Update profile"))
|
||||||
self.show_hide = app.Append(wx.ID_ANY, _(u"&Hide window"))
|
self.show_hide = self.menubar_application.Append(wx.ID_ANY, _(u"&Hide window"))
|
||||||
self.menuitem_search = app.Append(wx.ID_ANY, _(u"&Search"))
|
self.menuitem_search = self.menubar_application.Append(wx.ID_ANY, _(u"&Search"))
|
||||||
self.lists = app.Append(wx.ID_ANY, _(u"&Lists manager"))
|
self.lists = self.menubar_application.Append(wx.ID_ANY, _(u"&Lists manager"))
|
||||||
self.manageAliases = app.Append(wx.ID_ANY, _("Manage user aliases"))
|
self.manageAliases = self.menubar_application.Append(wx.ID_ANY, _("Manage user aliases"))
|
||||||
self.keystroke_editor = app.Append(wx.ID_ANY, _(u"&Edit keystrokes"))
|
self.keystroke_editor = self.menubar_application.Append(wx.ID_ANY, _(u"&Edit keystrokes"))
|
||||||
self.account_settings = app.Append(wx.ID_ANY, _(u"Account se&ttings"))
|
self.account_settings = self.menubar_application.Append(wx.ID_ANY, _(u"Account se&ttings"))
|
||||||
self.prefs = app.Append(wx.ID_PREFERENCES, _(u"&Global settings"))
|
self.prefs = self.menubar_application.Append(wx.ID_PREFERENCES, _(u"&Global settings"))
|
||||||
self.close = app.Append(wx.ID_EXIT, _(u"E&xit"))
|
self.close = self.menubar_application.Append(wx.ID_EXIT, _(u"E&xit"))
|
||||||
|
|
||||||
# Tweet menu
|
# Tweet menu
|
||||||
tweet = wx.Menu()
|
self.menubar_item = wx.Menu()
|
||||||
self.compose = tweet.Append(wx.ID_ANY, _(u"&Tweet"))
|
self.compose = self.menubar_item.Append(wx.ID_ANY, _(u"&Tweet"))
|
||||||
self.reply = tweet.Append(wx.ID_ANY, _(u"Re&ply"))
|
self.reply = self.menubar_item.Append(wx.ID_ANY, _(u"Re&ply"))
|
||||||
self.retweet = tweet.Append(wx.ID_ANY, _(u"&Retweet"))
|
self.share = self.menubar_item.Append(wx.ID_ANY, _(u"&Retweet"))
|
||||||
self.fav = tweet.Append(wx.ID_ANY, _(u"&Like"))
|
self.fav = self.menubar_item.Append(wx.ID_ANY, _(u"&Like"))
|
||||||
self.unfav = tweet.Append(wx.ID_ANY, _(u"&Unlike"))
|
self.unfav = self.menubar_item.Append(wx.ID_ANY, _(u"&Unlike"))
|
||||||
self.view = tweet.Append(wx.ID_ANY, _(u"&Show tweet"))
|
self.view = self.menubar_item.Append(wx.ID_ANY, _(u"&Show tweet"))
|
||||||
self.view_coordinates = tweet.Append(wx.ID_ANY, _(u"View &address"))
|
self.view_coordinates = self.menubar_item.Append(wx.ID_ANY, _(u"View &address"))
|
||||||
self.view_conversation = tweet.Append(wx.ID_ANY, _(u"View conversa&tion"))
|
self.view_conversation = self.menubar_item.Append(wx.ID_ANY, _(u"View conversa&tion"))
|
||||||
self.ocr = tweet.Append(wx.ID_ANY, _(u"Read text in picture"))
|
self.ocr = self.menubar_item.Append(wx.ID_ANY, _(u"Read text in picture"))
|
||||||
self.delete = tweet.Append(wx.ID_ANY, _(u"&Delete"))
|
self.delete = self.menubar_item.Append(wx.ID_ANY, _(u"&Delete"))
|
||||||
|
|
||||||
# User menu
|
# User menu
|
||||||
user = wx.Menu()
|
self.menubar_user = wx.Menu()
|
||||||
self.follow = user.Append(wx.ID_ANY, _(u"&Actions..."))
|
self.follow = self.menubar_user.Append(wx.ID_ANY, _(u"&Actions..."))
|
||||||
self.timeline = user.Append(wx.ID_ANY, _(u"&View timeline..."))
|
self.timeline = self.menubar_user.Append(wx.ID_ANY, _(u"&View timeline..."))
|
||||||
self.dm = user.Append(wx.ID_ANY, _(u"Direct me&ssage"))
|
self.dm = self.menubar_user.Append(wx.ID_ANY, _(u"Direct me&ssage"))
|
||||||
self.addAlias = user.Append(wx.ID_ANY, _("Add a&lias"))
|
self.addAlias = self.menubar_user.Append(wx.ID_ANY, _("Add a&lias"))
|
||||||
self.addToList = user.Append(wx.ID_ANY, _(u"&Add to list"))
|
self.addToList = self.menubar_user.Append(wx.ID_ANY, _(u"&Add to list"))
|
||||||
self.removeFromList = user.Append(wx.ID_ANY, _(u"R&emove from list"))
|
self.removeFromList = self.menubar_user.Append(wx.ID_ANY, _(u"R&emove from list"))
|
||||||
self.viewLists = user.Append(wx.ID_ANY, _(u"&View lists"))
|
self.viewLists = self.menubar_user.Append(wx.ID_ANY, _(u"&View lists"))
|
||||||
self.details = user.Append(wx.ID_ANY, _(u"Show user &profile"))
|
self.details = self.menubar_user.Append(wx.ID_ANY, _(u"Show user &profile"))
|
||||||
self.favs = user.Append(wx.ID_ANY, _(u"V&iew likes"))
|
self.favs = self.menubar_user.Append(wx.ID_ANY, _(u"V&iew likes"))
|
||||||
|
|
||||||
# buffer menu
|
# buffer menu
|
||||||
buffer = wx.Menu()
|
self.menubar_buffer = wx.Menu()
|
||||||
self.update_buffer = buffer.Append(wx.ID_ANY, _(u"&Update buffer"))
|
self.update_buffer = self.menubar_buffer.Append(wx.ID_ANY, _(u"&Update buffer"))
|
||||||
self.trends = buffer.Append(wx.ID_ANY, _(u"New &trending topics buffer..."))
|
self.trends = self.menubar_buffer.Append(wx.ID_ANY, _(u"New &trending topics buffer..."))
|
||||||
self.filter = buffer.Append(wx.ID_ANY, _(u"Create a &filter"))
|
self.filter = self.menubar_buffer.Append(wx.ID_ANY, _(u"Create a &filter"))
|
||||||
self.manage_filters = buffer.Append(wx.ID_ANY, _(u"&Manage filters"))
|
self.manage_filters = self.menubar_buffer.Append(wx.ID_ANY, _(u"&Manage filters"))
|
||||||
self.find = buffer.Append(wx.ID_ANY, _(u"Find a string in the currently focused buffer..."))
|
self.find = self.menubar_buffer.Append(wx.ID_ANY, _(u"Find a string in the currently focused buffer..."))
|
||||||
self.load_previous_items = buffer.Append(wx.ID_ANY, _(u"&Load previous items"))
|
self.load_previous_items = self.menubar_buffer.Append(wx.ID_ANY, _(u"&Load previous items"))
|
||||||
buffer.AppendSeparator()
|
self.menubar_buffer.AppendSeparator()
|
||||||
self.mute_buffer = buffer.AppendCheckItem(wx.ID_ANY, _(u"&Mute"))
|
self.mute_buffer = self.menubar_buffer.AppendCheckItem(wx.ID_ANY, _(u"&Mute"))
|
||||||
self.autoread = buffer.AppendCheckItem(wx.ID_ANY, _(u"&Autoread"))
|
self.autoread = self.menubar_buffer.AppendCheckItem(wx.ID_ANY, _(u"&Autoread"))
|
||||||
self.clear = buffer.Append(wx.ID_ANY, _(u"&Clear buffer"))
|
self.clear = self.menubar_buffer.Append(wx.ID_ANY, _(u"&Clear buffer"))
|
||||||
self.deleteTl = buffer.Append(wx.ID_ANY, _(u"&Destroy"))
|
self.deleteTl = self.menubar_buffer.Append(wx.ID_ANY, _(u"&Destroy"))
|
||||||
|
|
||||||
# audio menu
|
# audio menu
|
||||||
audio = wx.Menu()
|
self.menubar_audio = wx.Menu()
|
||||||
self.seekLeft = audio.Append(wx.ID_ANY, _(u"&Seek back 5 seconds"))
|
self.seekLeft = self.menubar_audio.Append(wx.ID_ANY, _(u"&Seek back 5 seconds"))
|
||||||
self.seekRight = audio.Append(wx.ID_ANY, _(u"&Seek forward 5 seconds"))
|
self.seekRight = self.menubar_audio.Append(wx.ID_ANY, _(u"&Seek forward 5 seconds"))
|
||||||
|
|
||||||
# Help Menu
|
# Help Menu
|
||||||
help = wx.Menu()
|
self.menubar_help = wx.Menu()
|
||||||
self.doc = help.Append(-1, _(u"&Documentation"))
|
self.doc = self.menubar_help.Append(-1, _(u"&Documentation"))
|
||||||
self.sounds_tutorial = help.Append(wx.ID_ANY, _(u"Sounds &tutorial"))
|
self.sounds_tutorial = self.menubar_help.Append(wx.ID_ANY, _(u"Sounds &tutorial"))
|
||||||
self.changelog = help.Append(wx.ID_ANY, _(u"&What's new in this version?"))
|
self.changelog = self.menubar_help.Append(wx.ID_ANY, _(u"&What's new in this version?"))
|
||||||
self.check_for_updates = help.Append(wx.ID_ANY, _(u"&Check for updates"))
|
self.check_for_updates = self.menubar_help.Append(wx.ID_ANY, _(u"&Check for updates"))
|
||||||
self.reportError = help.Append(wx.ID_ANY, _(u"&Report an error"))
|
self.reportError = self.menubar_help.Append(wx.ID_ANY, _(u"&Report an error"))
|
||||||
self.visit_website = help.Append(-1, _(u"{0}'s &website").format(application.name,))
|
self.visit_website = self.menubar_help.Append(-1, _(u"{0}'s &website").format(application.name,))
|
||||||
self.get_soundpacks = help.Append(-1, _(u"Get soundpacks for TWBlue"))
|
self.get_soundpacks = self.menubar_help.Append(-1, _(u"Get soundpacks for TWBlue"))
|
||||||
self.about = help.Append(-1, _(u"About &{0}").format(application.name,))
|
self.about = self.menubar_help.Append(-1, _(u"About &{0}").format(application.name,))
|
||||||
|
|
||||||
# Add all to the menu Bar
|
# Add all to the menu Bar
|
||||||
menuBar.Append(app, _(u"&Application"))
|
self.menubar.Append(self.menubar_application, _(u"&Application"))
|
||||||
menuBar.Append(tweet, _(u"&Tweet"))
|
self.menubar.Append(self.menubar_item, _("&Tweet"))
|
||||||
menuBar.Append(user, _(u"&User"))
|
self.menubar.Append(self.menubar_user, _(u"&User"))
|
||||||
menuBar.Append(buffer, _(u"&Buffer"))
|
self.menubar.Append(self.menubar_buffer, _(u"&Buffer"))
|
||||||
menuBar.Append(audio, _(u"&Audio"))
|
self.menubar.Append(self.menubar_audio, _(u"&Audio"))
|
||||||
menuBar.Append(help, _(u"&Help"))
|
self.menubar.Append(self.menubar_help, _(u"&Help"))
|
||||||
|
|
||||||
self.accel_tbl = wx.AcceleratorTable([
|
self.accel_tbl = wx.AcceleratorTable([
|
||||||
(wx.ACCEL_CTRL, ord('N'), self.compose.GetId()),
|
(wx.ACCEL_CTRL, ord('N'), self.compose.GetId()),
|
||||||
(wx.ACCEL_CTRL, ord('R'), self.reply.GetId()),
|
(wx.ACCEL_CTRL, ord('R'), self.reply.GetId()),
|
||||||
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('R'), self.retweet.GetId()),
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('R'), self.share.GetId()),
|
||||||
(wx.ACCEL_CTRL, ord('F'), self.fav.GetId()),
|
(wx.ACCEL_CTRL, ord('F'), self.fav.GetId()),
|
||||||
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('F'), self.unfav.GetId()),
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('F'), self.unfav.GetId()),
|
||||||
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('V'), self.view.GetId()),
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('V'), self.view.GetId()),
|
||||||
@@ -110,7 +108,6 @@ class mainFrame(wx.Frame):
|
|||||||
])
|
])
|
||||||
|
|
||||||
self.SetAcceleratorTable(self.accel_tbl)
|
self.SetAcceleratorTable(self.accel_tbl)
|
||||||
return menuBar
|
|
||||||
|
|
||||||
### MAIN
|
### MAIN
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -119,7 +116,8 @@ class mainFrame(wx.Frame):
|
|||||||
self.panel = wx.Panel(self)
|
self.panel = wx.Panel(self)
|
||||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
self.SetTitle(application.name)
|
self.SetTitle(application.name)
|
||||||
self.SetMenuBar(self.makeMenus())
|
self.makeMenus()
|
||||||
|
self.SetMenuBar(self.menubar)
|
||||||
self.nb = wx.Treebook(self.panel, wx.ID_ANY)
|
self.nb = wx.Treebook(self.panel, wx.ID_ANY)
|
||||||
self.buffers = {}
|
self.buffers = {}
|
||||||
|
|
||||||
|
@@ -1,57 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Define paths for a regular use, if there are not paths for python32 or 64, these commands will be taken.
|
|
||||||
pythonpath32="/C/python27x86"
|
|
||||||
pythonpath64="/C/python27"
|
|
||||||
nsispath=$PROGRAMFILES/NSIS
|
|
||||||
|
|
||||||
help () {
|
|
||||||
echo -e "$0 | usage:"
|
|
||||||
echo -e "$0 | \t./build_twblue.sh [-py32path <path to python for 32 bits> | -py64path <path for python on 64 bits> | -nsyspath <path to nsys> | -h]"
|
|
||||||
}
|
|
||||||
|
|
||||||
# parsing options from the command line
|
|
||||||
while [[ $# > 1 ]]
|
|
||||||
do
|
|
||||||
key="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
case $key in
|
|
||||||
-py32path)
|
|
||||||
pythonpath32="$1"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-py64path)
|
|
||||||
pythonpath64="$1"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-nsispath)
|
|
||||||
nsispath="$1"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-help)
|
|
||||||
help
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
help
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
cd ../src
|
|
||||||
if [ -d build/ ];
|
|
||||||
then
|
|
||||||
rm -rf build
|
|
||||||
fi
|
|
||||||
if [ -d dist/ ];
|
|
||||||
then
|
|
||||||
rm -rf dist
|
|
||||||
fi
|
|
||||||
$pythonpath32/python.exe "setup.py" "py2exe" "--quiet"
|
|
||||||
mv -f dist ../scripts/TWBlue
|
|
||||||
rm -rf build
|
|
||||||
$pythonpath64/python.exe "setup.py" "py2exe" "--quiet"
|
|
||||||
mv -f dist ../scripts/TWBlue64
|
|
||||||
rm -rf build
|
|
||||||
cd ../scripts
|
|
||||||
#$nsispath/Unicode/makensis.exe "twblue.nsi"
|
|
||||||
#rm -rf TWBlue
|
|
||||||
#rm -rf TWBlue64
|
|
@@ -1,41 +0,0 @@
|
|||||||
import sys, os, os.path, glob, argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', default=False, dest='verbose')
|
|
||||||
verbose = parser.parse_args().verbose
|
|
||||||
|
|
||||||
srcpath = os.path.normpath(os.path.join(sys.path[0], "../src"))
|
|
||||||
file_count = 0
|
|
||||||
dir_count = 0
|
|
||||||
filenames = []
|
|
||||||
for (path, subdirs, files) in os.walk(srcpath):
|
|
||||||
filenames.extend(glob.glob(os.path.join(path, "*.pyc")))
|
|
||||||
# filenames.extend(glob.glob(os.path.join(path, "*.py")))
|
|
||||||
for filename in filenames:
|
|
||||||
try:
|
|
||||||
os.remove(filename)
|
|
||||||
if verbose: print "Removed " + filename
|
|
||||||
file_count += 1
|
|
||||||
except:
|
|
||||||
if verbose: print "Can't remove " + filename
|
|
||||||
|
|
||||||
#Remove empty directories.
|
|
||||||
if verbose: print "Removing empty directories..."
|
|
||||||
run_again = True
|
|
||||||
while run_again:
|
|
||||||
run_again = False
|
|
||||||
removals = []
|
|
||||||
for (path, subdirs, files) in os.walk(srcpath, topdown=False):
|
|
||||||
if len(subdirs) == 0 and len(files) == 0:
|
|
||||||
removals.append(path)
|
|
||||||
for path in removals:
|
|
||||||
try:
|
|
||||||
os.rmdir(path)
|
|
||||||
run_again = True
|
|
||||||
if verbose: print "Removed directory " + path
|
|
||||||
dir_count += 1
|
|
||||||
except:
|
|
||||||
if verbose: print "Can't remove directory " + path
|
|
||||||
|
|
||||||
print
|
|
||||||
print "{0} file(s), {1} dir(s) removed.".format(file_count, dir_count)
|
|
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
mkdir ../src/documentation
|
|
||||||
for i in `ls ../documentation`
|
|
||||||
do
|
|
||||||
if test -d ../documentation/$i
|
|
||||||
then
|
|
||||||
mkdir ../src/documentation/$i
|
|
||||||
pandoc -s ../documentation/$i/changes.md -o ../src/documentation/$i/changes.html
|
|
||||||
pandoc -s ../documentation/$i/manual.md -o ../src/documentation/$i/manual.html
|
|
||||||
cp ../documentation/license.txt ../src/documentation/license.txt
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
exit
|
|
@@ -1,5 +0,0 @@
|
|||||||
@echo off
|
|
||||||
echo Generating application translation strings...
|
|
||||||
C:\python27\python.exe pygettext.py -v -d twblue ../src/*.pyw ../src/*.py ../src/*/*.py ../src/*/*.pyw ../src/*/*/*.py ../src/*/*/*.pyw ../src/*/*/*/*.py ../src/*/*/*/*.pyw ../src/*/*/*/*/*.py ../src/*/*/*/*/*.pyw
|
|
||||||
C:\python27\python.exe pygettext.py -v -d twblue-documentation ../doc/strings.py
|
|
||||||
C:\python27\python.exe pygettext.py -v -d twblue-changelog ../doc/changelog.py
|
|
@@ -1,631 +0,0 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
# -*- coding: iso-8859-1 -*-
|
|
||||||
# Originally written by Barry Warsaw <barry@python.org>
|
|
||||||
#
|
|
||||||
# Minimally patched to make it even more xgettext compatible
|
|
||||||
# by Peter Funk <pf@artcom-gmbh.de>
|
|
||||||
#
|
|
||||||
# 2002-11-22 J<>rgen Hermann <jh@web.de>
|
|
||||||
# Added checks that _() only contains string literals, and
|
|
||||||
# command line args are resolved to module lists, i.e. you
|
|
||||||
# can now pass a filename, a module or package name, or a
|
|
||||||
# directory (including globbing chars, important for Win32).
|
|
||||||
# Made docstring fit in 80 chars wide displays using pydoc.
|
|
||||||
#
|
|
||||||
|
|
||||||
# for selftesting
|
|
||||||
try:
|
|
||||||
import fintl
|
|
||||||
_ = fintl.gettext
|
|
||||||
except ImportError:
|
|
||||||
_ = lambda s: s
|
|
||||||
|
|
||||||
__doc__ = _("""pygettext -- Python equivalent of xgettext(1)
|
|
||||||
|
|
||||||
Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the
|
|
||||||
internationalization of C programs. Most of these tools are independent of
|
|
||||||
the programming language and can be used from within Python programs.
|
|
||||||
Martin von Loewis' work[1] helps considerably in this regard.
|
|
||||||
|
|
||||||
There's one problem though; xgettext is the program that scans source code
|
|
||||||
looking for message strings, but it groks only C (or C++). Python
|
|
||||||
introduces a few wrinkles, such as dual quoting characters, triple quoted
|
|
||||||
strings, and raw strings. xgettext understands none of this.
|
|
||||||
|
|
||||||
Enter pygettext, which uses Python's standard tokenize module to scan
|
|
||||||
Python source code, generating .pot files identical to what GNU xgettext[2]
|
|
||||||
generates for C and C++ code. From there, the standard GNU tools can be
|
|
||||||
used.
|
|
||||||
|
|
||||||
A word about marking Python strings as candidates for translation. GNU
|
|
||||||
xgettext recognizes the following keywords: gettext, dgettext, dcgettext,
|
|
||||||
and gettext_noop. But those can be a lot of text to include all over your
|
|
||||||
code. C and C++ have a trick: they use the C preprocessor. Most
|
|
||||||
internationalized C source includes a #define for gettext() to _() so that
|
|
||||||
what has to be written in the source is much less. Thus these are both
|
|
||||||
translatable strings:
|
|
||||||
|
|
||||||
gettext("Translatable String")
|
|
||||||
_("Translatable String")
|
|
||||||
|
|
||||||
Python of course has no preprocessor so this doesn't work so well. Thus,
|
|
||||||
pygettext searches only for _() by default, but see the -k/--keyword flag
|
|
||||||
below for how to augment this.
|
|
||||||
|
|
||||||
[1] http://www.python.org/workshops/1997-10/proceedings/loewis.html
|
|
||||||
[2] http://www.gnu.org/software/gettext/gettext.html
|
|
||||||
|
|
||||||
NOTE: pygettext attempts to be option and feature compatible with GNU
|
|
||||||
xgettext where ever possible. However some options are still missing or are
|
|
||||||
not fully implemented. Also, xgettext's use of command line switches with
|
|
||||||
option arguments is broken, and in these cases, pygettext just defines
|
|
||||||
additional switches.
|
|
||||||
|
|
||||||
Usage: pygettext [options] inputfile ...
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
-a
|
|
||||||
--extract-all
|
|
||||||
Extract all strings.
|
|
||||||
|
|
||||||
-d name
|
|
||||||
--default-domain=name
|
|
||||||
Rename the default output file from messages.pot to name.pot.
|
|
||||||
|
|
||||||
-E
|
|
||||||
--escape
|
|
||||||
Replace non-ASCII characters with octal escape sequences.
|
|
||||||
|
|
||||||
-D
|
|
||||||
--docstrings
|
|
||||||
Extract module, class, method, and function docstrings. These do
|
|
||||||
not need to be wrapped in _() markers, and in fact cannot be for
|
|
||||||
Python to consider them docstrings. (See also the -X option).
|
|
||||||
|
|
||||||
-h
|
|
||||||
--help
|
|
||||||
Print this help message and exit.
|
|
||||||
|
|
||||||
-k word
|
|
||||||
--keyword=word
|
|
||||||
Keywords to look for in addition to the default set, which are:
|
|
||||||
%(DEFAULTKEYWORDS)s
|
|
||||||
|
|
||||||
You can have multiple -k flags on the command line.
|
|
||||||
|
|
||||||
-K
|
|
||||||
--no-default-keywords
|
|
||||||
Disable the default set of keywords (see above). Any keywords
|
|
||||||
explicitly added with the -k/--keyword option are still recognized.
|
|
||||||
|
|
||||||
--no-location
|
|
||||||
Do not write filename/lineno location comments.
|
|
||||||
|
|
||||||
-n
|
|
||||||
--add-location
|
|
||||||
Write filename/lineno location comments indicating where each
|
|
||||||
extracted string is found in the source. These lines appear before
|
|
||||||
each msgid. The style of comments is controlled by the -S/--style
|
|
||||||
option. This is the default.
|
|
||||||
|
|
||||||
-o filename
|
|
||||||
--output=filename
|
|
||||||
Rename the default output file from messages.pot to filename. If
|
|
||||||
filename is `-' then the output is sent to standard out.
|
|
||||||
|
|
||||||
-p dir
|
|
||||||
--output-dir=dir
|
|
||||||
Output files will be placed in directory dir.
|
|
||||||
|
|
||||||
-S stylename
|
|
||||||
--style stylename
|
|
||||||
Specify which style to use for location comments. Two styles are
|
|
||||||
supported:
|
|
||||||
|
|
||||||
Solaris # File: filename, line: line-number
|
|
||||||
GNU #: filename:line
|
|
||||||
|
|
||||||
The style name is case insensitive. GNU style is the default.
|
|
||||||
|
|
||||||
-v
|
|
||||||
--verbose
|
|
||||||
Print the names of the files being processed.
|
|
||||||
|
|
||||||
-V
|
|
||||||
--version
|
|
||||||
Print the version of pygettext and exit.
|
|
||||||
|
|
||||||
-w columns
|
|
||||||
--width=columns
|
|
||||||
Set width of output to columns.
|
|
||||||
|
|
||||||
-x filename
|
|
||||||
--exclude-file=filename
|
|
||||||
Specify a file that contains a list of strings that are not be
|
|
||||||
extracted from the input files. Each string to be excluded must
|
|
||||||
appear on a line by itself in the file.
|
|
||||||
|
|
||||||
-X filename
|
|
||||||
--no-docstrings=filename
|
|
||||||
Specify a file that contains a list of files (one per line) that
|
|
||||||
should not have their docstrings extracted. This is only useful in
|
|
||||||
conjunction with the -D option above.
|
|
||||||
|
|
||||||
If `inputfile' is -, standard input is read.
|
|
||||||
""")
|
|
||||||
|
|
||||||
import os
|
|
||||||
import importlib.machinery
|
|
||||||
import importlib.util
|
|
||||||
import sys
|
|
||||||
import glob
|
|
||||||
import time
|
|
||||||
import getopt
|
|
||||||
import token
|
|
||||||
import tokenize
|
|
||||||
|
|
||||||
__version__ = '1.5'
|
|
||||||
|
|
||||||
default_keywords = ['_']
|
|
||||||
DEFAULTKEYWORDS = ', '.join(default_keywords)
|
|
||||||
|
|
||||||
EMPTYSTRING = ''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
|
|
||||||
# there.
|
|
||||||
pot_header = _('''\
|
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR ORGANIZATION
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\\n"
|
|
||||||
"POT-Creation-Date: %(time)s\\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\\n"
|
|
||||||
"MIME-Version: 1.0\\n"
|
|
||||||
"Content-Type: text/plain; charset=%(charset)s\\n"
|
|
||||||
"Content-Transfer-Encoding: %(encoding)s\\n"
|
|
||||||
"Generated-By: pygettext.py %(version)s\\n"
|
|
||||||
|
|
||||||
''')
|
|
||||||
|
|
||||||
|
|
||||||
def usage(code, msg=''):
|
|
||||||
print(__doc__ % globals(), file=sys.stderr)
|
|
||||||
if msg:
|
|
||||||
print(msg, file=sys.stderr)
|
|
||||||
sys.exit(code)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def make_escapes(pass_nonascii):
|
|
||||||
global escapes, escape
|
|
||||||
if pass_nonascii:
|
|
||||||
# Allow non-ascii characters to pass through so that e.g. 'msgid
|
|
||||||
# "H<>he"' would result not result in 'msgid "H\366he"'. Otherwise we
|
|
||||||
# escape any character outside the 32..126 range.
|
|
||||||
mod = 128
|
|
||||||
escape = escape_ascii
|
|
||||||
else:
|
|
||||||
mod = 256
|
|
||||||
escape = escape_nonascii
|
|
||||||
escapes = [r"\%03o" % i for i in range(mod)]
|
|
||||||
for i in range(32, 127):
|
|
||||||
escapes[i] = chr(i)
|
|
||||||
escapes[ord('\\')] = r'\\'
|
|
||||||
escapes[ord('\t')] = r'\t'
|
|
||||||
escapes[ord('\r')] = r'\r'
|
|
||||||
escapes[ord('\n')] = r'\n'
|
|
||||||
escapes[ord('\"')] = r'\"'
|
|
||||||
|
|
||||||
|
|
||||||
def escape_ascii(s, encoding):
|
|
||||||
return ''.join(escapes[ord(c)] if ord(c) < 128 else c for c in s)
|
|
||||||
|
|
||||||
def escape_nonascii(s, encoding):
|
|
||||||
return ''.join(escapes[b] for b in s.encode(encoding))
|
|
||||||
|
|
||||||
|
|
||||||
def is_literal_string(s):
|
|
||||||
return s[0] in '\'"' or (s[0] in 'rRuU' and s[1] in '\'"')
|
|
||||||
|
|
||||||
|
|
||||||
def safe_eval(s):
|
|
||||||
# unwrap quotes, safely
|
|
||||||
return eval(s, {'__builtins__':{}}, {})
|
|
||||||
|
|
||||||
|
|
||||||
def normalize(s, encoding):
|
|
||||||
# This converts the various Python string types into a format that is
|
|
||||||
# appropriate for .po files, namely much closer to C style.
|
|
||||||
lines = s.split('\n')
|
|
||||||
if len(lines) == 1:
|
|
||||||
s = '"' + escape(s, encoding) + '"'
|
|
||||||
else:
|
|
||||||
if not lines[-1]:
|
|
||||||
del lines[-1]
|
|
||||||
lines[-1] = lines[-1] + '\n'
|
|
||||||
for i in range(len(lines)):
|
|
||||||
lines[i] = escape(lines[i], encoding)
|
|
||||||
lineterm = '\\n"\n"'
|
|
||||||
s = '""\n"' + lineterm.join(lines) + '"'
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def containsAny(str, set):
|
|
||||||
"""Check whether 'str' contains ANY of the chars in 'set'"""
|
|
||||||
return 1 in [c in str for c in set]
|
|
||||||
|
|
||||||
|
|
||||||
def getFilesForName(name):
|
|
||||||
"""Get a list of module files for a filename, a module or package name,
|
|
||||||
or a directory.
|
|
||||||
"""
|
|
||||||
if not os.path.exists(name):
|
|
||||||
# check for glob chars
|
|
||||||
if containsAny(name, "*?[]"):
|
|
||||||
files = glob.glob(name)
|
|
||||||
list = []
|
|
||||||
for file in files:
|
|
||||||
list.extend(getFilesForName(file))
|
|
||||||
return list
|
|
||||||
|
|
||||||
# try to find module or package
|
|
||||||
try:
|
|
||||||
spec = importlib.util.find_spec(name)
|
|
||||||
name = spec.origin
|
|
||||||
except ImportError:
|
|
||||||
name = None
|
|
||||||
if not name:
|
|
||||||
return []
|
|
||||||
|
|
||||||
if os.path.isdir(name):
|
|
||||||
# find all python files in directory
|
|
||||||
list = []
|
|
||||||
# get extension for python source files
|
|
||||||
_py_ext = importlib.machinery.SOURCE_SUFFIXES[0]
|
|
||||||
for root, dirs, files in os.walk(name):
|
|
||||||
# don't recurse into CVS directories
|
|
||||||
if 'CVS' in dirs:
|
|
||||||
dirs.remove('CVS')
|
|
||||||
# add all *.py files to list
|
|
||||||
list.extend(
|
|
||||||
[os.path.join(root, file) for file in files
|
|
||||||
if os.path.splitext(file)[1] == _py_ext]
|
|
||||||
)
|
|
||||||
return list
|
|
||||||
elif os.path.exists(name):
|
|
||||||
# a single file
|
|
||||||
return [name]
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class TokenEater:
|
|
||||||
def __init__(self, options):
|
|
||||||
self.__options = options
|
|
||||||
self.__messages = {}
|
|
||||||
self.__state = self.__waiting
|
|
||||||
self.__data = []
|
|
||||||
self.__lineno = -1
|
|
||||||
self.__freshmodule = 1
|
|
||||||
self.__curfile = None
|
|
||||||
self.__enclosurecount = 0
|
|
||||||
|
|
||||||
def __call__(self, ttype, tstring, stup, etup, line):
|
|
||||||
# dispatch
|
|
||||||
## import token
|
|
||||||
## print('ttype:', token.tok_name[ttype], 'tstring:', tstring,
|
|
||||||
## file=sys.stderr)
|
|
||||||
self.__state(ttype, tstring, stup[0])
|
|
||||||
|
|
||||||
def __waiting(self, ttype, tstring, lineno):
|
|
||||||
opts = self.__options
|
|
||||||
# Do docstring extractions, if enabled
|
|
||||||
if opts.docstrings and not opts.nodocstrings.get(self.__curfile):
|
|
||||||
# module docstring?
|
|
||||||
if self.__freshmodule:
|
|
||||||
if ttype == tokenize.STRING and is_literal_string(tstring):
|
|
||||||
self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
|
|
||||||
self.__freshmodule = 0
|
|
||||||
elif ttype not in (tokenize.COMMENT, tokenize.NL):
|
|
||||||
self.__freshmodule = 0
|
|
||||||
return
|
|
||||||
# class or func/method docstring?
|
|
||||||
if ttype == tokenize.NAME and tstring in ('class', 'def'):
|
|
||||||
self.__state = self.__suiteseen
|
|
||||||
return
|
|
||||||
if ttype == tokenize.NAME and tstring in opts.keywords:
|
|
||||||
self.__state = self.__keywordseen
|
|
||||||
|
|
||||||
def __suiteseen(self, ttype, tstring, lineno):
|
|
||||||
# skip over any enclosure pairs until we see the colon
|
|
||||||
if ttype == tokenize.OP:
|
|
||||||
if tstring == ':' and self.__enclosurecount == 0:
|
|
||||||
# we see a colon and we're not in an enclosure: end of def
|
|
||||||
self.__state = self.__suitedocstring
|
|
||||||
elif tstring in '([{':
|
|
||||||
self.__enclosurecount += 1
|
|
||||||
elif tstring in ')]}':
|
|
||||||
self.__enclosurecount -= 1
|
|
||||||
|
|
||||||
def __suitedocstring(self, ttype, tstring, lineno):
|
|
||||||
# ignore any intervening noise
|
|
||||||
if ttype == tokenize.STRING and is_literal_string(tstring):
|
|
||||||
self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
|
|
||||||
self.__state = self.__waiting
|
|
||||||
elif ttype not in (tokenize.NEWLINE, tokenize.INDENT,
|
|
||||||
tokenize.COMMENT):
|
|
||||||
# there was no class docstring
|
|
||||||
self.__state = self.__waiting
|
|
||||||
|
|
||||||
def __keywordseen(self, ttype, tstring, lineno):
|
|
||||||
if ttype == tokenize.OP and tstring == '(':
|
|
||||||
self.__data = []
|
|
||||||
self.__lineno = lineno
|
|
||||||
self.__state = self.__openseen
|
|
||||||
else:
|
|
||||||
self.__state = self.__waiting
|
|
||||||
|
|
||||||
def __openseen(self, ttype, tstring, lineno):
|
|
||||||
if ttype == tokenize.OP and tstring == ')':
|
|
||||||
# We've seen the last of the translatable strings. Record the
|
|
||||||
# line number of the first line of the strings and update the list
|
|
||||||
# of messages seen. Reset state for the next batch. If there
|
|
||||||
# were no strings inside _(), then just ignore this entry.
|
|
||||||
if self.__data:
|
|
||||||
self.__addentry(EMPTYSTRING.join(self.__data))
|
|
||||||
self.__state = self.__waiting
|
|
||||||
elif ttype == tokenize.STRING and is_literal_string(tstring):
|
|
||||||
self.__data.append(safe_eval(tstring))
|
|
||||||
elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT,
|
|
||||||
token.NEWLINE, tokenize.NL]:
|
|
||||||
# warn if we see anything else than STRING or whitespace
|
|
||||||
print(_(
|
|
||||||
'*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"'
|
|
||||||
) % {
|
|
||||||
'token': tstring,
|
|
||||||
'file': self.__curfile,
|
|
||||||
'lineno': self.__lineno
|
|
||||||
}, file=sys.stderr)
|
|
||||||
self.__state = self.__waiting
|
|
||||||
|
|
||||||
def __addentry(self, msg, lineno=None, isdocstring=0):
|
|
||||||
if lineno is None:
|
|
||||||
lineno = self.__lineno
|
|
||||||
if not msg in self.__options.toexclude:
|
|
||||||
entry = (self.__curfile, lineno)
|
|
||||||
self.__messages.setdefault(msg, {})[entry] = isdocstring
|
|
||||||
|
|
||||||
def set_filename(self, filename):
|
|
||||||
self.__curfile = filename
|
|
||||||
self.__freshmodule = 1
|
|
||||||
|
|
||||||
def write(self, fp):
|
|
||||||
options = self.__options
|
|
||||||
timestamp = time.strftime('%Y-%m-%d %H:%M%z')
|
|
||||||
encoding = fp.encoding if fp.encoding else 'UTF-8'
|
|
||||||
print(pot_header % {'time': timestamp, 'version': __version__,
|
|
||||||
'charset': encoding,
|
|
||||||
'encoding': '8bit'}, file=fp)
|
|
||||||
# Sort the entries. First sort each particular entry's keys, then
|
|
||||||
# sort all the entries by their first item.
|
|
||||||
reverse = {}
|
|
||||||
for k, v in self.__messages.items():
|
|
||||||
keys = sorted(v.keys())
|
|
||||||
reverse.setdefault(tuple(keys), []).append((k, v))
|
|
||||||
rkeys = sorted(reverse.keys())
|
|
||||||
for rkey in rkeys:
|
|
||||||
rentries = reverse[rkey]
|
|
||||||
rentries.sort()
|
|
||||||
for k, v in rentries:
|
|
||||||
# If the entry was gleaned out of a docstring, then add a
|
|
||||||
# comment stating so. This is to aid translators who may wish
|
|
||||||
# to skip translating some unimportant docstrings.
|
|
||||||
isdocstring = any(v.values())
|
|
||||||
# k is the message string, v is a dictionary-set of (filename,
|
|
||||||
# lineno) tuples. We want to sort the entries in v first by
|
|
||||||
# file name and then by line number.
|
|
||||||
v = sorted(v.keys())
|
|
||||||
if not options.writelocations:
|
|
||||||
pass
|
|
||||||
# location comments are different b/w Solaris and GNU:
|
|
||||||
elif options.locationstyle == options.SOLARIS:
|
|
||||||
for filename, lineno in v:
|
|
||||||
d = {'filename': filename, 'lineno': lineno}
|
|
||||||
print(_(
|
|
||||||
'# File: %(filename)s, line: %(lineno)d') % d, file=fp)
|
|
||||||
elif options.locationstyle == options.GNU:
|
|
||||||
# fit as many locations on one line, as long as the
|
|
||||||
# resulting line length doesn't exceed 'options.width'
|
|
||||||
locline = '#:'
|
|
||||||
for filename, lineno in v:
|
|
||||||
d = {'filename': filename, 'lineno': lineno}
|
|
||||||
s = _(' %(filename)s:%(lineno)d') % d
|
|
||||||
if len(locline) + len(s) <= options.width:
|
|
||||||
locline = locline + s
|
|
||||||
else:
|
|
||||||
print(locline, file=fp)
|
|
||||||
locline = "#:" + s
|
|
||||||
if len(locline) > 2:
|
|
||||||
print(locline, file=fp)
|
|
||||||
if isdocstring:
|
|
||||||
print('#, docstring', file=fp)
|
|
||||||
print('msgid', normalize(k, encoding), file=fp)
|
|
||||||
print('msgstr ""\n', file=fp)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
global default_keywords
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(
|
|
||||||
sys.argv[1:],
|
|
||||||
'ad:DEhk:Kno:p:S:Vvw:x:X:',
|
|
||||||
['extract-all', 'default-domain=', 'escape', 'help',
|
|
||||||
'keyword=', 'no-default-keywords',
|
|
||||||
'add-location', 'no-location', 'output=', 'output-dir=',
|
|
||||||
'style=', 'verbose', 'version', 'width=', 'exclude-file=',
|
|
||||||
'docstrings', 'no-docstrings',
|
|
||||||
])
|
|
||||||
except getopt.error as msg:
|
|
||||||
usage(1, msg)
|
|
||||||
|
|
||||||
# for holding option values
|
|
||||||
class Options:
|
|
||||||
# constants
|
|
||||||
GNU = 1
|
|
||||||
SOLARIS = 2
|
|
||||||
# defaults
|
|
||||||
extractall = 0 # FIXME: currently this option has no effect at all.
|
|
||||||
escape = 0
|
|
||||||
keywords = []
|
|
||||||
outpath = ''
|
|
||||||
outfile = 'messages.pot'
|
|
||||||
writelocations = 1
|
|
||||||
locationstyle = GNU
|
|
||||||
verbose = 0
|
|
||||||
width = 78
|
|
||||||
excludefilename = ''
|
|
||||||
docstrings = 0
|
|
||||||
nodocstrings = {}
|
|
||||||
|
|
||||||
options = Options()
|
|
||||||
locations = {'gnu' : options.GNU,
|
|
||||||
'solaris' : options.SOLARIS,
|
|
||||||
}
|
|
||||||
|
|
||||||
# parse options
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt in ('-h', '--help'):
|
|
||||||
usage(0)
|
|
||||||
elif opt in ('-a', '--extract-all'):
|
|
||||||
options.extractall = 1
|
|
||||||
elif opt in ('-d', '--default-domain'):
|
|
||||||
options.outfile = arg + '.pot'
|
|
||||||
elif opt in ('-E', '--escape'):
|
|
||||||
options.escape = 1
|
|
||||||
elif opt in ('-D', '--docstrings'):
|
|
||||||
options.docstrings = 1
|
|
||||||
elif opt in ('-k', '--keyword'):
|
|
||||||
options.keywords.append(arg)
|
|
||||||
elif opt in ('-K', '--no-default-keywords'):
|
|
||||||
default_keywords = []
|
|
||||||
elif opt in ('-n', '--add-location'):
|
|
||||||
options.writelocations = 1
|
|
||||||
elif opt in ('--no-location',):
|
|
||||||
options.writelocations = 0
|
|
||||||
elif opt in ('-S', '--style'):
|
|
||||||
options.locationstyle = locations.get(arg.lower())
|
|
||||||
if options.locationstyle is None:
|
|
||||||
usage(1, _('Invalid value for --style: %s') % arg)
|
|
||||||
elif opt in ('-o', '--output'):
|
|
||||||
options.outfile = arg
|
|
||||||
elif opt in ('-p', '--output-dir'):
|
|
||||||
options.outpath = arg
|
|
||||||
elif opt in ('-v', '--verbose'):
|
|
||||||
options.verbose = 1
|
|
||||||
elif opt in ('-V', '--version'):
|
|
||||||
print(_('pygettext.py (xgettext for Python) %s') % __version__)
|
|
||||||
sys.exit(0)
|
|
||||||
elif opt in ('-w', '--width'):
|
|
||||||
try:
|
|
||||||
options.width = int(arg)
|
|
||||||
except ValueError:
|
|
||||||
usage(1, _('--width argument must be an integer: %s') % arg)
|
|
||||||
elif opt in ('-x', '--exclude-file'):
|
|
||||||
options.excludefilename = arg
|
|
||||||
elif opt in ('-X', '--no-docstrings'):
|
|
||||||
fp = open(arg)
|
|
||||||
try:
|
|
||||||
while 1:
|
|
||||||
line = fp.readline()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
options.nodocstrings[line[:-1]] = 1
|
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
# calculate escapes
|
|
||||||
make_escapes(not options.escape)
|
|
||||||
|
|
||||||
# calculate all keywords
|
|
||||||
options.keywords.extend(default_keywords)
|
|
||||||
|
|
||||||
# initialize list of strings to exclude
|
|
||||||
if options.excludefilename:
|
|
||||||
try:
|
|
||||||
fp = open(options.excludefilename)
|
|
||||||
options.toexclude = fp.readlines()
|
|
||||||
fp.close()
|
|
||||||
except IOError:
|
|
||||||
print(_(
|
|
||||||
"Can't read --exclude-file: %s") % options.excludefilename, file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
options.toexclude = []
|
|
||||||
|
|
||||||
# resolve args to module lists
|
|
||||||
expanded = []
|
|
||||||
for arg in args:
|
|
||||||
if arg == '-':
|
|
||||||
expanded.append(arg)
|
|
||||||
else:
|
|
||||||
expanded.extend(getFilesForName(arg))
|
|
||||||
args = expanded
|
|
||||||
|
|
||||||
# slurp through all the files
|
|
||||||
eater = TokenEater(options)
|
|
||||||
for filename in args:
|
|
||||||
if filename == '-':
|
|
||||||
if options.verbose:
|
|
||||||
print(_('Reading standard input'))
|
|
||||||
fp = sys.stdin.buffer
|
|
||||||
closep = 0
|
|
||||||
else:
|
|
||||||
if options.verbose:
|
|
||||||
print(_('Working on %s') % filename)
|
|
||||||
fp = open(filename, 'rb')
|
|
||||||
closep = 1
|
|
||||||
try:
|
|
||||||
eater.set_filename(filename)
|
|
||||||
try:
|
|
||||||
tokens = tokenize.tokenize(fp.readline)
|
|
||||||
for _token in tokens:
|
|
||||||
eater(*_token)
|
|
||||||
except tokenize.TokenError as e:
|
|
||||||
print('%s: %s, line %d, column %d' % (
|
|
||||||
e.args[0], filename, e.args[1][0], e.args[1][1]),
|
|
||||||
file=sys.stderr)
|
|
||||||
finally:
|
|
||||||
if closep:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
# write the output
|
|
||||||
if options.outfile == '-':
|
|
||||||
fp = sys.stdout
|
|
||||||
closep = 0
|
|
||||||
else:
|
|
||||||
if options.outpath:
|
|
||||||
options.outfile = os.path.join(options.outpath, options.outfile)
|
|
||||||
fp = open(options.outfile, 'w')
|
|
||||||
closep = 1
|
|
||||||
try:
|
|
||||||
eater.write(fp)
|
|
||||||
finally:
|
|
||||||
if closep:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
# some more test strings
|
|
||||||
# this one creates a warning
|
|
||||||
_('*** Seen unexpected token "%(token)s"') % {'token': 'test'}
|
|
||||||
_('more' 'than' 'one' 'string')
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3845
tools/twblue.pot
3845
tools/twblue.pot
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
{"current_version": "2022.8.28",
|
{"current_version": "2023.2.3",
|
||||||
"description": "Added support for creating threads, upload videos and polls to Twitter.",
|
"description": "Stops support for Twitter session on Feb 9, updates to mastodon sessions. Please avoid using autoupdate as it will not work.",
|
||||||
"date": "unknown",
|
"date": "unknown",
|
||||||
"downloads":
|
"downloads":
|
||||||
{"Windows32": "https://twblue.es/pubs/twblue_x86.zip",
|
{"Windows32": "https://twblue.es/pubs/twblue_x86.zip",
|
||||||
|
Reference in New Issue
Block a user