mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2025-08-25 09:29:22 +00:00
Compare commits
284 Commits
v2022.8.28
...
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 | |||
11b6669bf6 | |||
3adc726f33 | |||
a3bc684721 | |||
ffbb67765d | |||
b57a430dc5 | |||
0f04cfabf9 | |||
3b29cbd746 | |||
730665b5da | |||
924cc83090 | |||
4d46bab37f | |||
a5e075a215 | |||
c9b75925b9 | |||
3450e0d38f | |||
ca83604870 | |||
35b758ecf1 | |||
d5ed5f12af | |||
eedcb49f3d | |||
a1878f10b3 | |||
2323c3cac5 | |||
e90c370e73 | |||
cadcc56182 | |||
619f58ad90 | |||
c480554e01 | |||
6da81a9734 | |||
b559726535 | |||
768f0bc396 | |||
5bbf069d61 | |||
d177ef5be2 | |||
b6dd539dc6 | |||
fabca1207d | |||
bf4c7ff7c7 | |||
8d2fb59ba8 | |||
b7497791b4 | |||
48730ead63 | |||
98ecd000a9 | |||
4a9098021f | |||
8850e5fdde | |||
e42bd85274 | |||
d3914a4e34 | |||
73de8d4f49 | |||
ce458a8d4d | |||
149ce51f49 | |||
2fe9c35c0b | |||
39fb5b4830 | |||
71ca547abe | |||
1c5c1067e6 | |||
df4d3eb0e6 | |||
927bbae1be | |||
cbc4fd0632 | |||
10d4d47a17 | |||
52d64d86d8 | |||
2ab61ebee6 | |||
3ad01d1ab0 | |||
7835e09c5b | |||
4c5d2b5e04 | |||
8f72ee97c9 | |||
aebdcae9cf | |||
3b5ef02def | |||
35599ee0ef | |||
6e03578e86 | |||
7cbf873db5 | |||
fefd88b71c | |||
7c80c9d842 | |||
f5e52c6387 | |||
4c43f82b60 | |||
2caaaa9d87 | |||
3fa39c712e | |||
252a93f82d | |||
f5328379e8 | |||
2e8c8c6db4 | |||
862dbd0b8a | |||
120da217f5 | |||
035de92496 | |||
2a0e73ad33 | |||
701a557357 | |||
de1d94e679 | |||
64d5b7e684 | |||
2c9048618f | |||
![]() |
d66f4a2640 | ||
bfbb280c27 | |||
cad7c9a9fd | |||
81ff530a71 | |||
39fc982665 | |||
803f5fbe89 | |||
0d1d4c887d | |||
f8f13be6d3 | |||
5f645508ba | |||
3a3cb3963c | |||
2c298577cc | |||
97815f807a | |||
f24f97baae | |||
2a10f029f0 | |||
3deffa57de | |||
d6985be896 | |||
0aad2f0ab3 | |||
f151d6554d | |||
62d6ae2277 | |||
b405e384c8 | |||
7aa986163b | |||
59409e61a5 | |||
edbc74262a | |||
e07efa46b3 | |||
bedb2e2a4f | |||
96b5eec8e0 | |||
c6433d8655 | |||
c0654658b5 | |||
d71d3695eb | |||
4b6a7c5d83 | |||
368e089639 | |||
33647da6e8 | |||
ca39db649b | |||
07dc813cb6 | |||
a852a429f4 | |||
1e3a0d9b2e | |||
9959ac24d9 | |||
90f9f18deb | |||
6983d11b73 | |||
cd5b71a26e | |||
aa3ca82f25 | |||
bf9b8a4f46 | |||
2be460b662 | |||
1d4057ac5b | |||
b9731a3c75 | |||
d53decb165 | |||
02b8330ca0 | |||
12046c1f56 | |||
6f69426f03 | |||
c8c242a27f | |||
60daa548ca | |||
40989a54ed | |||
87f2976419 | |||
b6b81e2b36 | |||
dfcd63b9b6 | |||
23af944fba | |||
cf9add1fc9 | |||
a63f19a70f | |||
8ad266ad1b | |||
b593071364 | |||
0e814a765a | |||
2c7eab60a8 | |||
d43738a2ec | |||
0f885712b6 | |||
14a10989e5 | |||
d5253f651b | |||
31e901b000 | |||
26e7ff009f | |||
1ded773e84 | |||
b8637ba8ca | |||
4dc4f46b84 | |||
e00ec84df3 | |||
8dd122cfb2 | |||
dcfacf8cdd | |||
bc1522833c | |||
4b7382e61f | |||
585d8e2b0c | |||
f34829d72d | |||
![]() |
fd1d60b815 | ||
![]() |
31e194a038 | ||
![]() |
e25712920a | ||
![]() |
27867a1961 | ||
![]() |
1425dcd313 | ||
![]() |
b9cff1a9c9 | ||
fc1ed54963 | |||
![]() |
442abe82db | ||
1b693cd798 | |||
![]() |
8426d8851f | ||
![]() |
eca5aa7bfe | ||
485b4c3c6c | |||
b35cdbd7b4 | |||
dbafeb9872 | |||
c6865a7742 | |||
fcb2ce119b | |||
9f74d568f0 | |||
e3137f4c3d | |||
3f58112a61 | |||
307085b0d5 | |||
44ea77b605 | |||
085c9038b5 | |||
af4e72ee27 | |||
9322241747 | |||
116a0288d4 | |||
d0322e7131 | |||
000cf1aff1 | |||
cf56b518d6 | |||
1262a9d784 | |||
e7a30b418a | |||
8924f787d4 | |||
b81aff7f8d | |||
1dfe256cfe | |||
ee8e4b1d8b | |||
0317eff6a5 | |||
793bd2cf5b | |||
6b9540a0a8 | |||
5687cf6a62 | |||
1fd2b5914b | |||
d863aafa8c | |||
5bce84b786 | |||
9eab9ad5f0 |
@@ -1,6 +1,7 @@
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
PYTHON: "C:\\python37\\python.exe"
|
||||
PYTHON: "C:\\python310\\python.exe"
|
||||
PYTHON37: "C:\\python37\\python.exe" # for Windows 7 support.
|
||||
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
|
||||
|
||||
stages:
|
||||
@@ -17,9 +18,10 @@ twblue32:
|
||||
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||
- echo ${time}
|
||||
- echo "started by ${GITLAB_USER_NAME}"
|
||||
- choco install python --version 3.7.9 -y -ForceX86
|
||||
- choco install python --version 3.10.8 -y -ForceX86
|
||||
- '&$env:PYTHON -V'
|
||||
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||
- '&$env:PYTHON -m pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp310-cp310-win32.whl?raw=true'
|
||||
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
|
||||
stage: build
|
||||
interruptible: true
|
||||
@@ -30,6 +32,7 @@ twblue32:
|
||||
- cd ..\src
|
||||
- '&$env:PYTHON ..\doc\generator.py'
|
||||
- '&$env:PYTHON write_version_data.py'
|
||||
- New-Item "appkeys.py" -ItemType File -Value "twitter_api_key='$TWITTER_API_KEY'`ntwitter_api_secret='$TWITTER_API_SECRET'"
|
||||
- '&$env:PYTHON setup.py build'
|
||||
- cd ..
|
||||
- mkdir artifacts
|
||||
@@ -56,7 +59,7 @@ twblue64:
|
||||
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||
- echo ${time}
|
||||
- echo "started by ${GITLAB_USER_NAME}"
|
||||
- choco install python --version 3.7.9 -y
|
||||
- choco install python --version 3.10.8 -y
|
||||
- '&$env:PYTHON -V'
|
||||
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
|
||||
@@ -69,6 +72,7 @@ twblue64:
|
||||
- cd ..\src
|
||||
- '&$env:PYTHON ..\doc\generator.py'
|
||||
- '&$env:PYTHON write_version_data.py'
|
||||
- New-Item "appkeys.py" -ItemType File -Value "twitter_api_key='$TWITTER_API_KEY'`ntwitter_api_secret='$TWITTER_API_SECRET'"
|
||||
- '&$env:PYTHON setup.py build'
|
||||
- cd ..
|
||||
- mkdir artifacts
|
||||
@@ -84,6 +88,44 @@ twblue64:
|
||||
- artifacts
|
||||
expire_in: 1 day
|
||||
|
||||
twblueWin7:
|
||||
tags:
|
||||
- shared-windows
|
||||
- windows
|
||||
- windows-1809
|
||||
before_script:
|
||||
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||
- echo ${time}
|
||||
- echo "started by ${GITLAB_USER_NAME}"
|
||||
- choco install python --version 3.7.9 -y -ForceX86
|
||||
- '&$env:PYTHON37 -V'
|
||||
- '&$env:PYTHON37 -m pip install --upgrade pip'
|
||||
- '&$env:PYTHON37 -m pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp37-cp37m-win32.whl?raw=true'
|
||||
- '&$env:PYTHON37 -m pip install --upgrade -r requirements.txt'
|
||||
stage: build
|
||||
interruptible: true
|
||||
script:
|
||||
# Create html documentation firstly.
|
||||
- cd doc
|
||||
- '&$env:PYTHON37 documentation_importer.py'
|
||||
- cd ..\src
|
||||
- '&$env:PYTHON37 ..\doc\generator.py'
|
||||
- '&$env:PYTHON37 write_version_data.py'
|
||||
- New-Item "appkeys.py" -ItemType File -Value "twitter_api_key='$TWITTER_API_KEY'`ntwitter_api_secret='$TWITTER_API_SECRET'"
|
||||
- '&$env:PYTHON37 setup.py build'
|
||||
- cd ..
|
||||
- mkdir artifacts
|
||||
- cd scripts
|
||||
- '&$env:PYTHON37 make_archive.py'
|
||||
- cd ..
|
||||
- move src/twblue.zip artifacts/twblue_windows7_x86.zip
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts
|
||||
expire_in: 1 day
|
||||
|
||||
generate_versions:
|
||||
stage: make_installer
|
||||
tags:
|
||||
|
59
README.md
59
README.md
@@ -1,26 +1,25 @@
|
||||
TWBlue -
|
||||
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.
|
||||
With this app you’ll have access to twitter features such as:
|
||||
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.
|
||||
|
||||
* Create, reply to, like, retweet and delete tweets,
|
||||
* 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.
|
||||
See [TWBlue's webpage](https://twblue.es) for more details.
|
||||
|
||||
## Running TWBlue from source
|
||||
|
||||
This document describes how to run tw blue from source and how to build a binary version which doesn't need Python and the other dependencies to run.
|
||||
|
||||
### Generating application keys
|
||||
|
||||
In order to communicate with Twitter, you will need to generate a set of API keys in their [developer portal](https://developer.twitter.com/en/portal/dashboard) (If you haven't signed up, [visit this site to register as a developer](https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api)) and create a module called appkeys.py, within the src directory, with the following content, replacing the example values with your set of API keys:
|
||||
|
||||
```
|
||||
twitter_api_key='xxxxxxxxxx'
|
||||
twitter_api_secret='xxxxxxxxxx'
|
||||
```
|
||||
|
||||
### Required dependencies.
|
||||
|
||||
Although most dependencies can be found in the windows-dependencies directory, we provide links to their official websites. If you are cloning with git, don't forget to initialize and update the submodules to get the windows-dependencies folder. You can use these two commands to perform this task from git bash:
|
||||
@@ -31,18 +30,22 @@ Although most dependencies can be found in the windows-dependencies directory, w
|
||||
|
||||
#### Dependencies packaged in windows installers
|
||||
|
||||
* [Python,](https://python.org) version 3.7.9
|
||||
If you want to build both x86 and x64 binaries, you can install python x86 to C:\python38 and python x64 to C:\python38x64, for example.
|
||||
* [Python,](https://python.org) version 3.10.8
|
||||
If you want to build both x86 and x64 binaries, you can install python x64 to C:\python310 and python x86 to C:\python310-32, for example.
|
||||
|
||||
#### Dependencies that must be installed using pip
|
||||
|
||||
Python installs a tool called Pip that allows to install packages in a simple way. You can find it in the python scripts directory. To install packages using Pip, you have to navigate to the scripts directory using a command prompt, for example:
|
||||
|
||||
`cd C:\python37x64\scripts`
|
||||
`cd C:\python310\scripts`
|
||||
|
||||
You can also add the scripts folder to your path environment variable or choose the corresponding option when installing Python.
|
||||
Note: pip and setuptools are included in the Python installer since version 2.7.9.
|
||||
|
||||
Note: If you are using Python for 32-bit systems, you will need to install WXPython for 32-bits before running the command for installing everything else. You can do so by running the following command:
|
||||
|
||||
`pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp310-cp310-win32.whl?raw=true`
|
||||
|
||||
Pip is able to install packages listed in a special text file, called the requirements file. To install all remaining dependencies, perform the following command:
|
||||
|
||||
`pip install -r requirements.txt`
|
||||
@@ -70,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
|
||||
|
||||
#### Dependencies required to build the portableApps.com format archive
|
||||
|
||||
* [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 ####
|
||||
#### 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.
|
||||
|
||||
@@ -123,16 +118,8 @@ If you want to install TWBlue on your computer, you must create the installer fi
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
pybabel extract -o twblue.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "MCV software" --input-dirs ..\src
|
||||
|
||||
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,74 @@ TWBlue Changelog
|
||||
|
||||
## 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.
|
||||
* 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.
|
||||
* Twitter:
|
||||
* Fixed a bug that was making sent direct messages to be placed in received direct messages buffer.
|
||||
* When quoting a tweet, you can use all 280 characters to send your quoted tweet, as opposed to the 256 characters TWBlue allowed before.
|
||||
* Fixed a bug that was making TWBlue unable to reply to direct messages by using the "reply" keystroke.
|
||||
* Mastodon:
|
||||
* Added account settings dialog.
|
||||
* Added template editing functionality for mastodon accounts.
|
||||
* When a post is edited, TWBlue will update the post object in the buffer to reflect the latest edit.
|
||||
* Fixed a small issue that was preventing TWBlue to display some posts in their corresponding dialog.
|
||||
|
||||
## Changes in version 2022.12.6
|
||||
|
||||
Most of all changes in this release are focused on adding Mastodon support to TWBlue. The features present to handle Twitter should not have been altered in any way. We were not intended to release this version so soon, but unfortunately, Twitter started to present issues in some regions with one particular API endpoint we were using, making impossible for everyone in such regions to use the application. We will release more updates to fix any possible issue regarding Twitter API, but please take into account that this is sometimes an issue happening in Twitter's servers and while we do our best to make TWBlue work despite those problems, you might encounter glitches from time to time.
|
||||
|
||||
* TWBlue now builds with Python 3.10.8. ([#493](https://github.com/MCV-Software/TWBlue/issues/493))
|
||||
* This change also drops support for Windows 7.
|
||||
* The TWBlue interface has not been translated yet, as we are releasing this update to fix an important Twitter issue for some regions.
|
||||
* Twitter sessions should be able to be opened properly again in TWBlue, in regions where it didn't work since last week.
|
||||
* It is now possible to log in to instances of mastodon, hometown and similar software (Pleroma should work as well, although it has not been tested at this time). From the session manager, clicking on the “new account” button will bring up a menu from which you can select whether you want to log in to Twitter or Mastodon. For instances that have a different character limit than the one set by Mastodon, TWBlue will detect the new limit and adjust the dialogs to allow you to use it correctly.
|
||||
* Most of the TWBlue GUI has been adapted so that the buffers reflect the change of social network (in mastodon, for example, the buttons to write posts say post instead of tweet). However, the menu bar has not yet been updated. This means that most of the options still refer to Twitter, although they can be used with mastodon accounts. For example, if you select the “tweet” menu in the menu bar, and then select the “Retweet” option, TWBlue will actually do a “boost” if the buffer you are in is a Mastodon account buffer.
|
||||
* Keystrokes for the invisible interface also refer to terms used in Twitter, but can be applied to Mastodon as well.
|
||||
* There are some features, within TWBlue, that are not yet compatible with mastodon accounts. These are as follows:
|
||||
* User autocompletion.
|
||||
* Currently, it is not possible to update account settings for mastodon sessions. However, if you know how to edit configuration files, you can close TWBlue, change your session file with any text editor and restart the application to update what you want.
|
||||
* The template editor is not yet available for mastodon accounts.
|
||||
* Filters have not yet been implemented in TWBlue mastodon support.
|
||||
* User aliases are not implemented yet.
|
||||
* It is not possible to view a user’s profile, nor edit your own, for now. However, you can use the keystroke to open the item in the browser when focusing a user to access their profile website. This only works in buffers where users are listed.
|
||||
* You cannot manage lists in TWBlue at the moment.
|
||||
* Most of the buffers planned for mastodon should just work. Among those currently tested are: home (main timeline for the logged-in user), Local (public posts for the instance), federated (public posts for all federating instances), mentions, direct messages, sent posts, favorites, bookmarks, followers, following, blocked users, muted users, user searches and timelines for users.
|
||||
* The difference between favorites and bookmarks is that the author of the post can see who has marked his posts as favorites, but bookmarks are completely private. In any buffer containing mastodon posts, except direct messages, the GUI will display an option to add the post to favorites or bookmarks.
|
||||
* Direct messages in mastodon are posts, exactly like normal posts, but with their privacy setting set so that they can only be seen by the accounts that are mentioned. In the direct message buffer, a conversation will appear for each item in the buffer. The conversation represents a thread of messages, but TWBlue can only display the last of the messages sent. This is similar to what happens on platforms like Telegram, where you can only see the list of conversations at the beginning. To see the entire thread of direct messages present in a conversation, you can use the command to open the conversation, or go to the “tweet” menu in the menu bar, and then towards the “view conversation” option. This will create a new conversation buffer that will be located just after the direct messages buffer (for the GUI, the buffer will be located just inside the direct messages buffer in the buffer tree). When a private post appears (whose visibility only allows the mentioned accounts to see it), TWBlue will display that post in the home buffer, in mentions and also will update/create the conversation with that item. This is because Mastodon does not differentiate between a private message and a normal post. You can reply to the post in any buffer to continue the conversation. If you reply to any post, the privacy set in the original post is maintained by default, but can also be changed.
|
||||
* The buffer showing the federated timeline has been disabled from settings. This is because on servers that federate with many instances it can load many posts in a very short time. To enable this buffer, for now, edit the TWBlue configuration while the application is closed, and add the “federated” buffer in the option called “buffer_order”. As soon as buffers can be shown or hidden, this process can be done through the GUI.
|
||||
* There is a Streaming API that allows the elements for the start buffers, mentions, direct messages, sent posts and followers to appear in real time. This feature is implemented by default and should also just work.
|
||||
* Timelines for users only allow to get all posts from users who are in the same instance. For users belonging to other instances, you can get the posts that have been downloaded to your instance since your instance “knows” the remote user.
|
||||
* Timelines for followers and following can be fully retrieved only for users belonging to the same instance. Remote users may yield unclear results.
|
||||
* You can search by users (by opening a search and selecting the “users” radio button). The search can be done by local users, such as twblue, or by remote users, such as @twblue@maaw.social.
|
||||
* In all buffers, a maximum of 40 items are retrieved per load, but more can be retrieved by using the option to load more items in the buffer.
|
||||
* In post buffers, you can do most of the actions already supported in TWBlue (boost, add/remove from favorites or bookmarks, reply, send message to user, open post URL, play audio or video, open post on web, view conversation, open action dialog for user).
|
||||
* In user buffers, you can send private message to the user, and open user actions dialog, which in turn allows you to follow/unfollow, block/unblock and mute/unmute.
|
||||
* When writing posts, it is possible to attach up to 4 images, 4 givs, or even a video, poll, or audio. It is also possible to add the “sensitive content” tag to posts, change privacy and write a content warning text. It is possible to create threads using the “add post” button.
|
||||
* When replying to a post, TWBlue will place the username of all participants in the item you reply to. The privacy options will default to those of the original post.
|
||||
|
||||
## Changes in version 2022.8.28
|
||||
|
||||
* the user autocompletion feature has been completely rewritten to be easier to use, particularly for people with many followers/following users:
|
||||
* In the account settings dialog, there's a button that opens up a new dialog that allows you to "scan" your account in order to add all users from your followers/following list. This process will read your data directly from Twitter and depending in the amount of people you have in your account it might take too many API calls. Please use it with caution. You can, for example, do the process separately for your followers/following people so it will be easier to handle, in case you have a massive amount of people. If TWBlue is unable to complete the scan, you will see an error and will be prompted to try again in 15 minutes, once your API calls have refreshed.
|
||||
* It is possible to use the user autocompletion functionality in dialogs where you can select an user, for example when adding or removing someone from a list, or displaying lists for someone.
|
||||
|
2315
doc/locales/de/LC_MESSAGES/twblue-changelog.po
Normal file
2315
doc/locales/de/LC_MESSAGES/twblue-changelog.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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
20
documentation/Makefile
Normal file
20
documentation/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
35
documentation/make.bat
Normal file
35
documentation/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
31
documentation/source/basic_concepts.rst
Normal file
31
documentation/source/basic_concepts.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
Basic concepts
|
||||
----------------------
|
||||
|
||||
Before starting to describe TWBlue's usage, we'll explain some concepts that will be used extensively throughout this manual.
|
||||
|
||||
Session
|
||||
++++++
|
||||
|
||||
A session is an account set up on a service. When you authorize TWBlue to use any of your social network accounts, a session will be created in the application. In this session, TWBlue creates buffers that allow you to display different types of items present in the social network. TWBlue allows you to have any number of sessions authorized and started. You can have TWBlue automatically start all your sessions, which is the default setting, although it is also possible to make certain sessions not start when you open the application.
|
||||
|
||||
Buffer
|
||||
++++++
|
||||
|
||||
A buffer is a list of items that come from your configured account, after being processed by TWBlue. TWBlue will create buffers with different types of items. For example, your posts sent on a social network, or your private messages with other users. You can perform certain actions on each of these elements depending on the type of buffer you are focusing.
|
||||
|
||||
Graphical user Interface (GUI)
|
||||
+++++++++++++++++++++++++++++++++
|
||||
|
||||
TWBlue has two different interfaces: The graphical user interface (GUI) and the invisible interface. The GUI allows you to interact with the application through a window containing two important elements: A menu bar, which can be accessed by pressing the Alt key, and the list of sessions, buffers and available actions for them, which you can access by pressing the Tab key.
|
||||
|
||||
The list of sessions and buffers is grouped in a tree view, where sessions are located at the root level and each session contains the buffers that belong to it. When you select one of these buffers, you can access the list of items it contains by using the Tab key. Depending on the selected buffer, you can find, also with the Tab key, a list of buttons representing actions you can perform on the session (such as posting a message on the social network) or on the focused item.
|
||||
|
||||
Invisible interface
|
||||
++++++++++++++++++
|
||||
|
||||
The invisible interface, as its name suggests, has no graphical window and works directly with screen readers such as JAWS for Windows, NVDA and System Access through keyboard shortcuts that you can use in any window. This interface is disabled by default, but you can enable it by pressing Control + M, which will hide the TWBlue window. If you use other applications to manage Twitter, such as The Qube and chicken Nugget, TWBlue includes support for keymaps for these clients, which you can configure from the global options dialog. It is also possible to use the invisible interface from TWBlue's graphical window, although this option can be disabled to avoid conflicting with other applications that require the keyboard shortcuts globally.
|
||||
|
||||
Global settings and session settings
|
||||
++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
TWBlue has two different configuration dialogs: the global configuration dialog, which affects how TWBlue works for all sessions, and the session configuration dialog, which only affects how the current session works. You will find specific information about the session settings dialog for Twitter and Mastodon in its corresponding chapter in this guide.
|
390
documentation/source/changelog.rst
Normal file
390
documentation/source/changelog.rst
Normal file
@@ -0,0 +1,390 @@
|
||||
TWBlue Changelog
|
||||
===========================
|
||||
|
||||
Changes in this version
|
||||
+++++++++++++++++++++++++
|
||||
|
||||
* 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 an issue that was making TWBlue unable to open in certain computers due to errors related to Win32 API'S.
|
||||
|
||||
* Twitter:
|
||||
|
||||
* Fixed a bug that was making sent direct messages to be placed in received direct messages buffer.
|
||||
* When quoting a tweet, you can use all 280 characters to send your quoted tweet, as opposed to the 256 characters TWBlue allowed before.
|
||||
* Fixed a bug that was making TWBlue unable to reply to direct messages by using the "reply" keystroke.
|
||||
|
||||
* Mastodon:
|
||||
|
||||
* Added account settings dialog.
|
||||
* Added template editing functionality for mastodon accounts.
|
||||
* When a post is edited, TWBlue will update the post object in the buffer to reflect the latest edit.
|
||||
* Fixed a small issue that was preventing TWBlue to display some posts in their corresponding dialog.
|
||||
|
||||
Version 2022.12.6
|
||||
-----------------
|
||||
|
||||
Most of all changes in this release are focused on adding Mastodon support to TWBlue. The features present to handle Twitter should not have been altered in any way. We were not intended to release this version so soon, but unfortunately, Twitter started to present issues in some regions with one particular API endpoint we were using, making impossible for everyone in such regions to use the application. We will release more updates to fix any possible issue regarding Twitter API, but please take into account that this is sometimes an issue happening in Twitter's servers and while we do our best to make TWBlue work despite those problems, you might encounter glitches from time to time.
|
||||
|
||||
* TWBlue now builds with Python 3.10.8. (`#493 <https://github.com/MCV-Software/TWBlue/issues/493>`_)
|
||||
|
||||
* This change also drops support for Windows 7.
|
||||
|
||||
* The TWBlue interface has not been translated yet, as we are releasing this update to fix an important Twitter issue for some regions.
|
||||
* Twitter sessions should be able to be opened properly again in TWBlue, in regions where it didn't work since last week.
|
||||
* It is now possible to log in to instances of mastodon, hometown and similar software (Pleroma should work as well, although it has not been tested at this time). From the session manager, clicking on the “new account” button will bring up a menu from which you can select whether you want to log in to Twitter or Mastodon. For instances that have a different character limit than the one set by Mastodon, TWBlue will detect the new limit and adjust the dialogs to allow you to use it correctly.
|
||||
* Most of the TWBlue GUI has been adapted so that the buffers reflect the change of social network (in mastodon, for example, the buttons to write posts say post instead of tweet). However, the menu bar has not yet been updated. This means that most of the options still refer to Twitter, although they can be used with mastodon accounts. For example, if you select the “tweet” menu in the menu bar, and then select the “Retweet” option, TWBlue will actually do a “boost” if the buffer you are in is a Mastodon account buffer.
|
||||
* Keystrokes for the invisible interface also refer to terms used in Twitter, but can be applied to Mastodon as well.
|
||||
* There are some features, within TWBlue, that are not yet compatible with mastodon accounts. These are as follows:
|
||||
|
||||
* User autocompletion.
|
||||
* Currently, it is not possible to update account settings for mastodon sessions. However, if you know how to edit configuration files, you can close TWBlue, change your session file with any text editor and restart the application to update what you want.
|
||||
* The template editor is not yet available for mastodon accounts.
|
||||
* Filters have not yet been implemented in TWBlue mastodon support.
|
||||
* User aliases are not implemented yet.
|
||||
* It is not possible to view a user’s profile, nor edit your own, for now. However, you can use the keystroke to open the item in the browser when focusing a user to access their profile website. This only works in buffers where users are listed.
|
||||
* You cannot manage lists in TWBlue at the moment.
|
||||
|
||||
* Most of the buffers planned for mastodon should just work. Among those currently tested are: home (main timeline for the logged-in user), Local (public posts for the instance), federated (public posts for all federating instances), mentions, direct messages, sent posts, favorites, bookmarks, followers, following, blocked users, muted users, user searches and timelines for users.
|
||||
|
||||
* The difference between favorites and bookmarks is that the author of the post can see who has marked his posts as favorites, but bookmarks are completely private. In any buffer containing mastodon posts, except direct messages, the GUI will display an option to add the post to favorites or bookmarks.
|
||||
* Direct messages in mastodon are posts, exactly like normal posts, but with their privacy setting set so that they can only be seen by the accounts that are mentioned. In the direct message buffer, a conversation will appear for each item in the buffer. The conversation represents a thread of messages, but TWBlue can only display the last of the messages sent. This is similar to what happens on platforms like Telegram, where you can only see the list of conversations at the beginning. To see the entire thread of direct messages present in a conversation, you can use the command to open the conversation, or go to the “tweet” menu in the menu bar, and then towards the “view conversation” option. This will create a new conversation buffer that will be located just after the direct messages buffer (for the GUI, the buffer will be located just inside the direct messages buffer in the buffer tree). When a private post appears (whose visibility only allows the mentioned accounts to see it), TWBlue will display that post in the home buffer, in mentions and also will update/create the conversation with that item. This is because Mastodon does not differentiate between a private message and a normal post. You can reply to the post in any buffer to continue the conversation. If you reply to any post, the privacy set in the original post is maintained by default, but can also be changed.
|
||||
* The buffer showing the federated timeline has been disabled from settings. This is because on servers that federate with many instances it can load many posts in a very short time. To enable this buffer, for now, edit the TWBlue configuration while the application is closed, and add the “federated” buffer in the option called “buffer_order”. As soon as buffers can be shown or hidden, this process can be done through the GUI.
|
||||
* There is a Streaming API that allows the elements for the start buffers, mentions, direct messages, sent posts and followers to appear in real time. This feature is implemented by default and should also just work.
|
||||
* Timelines for users only allow to get all posts from users who are in the same instance. For users belonging to other instances, you can get the posts that have been downloaded to your instance since your instance “knows” the remote user.
|
||||
* Timelines for followers and following can be fully retrieved only for users belonging to the same instance. Remote users may yield unclear results.
|
||||
* You can search by users (by opening a search and selecting the “users” radio button). The search can be done by local users, such as twblue, or by remote users, such as @twblue@maaw.social.
|
||||
* In all buffers, a maximum of 40 items are retrieved per load, but more can be retrieved by using the option to load more items in the buffer.
|
||||
* In post buffers, you can do most of the actions already supported in TWBlue (boost, add/remove from favorites or bookmarks, reply, send message to user, open post URL, play audio or video, open post on web, view conversation, open action dialog for user).
|
||||
* In user buffers, you can send private message to the user, and open user actions dialog, which in turn allows you to follow/unfollow, block/unblock and mute/unmute.
|
||||
* When writing posts, it is possible to attach up to 4 images, 4 givs, or even a video, poll, or audio. It is also possible to add the “sensitive content” tag to posts, change privacy and write a content warning text. It is possible to create threads using the “add post” button.
|
||||
* When replying to a post, TWBlue will place the username of all participants in the item you reply to. The privacy options will default to those of the original post.
|
||||
|
||||
Version 2022.8.28
|
||||
------------------
|
||||
|
||||
* the user autocompletion feature has been completely rewritten to be easier to use, particularly for people with many followers/following users:
|
||||
|
||||
* In the account settings dialog, there's a button that opens up a new dialog that allows you to "scan" your account in order to add all users from your followers/following list. This process will read your data directly from Twitter and depending in the amount of people you have in your account it might take too many API calls. Please use it with caution. You can, for example, do the process separately for your followers/following people so it will be easier to handle, in case you have a massive amount of people. If TWBlue is unable to complete the scan, you will see an error and will be prompted to try again in 15 minutes, once your API calls have refreshed.
|
||||
* It is possible to use the user autocompletion functionality in dialogs where you can select an user, for example when adding or removing someone from a list, or displaying lists for someone.
|
||||
|
||||
* Implemented a new setting, available in the account settings dialog, that allows to hide emojis in twitter usernames.
|
||||
* TWBlue should be able to sort conversations in a more logical way. This should make it easier to follow a long thread in Twitter.
|
||||
* When opening a thread, TWBlue should be able to load the right conversation if the original tweet from where the thread was loaded was a retweet.
|
||||
* TWBlue will restart the Streaming subsystem every time there are changes to followed, muted or blocked users within the application.
|
||||
* Fixed error when attempting to mention an user by using the "mention" button in any people buffer. Now tweets should be posted normally.
|
||||
* Fixed error when loading other user lists. (`#465 <https://github.com/MCV-Software/TWBlue/issues/465>`_)
|
||||
* Fixed an issue that was making TWBlue to display incorrectly some retweets of quoted tweets.
|
||||
* If TWBlue is unable to open a timeline for someone who has blocked you, this will be reported in a dialog. (`#485, <https://github.com/mcv-software/twblue/issues/485>`_)
|
||||
* Added "find a string in the currently focused buffer" action into Windows 10 and windows 11 keymap. (`#476 <https://github.com/MCV-Software/TWBlue/pull/476>`_)
|
||||
|
||||
Version 2022.2.23
|
||||
--------------------
|
||||
|
||||
* We have added Experimental support for templates in the invisible interface. The GUI will remain unchanged for now:
|
||||
|
||||
* Each object (tweet, received direct message, sent direct message and people) has its own template in the settings. You can edit those templates from the account settings dialog, in the new "templates" tab.
|
||||
* Every template is composed of the group of variables you want to display for each object. Each variable will start with a dollar sign ($) and cannot contain spaces or special characters. Templates can include arbitrary text that will not be processed. When editing the example templates, you can get an idea of the variables that are available for each object by using the template editing dialog. When you press enter on a variable from the list of available variables, it will be added to the template automatically. When you try to save a template, TWBlue will warn you if the template is incorrectly formatted or if it includes variables that do not exist in the information provided by objects. It is also possible to return to default values from the same dialog when editing a template.
|
||||
* TWBlue can display image descriptions within Tweet templates. For that, you can use the $image_description variable in your template.
|
||||
|
||||
* We have restored conversation and threads support powered by Twitter API V2 thanks to a set of improvements we have done in the application, as well as more generous limits to Tweet monthly cap by Twitter.
|
||||
* In the Windows 11 Keymap, the default shortcut to open the keystrokes editor is now CTRL+Alt+Windows+K to avoid conflicts with the new global mute microphone shortcut.
|
||||
* TWBlue show display properly HTML entities in tweet's text.
|
||||
* TWBlue should no longer load old tweets in buffers.
|
||||
* Fixed issue when uploading attachments (images, videos or gif files) while sending tweets or replies.
|
||||
* Fixed an error that was making TWBlue to ask for a restart after saving account settings, even if such restart was not required. (`#413, <https://github.com/manuelcortez/TWBlue/issues/413>`_)
|
||||
|
||||
Version 2021.11.12
|
||||
------------------
|
||||
|
||||
* Now it is possible to create a tweet from a trending topics buffer again.
|
||||
* TWBlue now includes a completely new set of dialogs to handle tweeting, replying and sending direct messages that take advantage of more Twitter features.
|
||||
|
||||
* It is possible to add videos in tweets and direct messages by using the new "add" button, located in every dialog where media can be added. Twitter suggests to add videos from 5 seconds up to 2 minutes lenght, in mp4 format (video Codec H.264 and audio codec AAC). Currently, TWBlue does not check if the uploaded video complies with Twitter media requirements. You can add only a video in a tweet or direct message. No other kind of media can be added after a video is in a tweet. If the video was unable to be uploaded successfully, the tweet or direct message won't be created.
|
||||
* Now you can add a poll to tweets. Polls can have up to 4 different options and allow voting up to 7 days after being created. Take into account, though, that currently TWBlue does not support reading polls in tweets.
|
||||
* TWBlue now support threads while creating a new tweet. There is a new button, called add tweet which will add the current tweet to the thread and will allow you to write another tweet in the thread. Every tweet might include media (up to 4 photos, or one GIF image or a video) or up to one poll.
|
||||
* Some functionality was removed from tweet dialogs within TWBlue. Particularly, URL shorteners and long tweets via Twishort. You still can read long tweets posted via Twishort, though.
|
||||
|
||||
Version 2021.11.07
|
||||
------------------
|
||||
|
||||
* TWBlue should retrieve tweets from threads and conversations in a more reliable way. Tweets in the same thread (made by the same author) will be sorted correctly, although replies to the thread (made by different people) may not be ordered in the same way they are displayed in Twitter apps. (`#417 <https://github.com/manuelcortez/TWBlue/issues/417>`_)
|
||||
* When creating a filter, TWBlue will show an error if user has not provided a name for the filter. Before, unnamed filters were a cause of config breaks in the application.
|
||||
* It is again possible to read the changelog for TWBlue from the help menu in the menu bar.
|
||||
* fixed a bug when clearing the direct messages buffer. (`#418 <https://github.com/manuelcortez/TWBlue/issues/418>`_)
|
||||
* fixed an issue that was making TWBlue to show incorrectly titles for trending topic buffers upon startup. (`#421 <https://github.com/manuelcortez/TWBlue/issues/421>`_)
|
||||
* fixed an issue that was making users of the graphical user interface to delete a buffer if a trends buffer was opened in the same session.
|
||||
* Updated Spanish, Japanese and french translations.
|
||||
|
||||
Version 2021.10.30
|
||||
------------------
|
||||
|
||||
* Fixed many errors in the way we compile TWBlue, so users of 64 bits systems and particularly windows 7 users would be able to install TWBlue again. In case of issues with versions prior to 2021.10.30, please remove everything related to TWBlue (except configs) and reinstall the version 2021.10.30 to fix any possible error. This step won't be needed again in 23 months. (`#416, <https://github.com/manuelcortez/TWBlue/issues/416>`_, `#415, <https://github.com/manuelcortez/TWBlue/issues/415>`_)
|
||||
* fixed an issue that was making impossible to manually add an user to the autocomplete users database.
|
||||
* Started to improve support to conversations by searching for conversation_id.
|
||||
|
||||
Version 2021.10.27
|
||||
------------------
|
||||
|
||||
* Added an user alias manager, located in the application menu in the menu bar. From this dialog, it is possible to review, add, edit or remove user aliases for the current account. (`#401 <https://github.com/manuelcortez/TWBlue/issues/401>`_)
|
||||
* TWBlue now closes the VLC player window automatically when a video reaches its end. (`#399 <https://github.com/manuelcortez/TWBlue/issues/399>`_)
|
||||
* After a lot of time, TWBlue now uses a new default Soundpack, called FreakyBlue. This soundpack will be set by default in all new sessions created in the application. Thanks to `Andre Louis <https://twitter.com/FreakyFwoof>`_ for the pack. (`#247 <https://github.com/manuelcortez/TWBlue/issues/247>`_)
|
||||
* When reading a tweet, if the tweet contains more than 2 consecutive mentions, TWBlue will announce how many more users the tweet includes, as opposed to read every user in the conversation. You still can display the tweet to read all users.
|
||||
* In the tweet displayer, It is possible to copy a link to the current tweet or person by pressing a button called "copy link to clipboard".
|
||||
* Added a keymap capable to work under Windows 11. (`#391 <https://github.com/manuelcortez/TWBlue/pull/391>`_)
|
||||
* Added user aliases to TWBlue. This feature allows you to rename user's display names on Twitter, so the next time you'll read an user it will be announced as you configured. For adding an alias to an user, select the "add alias" option in the user menu, located in the menu bar. This feature works only if you have set display screen names unchecked. Users are displayed with their display name in people buffers only. This action is supported in all keymaps, although it is undefined by default. (`#389 <https://github.com/manuelcortez/TWBlue/pull/389>`_)
|
||||
* There are some changes to the autocomplete users feature:
|
||||
|
||||
* Now users can search for twitter screen names or display names in the database.
|
||||
|
||||
* It is possible to undefine keystrokes in the current keymap in TWBlue. This allows you, for example, to redefine keystrokes completely.
|
||||
* We have changed our Geocoding service to the Nominatim API from OpenStreetMap. Addresses present in tweets are going to be determined by this service, as the Google Maps API now requires an API key. (`#390 <https://github.com/manuelcortez/TWBlue/issues/390>`_)
|
||||
* Added a limited version of the Twitter's Streaming API: The Streaming API will work only for tweets, and will receive tweets only by people you follow. Protected users are not possible to be streamed. It is possible that during high tweet traffic, the Stream might get disconnected at times, but TWBlue should be capable of detecting this problem and reconnecting the stream again. (`#385 <https://github.com/manuelcortez/TWBlue/pull/385>`_)
|
||||
* Fixed an issue that made TWBlue to not show a dialog when attempting to show a profile for a suspended user. (`#387 <https://github.com/manuelcortez/TWBlue/issues/387>`_)
|
||||
* Added support for Twitter audio and videos: Tweets which contain audio or videos will be detected as audio items, and you can playback those with the regular command to play audios. (`#384, <https://github.com/manuelcortez/TWBlue/pull/384>`_)
|
||||
* We just implemented some changes in the way TWBlue handles tweets in order to reduce its RAM memory usage (`#380 <https://github.com/manuelcortez/TWBlue/pull/380>`_):
|
||||
|
||||
* We reduced the tweets size by storing only the tweet fields we currently use. This should reduce tweet's size in memory for every object up to 75%.
|
||||
* When using the cache database to store your tweets, there is a new setting present in the account settings dialog, in the general tab. This setting allows you to control whether TWBlue will load the whole database into memory (which is the current behaviour) or not.
|
||||
|
||||
* Loading the whole database into memory has the advantage of being extremely fast to access any element (for example when moving through tweets in a buffer), but it requires more memory as the tweet buffers grow up. This should, however, use less memory than before thanks to the optimizations performed in tweet objects. If you have a machine with enough memory, this should be a good option for your case.
|
||||
* If you uncheck this setting, TWBlue will read the whole database from disk. This is significantly slower, but the advantage of this setting is that it will consume almost no extra memory, no matter how big is the tweets dataset. Be ware, though, that TWBlue might start to feel slower when accessing elements (for example when reading tweets) as the buffers grow up. This setting is suggested for computers with low memory or for those people not wanting to keep a really big amount of tweets stored.
|
||||
|
||||
* Changed the label in the direct message's text control so it will indicate that the user needs to write the text there, without referring to any username in particular. (`#366, <https://github.com/manuelcortez/TWBlue/issues/366>`_)
|
||||
* TWBlue will take Shift+F10 again as the contextual menu key in the list of items in a buffer. This stopped working after we have migrated to WX 4.1. (`#353, <https://github.com/manuelcortez/TWBlue/issues/353>`_)
|
||||
* TWBlue should render correctly retweets of quoted tweets. (`#365, <https://github.com/manuelcortez/TWBlue/issues/365>`_)
|
||||
* Fixed an error that was causing TWBlue to be unable to output to screen readers at times. (`#369, <https://github.com/manuelcortez/TWBlue/issues/369>`_)
|
||||
* Fixed autocomplete users feature. (`#367, <https://github.com/manuelcortez/TWBlue/issues/367>`_)
|
||||
* Fixed error when displaying an URL at the end of a line, when the tweet or direct message contained multiple lines. Now the URL should be displayed correctly. (`#305, <https://github.com/manuelcortez/TWBlue/issues/305>`_ `#272, <https://github.com/manuelcortez/TWBlue/issues/272>`_)
|
||||
* TWBlue has been migrated completely to Python 3 (currently, the software builds with Python 3.8).
|
||||
* TWBlue should be restarted gracefully. Before, the application was alerting users of not being closed properly every time the application restarted by itself.
|
||||
* If TWBlue attemps to load an account with invalid tokens (this happens when reactivating a previously deactivated account, or when access to the ap is revoqued), TWBlue will inform the user about this error and will skip the account. Before, the app was unable to start due to a critical error. (`#328, <https://github.com/manuelcortez/TWBlue/issues/328>`_)
|
||||
* When sending a direct message, the title of the window will change appropiately when the recipient is edited. (`#276, <https://github.com/manuelcortez/TWBlue/issues/276>`_)
|
||||
* URL'S in user profiles are expanded automatically. (`#275, <https://github.com/manuelcortez/TWBlue/issues/275>`_)
|
||||
* TWBlue now uses `Tweepy, <https://github.com/tweepy/tweepy>`_ to connect with Twitter. We have adopted this change in order to support Twitter'S API V 2 in the very near future. (`#333, <https://github.com/manuelcortez/TWBlue/issues/337>`_ `#347 <https://github.com/manuelcortez/TWBlue/pull/347>`_)
|
||||
* TWBlue can upload images in Tweets and replies again. (`#240, <https://github.com/manuelcortez/TWBlue/issues/240>`_)
|
||||
* Fixed the way we use to count characters in Twitter. The new methods in TWBlue take into account special characters and URLS as documented in Twitter. (`#199, <https://github.com/manuelcortez/TWBlue/issues/199>`_ `#315 <https://github.com/manuelcortez/TWBlue/issues/315>`_)
|
||||
* Proxy support now works as expected.
|
||||
* Changed translation service from yandex.translate to Google Translator. (`#355, <https://github.com/manuelcortez/TWBlue/issues/355>`_)
|
||||
* Improved method to load direct messages in the buffers. Now it should be faster due to less calls to Twitter API performed from the client.
|
||||
* And more. (`#352, <https://github.com/manuelcortez/TWBlue/issues/352>`_)
|
||||
|
||||
Version 0.95
|
||||
-------------
|
||||
|
||||
* TWBlue can open a Tweet or user directly in Twitter. There is a new option in the context menu for people and tweet buffers, and also, the shortcut control+win+alt+Enter will open the focused item in Twitter.
|
||||
* Some keystrokes were remapped in the Windows 10 Keymap:
|
||||
|
||||
* Read location of a tweet: Ctrl+Win+G. (`#177 <https://github.com/manuelcortez/TWBlue/pull/177>`_)
|
||||
* Open global settings dialogue: Ctrl+Win+Alt+O.
|
||||
* Mute/unmute current session: Control + Windows + Alt + M.
|
||||
|
||||
* Fixed an error that was preventing TWBlue to load the direct messages buffer if an user who sent a message has been deleted.
|
||||
* Added support for playing audios posted in `AnyAudio.net <http://anyaudio.net>`_ directly from TWBlue. Thanks to `Sam Tupy <http://www.samtupy.com/>`_
|
||||
* Custom buffer ordering will not be reset every time the application restarts after an account setting has been modified.
|
||||
* When adding or removing an user from a list, it is possible to press enter in the focused list instead of having to search for the "add" or "delete" button.
|
||||
* Quoted and long tweets are displayed properly in the sent tweets buffer after being send. (`#253 <https://github.com/manuelcortez/TWBlue/issues/253>`_)
|
||||
* Fixed an issue that was making the list manager keystroke unable to be shown in the keystroke editor. Now the keystroke is listed properly. (`#260 <https://github.com/manuelcortez/TWBlue/issues/260>`_)
|
||||
* The volume slider, located in the account settings of TWBlue, now should decrease and increase value properly when up and down arrows are pressed. Before it was doing it in inverted order. (`#261 <https://github.com/manuelcortez/TWBlue/issues/261>`_)
|
||||
* autoreading has been redesigned to work in a similar way for almost all buffers. Needs testing. (`#221 <https://github.com/manuelcortez/TWBlue/issues/221>`_)
|
||||
* When displaying tweets or direct messages, a new field has been added to show the date when the item has been posted to Twitter.
|
||||
* Added support for deleting direct messages by using the new Twitter API methods.
|
||||
* When quoting a retweet, the quote will be made to the original tweet instead of the retweet.
|
||||
* If the sent direct messages buffer is hidden, TWBlue should keep loading everything as expected. (`#246 <https://github.com/manuelcortez/TWBlue/issues/246>`_)
|
||||
* There is a new soundpack, called FreakyBlue (Thanks to `Andre Louis <https://twitter.com/FreakyFwoof>`_) as a new option in TWBlue. This pack can be the default in the next stable, so users can take a look and share their opinion in snapshot versions. (`#247 <https://github.com/manuelcortez/TWBlue/issues/247>`_)
|
||||
* There is a new option in the help menu that allows you to visit the soundpacks section in the TWBlue website. (`#247 <https://github.com/manuelcortez/TWBlue/issues/247>`_)
|
||||
* When reading location of a geotagged tweet, it will be translated for users of other languages. (`#251 <https://github.com/manuelcortez/TWBlue/pull/251>`_)
|
||||
* When there are no more items to retrieve in direct messages and people buffers, a message will announce it.
|
||||
* Fixed an issue reported by some users that was making them unable to load more items in their direct messages.
|
||||
* It is possible to add a tweet to the likes buffer from the menu bar again.
|
||||
* Tweets, replies and retweets will be added to sent tweets right after being posted in Twitter.
|
||||
* Extended Tweets should be displayed properly in list buffers.
|
||||
|
||||
Version 0.94
|
||||
-------------
|
||||
|
||||
* Added an option in the global settings dialog to disable the Streaming features of TWBlue. TWBlue will remove all Streaming features after August 16, so this option will give people an idea about how it will be. (`#219 <https://github.com/manuelcortez/TWBlue/issues/219>`_)
|
||||
* Due to Twitter API changes, Switched authorisation method to Pin-code based authorisation. When you add new accounts to TWBlue, you will be required to paste a code displayed in the Twitter website in order to grant access to TWBlue. (`#216 <https://github.com/manuelcortez/TWBlue/issues/216>`_)
|
||||
* In order to comply with latest Twitter changes, TWBlue has switched to the new method used to send and receive direct messages, according to issue `#215. <https://github.com/manuelcortez/twblue/issues/215>`_
|
||||
|
||||
* The new method does not allow direct messages to be processed in real time. Direct messages will be updated periodically.
|
||||
|
||||
* After august 16 or when streaming is disabled, the events buffer will no longer be created in TWBlue.
|
||||
* You can configure frequency for buffer updates in TWBlue. By default, TWBlue will update all buffers every 2 minutes, but you can change this setting in the global settings dialog. (`#223 <https://github.com/manuelcortez/TWBlue/issues/223>`_)
|
||||
* Added a new tab called feedback, in the account settings dialog. This tab allows you to control whether automatic speech or Braille feedbak in certain events (mentions and direct messages received) is enabled. Take into account that this option will take preference over automatic reading of buffers and any kind of automatic output. (`#203 <https://github.com/manuelcortez/TWBlue/issues/203>`_)
|
||||
* The spell checking dialog now has access keys defined for the most important actions. (`#211 <https://github.com/manuelcortez/TWBlue/issues/211>`_)
|
||||
* TWBlue now Uses WXPython 4.0.1. This will allow us to migrate all important components to Python 3 in the future. (`#207 <https://github.com/manuelcortez/TWBlue/issues/207>`_)
|
||||
* When you quote a Tweet, if the original tweet was posted with Twishort, TWBlue should display properly the quoted tweet. Before it was displaying the original tweet only. (`#206 <https://github.com/manuelcortez/TWBlue/issues/206>`_)
|
||||
* It is possible to filter by retweets, quotes and replies when creating a new filter.
|
||||
* Added support for playing youtube Links directly from the client. (`#94 <https://github.com/manuelcortez/TWBlue/issues/94>`_)
|
||||
* Replaced Bass with libVLC for playing URL streams.
|
||||
* the checkbox for indicating whether TWBlue will include everyone in a reply or not, will be unchecked by default.
|
||||
* You can request TWBlue to save the state for two checkboxes: Long tweet and mention all, from the global settings dialogue.
|
||||
* For windows 10 users, some keystrokes in the invisible user interface have been changed or merged:
|
||||
|
||||
* control+Windows+alt+F will be used for toggling between adding and removing a tweet to user's likes. This function will execute the needed action based in the current status for the focused tweet.
|
||||
|
||||
* TWBlue will show an error if something goes wrong in an audio upload.
|
||||
* And more. (`#171, <https://github.com/manuelcortez/TWBlue/issues/171>`_)
|
||||
|
||||
Version 0.93
|
||||
--------------
|
||||
|
||||
* A new soundpack has been added to TWBlue. Thanks to `@ValeriaK305 <https://twitter.com/ValeriaK305>`_
|
||||
* In the Windows 10 keymap, we have changed some default keystrokes as windows now uses some previously assigned shortcuts:
|
||||
|
||||
* For liking a tweet, press Control+Windows+alt+f
|
||||
* for opening a trends buffer, press control+Windows+T
|
||||
|
||||
* TWBlue has received improvements in some functions for handling extended tweets, long tweets and quoted retweets. It should render some tweets in a better way.
|
||||
* In the spell checker module, there is a new button that will allow you to add your own words to your personal dictionary so the module won't mark them as mispelled the next time you will check spelling.
|
||||
* Added filtering capabilities to TWBlue. (`#102 <https://github.com/manuelcortez/TWBlue/issues/102>`_)
|
||||
|
||||
* You can create a filter for the current buffer from the buffer menu in the menu bar. At this moment, invisible interface does not have any shorcut for this.
|
||||
* You can create filters by word or languages.
|
||||
* For deleting already created filters, you can go to the filter manager in the buffer menu and delete the filters you won't need.
|
||||
|
||||
* Links should be opened properly in quoted tweets (`#167, <https://github.com/manuelcortez/TWBlue/issues/167>`_ `#184 <https://github.com/manuelcortez/TWBlue/issues/184>`_)
|
||||
* Increased display name limit up to 50 characters in update profile dialog.
|
||||
* When authorising an account, you will see a dialogue with a cancel button, in case you want to abort the process. Also, NVDA will not be blocked when the process starts. (`#101 <https://github.com/manuelcortez/TWBlue/issues/101>`_)
|
||||
* In the translator module, the list of available languages is fetched automatically from the provider. That means all of these languages will work and there will not be inconsistencies. Also we've removed the first combo box, because the language is detected automatically by Yandex'S API. (`#153 <https://github.com/manuelcortez/TWBlue/issues/153>`_)
|
||||
* Trending topics, searches and conversation buffers will use mute settings set for the session in wich they were opened. (`#157 <https://github.com/manuelcortez/TWBlue/issues/157>`_)
|
||||
* The Tweet limit is now 280 characters lenght instead 140. It means you can tweet longer tweets. (`#172 <https://github.com/manuelcortez/TWBlue/issues/172>`_)
|
||||
* Per popular request, Status for mention to all and long tweet checkboxes will not be saved in settings. (`#170 <https://github.com/manuelcortez/TWBlue/issues/170>`_)
|
||||
* Fixed a problem that was making TWBlue unable to start if it was being ran in Windows with Serbian language. (`#175 <https://github.com/manuelcortez/TWBlue/issues/175>`_)
|
||||
* Added Danish translation.
|
||||
* And more. (`#156, <https://github.com/manuelcortez/TWBlue/issues/156>`_ `#163, <https://github.com/manuelcortez/TWBlue/issues/163>`_ `#159, <https://github.com/manuelcortez/TWBlue/issues/159>`_ `#173, <https://github.com/manuelcortez/TWBlue/issues/173>`_ `#174, <https://github.com/manuelcortez/TWBlue/issues/174>`_ `#176, <https://github.com/manuelcortez/TWBlue/issues/176>`_)
|
||||
|
||||
Versions 0.91 and 0.92
|
||||
-------------------------
|
||||
|
||||
* Fixed incorrect unicode handling when copying tweet to clipboard. (`#150 <https://github.com/manuelcortez/TWBlue/issues/150>`_)
|
||||
* TWBlue will show an error when trying to open a timeline for a suspended user. (`#128 <https://github.com/manuelcortez/TWBlue/issues/128>`_)
|
||||
* Removed TwUp as service as it no longer exists. (`#112 <https://github.com/manuelcortez/TWBlue/issues/112>`_)
|
||||
* Release audio files after uploading them. (`#130 <https://github.com/manuelcortez/TWBlue/issues/130>`_)
|
||||
* Now TWBlue will use Yandex's translation services instead microsoft translator. (`#132 <https://github.com/manuelcortez/TWBlue/issues/132>`_)
|
||||
* SndUp users will be able to upload audio in their account by using their API Key again. (`#134 <https://github.com/manuelcortez/TWBlue/issues/134>`_)
|
||||
* old tweets shouldn't be added as new items in buffers. (`#116, <https://github.com/manuelcortez/TWBlue/issues/116>`_ `#133 <https://github.com/manuelcortez/TWBlue/issues/133>`_)
|
||||
* All mentionned users should be displayed correctly in Twishort's long tweets. (`#116, <https://github.com/manuelcortez/TWBlue/issues/116>`_ `#135 <https://github.com/manuelcortez/TWBlue/issues/135>`_)
|
||||
* It is possible to select a language for OCR service from the extras panel, in the account settings dialogue. You can, however, set this to detect automatically. OCR should work better in languages with special characters or non-english symbols. (`#107 <https://github.com/manuelcortez/TWBlue/issues/107>`_)
|
||||
* Fixed a problem with JAWS for Windows and TWBlue. Now JAWS will work normally in this update. (`#100 <https://github.com/manuelcortez/twblue/issues/100>`_)
|
||||
* And more (`#136, <https://github.com/manuelcortez/TWBlue/issues/136>`_)
|
||||
|
||||
Version 0.90
|
||||
--------------
|
||||
|
||||
* Fixed a bug in long tweet parsing that was making TWBlue to disconnect the streaming API. (`#103 <https://github.com/manuelcortez/TWBlue/issues/103>`_)
|
||||
* Now OCR will work in images from retweets. It fixes a bug where TWBlue was detecting images but couldn't apply OCR on them. (`#105 <https://github.com/manuelcortez/TWBlue/issues/105>`_)
|
||||
* TWBlue won't try to load tweets already deleted, made with Twishort. Before, if someone posted a long tweet but deleted it in the Twishort's site, TWBlue was trying to load the tweet and it was causing problems in all the client. (`#113 <https://github.com/manuelcortez/TWBlue/issues/113>`_)
|
||||
* TWBlue shows an error message when you try to view the profile of an user that does not exist or has been suspended. (`#114, <https://github.com/manuelcortez/TWBlue/issues/114>`_ `#115 <https://github.com/manuelcortez/TWBlue/issues/115>`_)
|
||||
* The spell checker module should select the right language when is set to "user default". (`#117 <https://github.com/manuelcortez/TWBlue/issues/117>`_)
|
||||
* Image description will be displayed in retweets too. (`#119 <https://github.com/manuelcortez/TWBlue/issues/119>`_)
|
||||
* When reading a long tweet, you shouldn't read strange entities anymore. (`#118 <https://github.com/manuelcortez/twblue/issues/118>`_)
|
||||
* TWBlue will not try to load timelines if the user is blocking you. (`#125 <https://github.com/manuelcortez/twblue/issues/125>`_)
|
||||
|
||||
Versions 0.88 and 0.89
|
||||
------------------------
|
||||
|
||||
* Fixed more issues with streams and reconnections.
|
||||
* newer updates will indicate the release date in the updater.
|
||||
* Changes to keystrokes are reflected in keystroke editor automatically.
|
||||
* In replies with multiple users, if the mention to all checkbox is unchecked, you will see a checkbox per user so you will be able to control who will be mentioned in the reply.
|
||||
* Fixed a bug that caused duplicated user mentions in replies when the tweet was made with Twishort.
|
||||
* Retweets should be displayed normally again when the originating tweet is a Twishort's long tweet.
|
||||
* Changed the way TWBlue saves user timelines in configuration. Now it uses user IDS instead usernames. With user IDS, if an user changes the username, TWBlue still will create his/her timeline. This was not possible by using usernames.
|
||||
* Added a new setting in the account settings dialogue that makes TWBlue to show twitter usernames instead the full name.
|
||||
* Added OCR in twitter pictures. There is a new item in the tweet menu that allows you to extract and display text in images. Also the keystroke alt+Win+o has been added for the same purpose from the invisible interface.
|
||||
* Now TWBlue will play a sound when the focused tweet contains images.
|
||||
* Your own quoted tweets will not appear in the mentions buffer anymore.
|
||||
* The config file is saved in a different way, it should fix the bug where TWBlue needs to be restarted after the config folder is deleted.
|
||||
* Mentioning people from friends or followers buffers works again.
|
||||
* Support for proxy servers has been improved. Now TWBlue supports http, https, socks4 and socks5 proxies, with and without autentication.
|
||||
|
||||
Version 0.87
|
||||
--------------
|
||||
|
||||
* Fixed stream connection errors.
|
||||
* Now TWBlue can handle properly a reply to the sender without including all other mentioned users.
|
||||
* Updated translations.
|
||||
* The status of the mention to all checkbox will be remembered the next time you reply to multiple users.
|
||||
|
||||
Version 0.86
|
||||
--------------
|
||||
|
||||
* Fixed a very important security issue. Now TWBlue will send tweets to twishort without using any other server.
|
||||
* When you add a comment to a tweet, it will be sent as a quoted tweet, even if your reply plus the original tweet is not exceeding 140 characters.
|
||||
* Updated windows 10 keymap for reflecting changes made in the last windows 10 build.
|
||||
* Added last changes in the twitter API.
|
||||
* When replying, it will not show the twitter username in the text box. When you send the tweet, the username will be added automatically.
|
||||
* When replying to multiple users, you'll have a checkbox instead a button for mentioning all people. If this is checked, twitter usernames will be added automatically when you send your reply.
|
||||
|
||||
Version 0.85
|
||||
--------------
|
||||
|
||||
* Long and quoted tweets should be displayed properly In lists.
|
||||
* The connection should be more stable.
|
||||
* Added an autostart option in the global settings dialogue.
|
||||
* Updated translations.
|
||||
* Updated russian documentation.
|
||||
* Tweets in cached database should be loaded properly.
|
||||
* Added some missed dictionaries for spelling correction.
|
||||
* Timelines, lists and other buffer should be created in the right order at startup.
|
||||
|
||||
Version 0.84
|
||||
--------------
|
||||
|
||||
* More improvements in quoted and long tweets.
|
||||
* Updated translations: Russian, Italian, French, Romanian, Galician and Finnish.
|
||||
* Improvements in the audio uploader module: Now it can handle audio with non-english characters.
|
||||
* the title of the window should be updated properly when spellcheck, translate or shorten/unshorten URL buttons are pressed.
|
||||
* the bug that changes the selected tweet in the home timeline shouldn't be happening so often.
|
||||
|
||||
Version 0.82 and 0.83
|
||||
------------------------
|
||||
|
||||
* If the tweet source (client) is an application with unicode characters (example: российская газета) it will not break the tweet displayer.
|
||||
* Added a new field for image description in tweet displayer. When available, it will show description for images posted in tweets.
|
||||
* users can add image descriptions to their photos. When uploading an image, a dialog will show for asking a description.
|
||||
* Redesigned upload image dialog.
|
||||
* Fixed photo uploads when posting tweets.
|
||||
* When getting tweets for a conversation, ignores deleted tweets or some errors, now TWBlue will try to get as much tweets as possible, even if some of these are no longer available.
|
||||
* Added audio playback from soundcloud.
|
||||
* Now the session mute option don't makes the screen reader speaks.
|
||||
* Fixed the direct message dialog. Now it should be displayed properly.
|
||||
* when a tweet is deleted in twitter, TWBlue should reflect this change and delete that tweet in every buffer it is displayed.
|
||||
* If your session is broken, TWBlue will be able to remove it automatically instead just crashing.
|
||||
* audio uploader should display the current progress.
|
||||
* users can disable the check for updates feature at startup from the general tab, in the global settings dialogue.
|
||||
* The invisible interface and the window should be synchronized when the client reconnects.
|
||||
* The documentation option in the systray icon should be enabled.
|
||||
* In trending buffers, you can press enter for posting a tweet about the focused trend.
|
||||
* Updated russian documentation and main program interface (thanks to Natalia Hedlund (Наталья Хедлунд), `@lifestar_n <https://twitter.com/lifestar_n>`_ in twitter)
|
||||
* updated translations.
|
||||
|
||||
Version 0.81
|
||||
--------------
|
||||
|
||||
* Updated translations
|
||||
* The updater module has received some improvements. Now it includes a Mirror URL for checking updates if the main URL is not available at the moment. If something is wrong and both locations don't work, the program will start anyway.
|
||||
* some GUI elements now use keyboard shortcuts for common actions.
|
||||
* fixed a bug in the geolocation dialog.
|
||||
* the chicken nugget keymap should work properly.
|
||||
* Added a new soundpack to the default installation of TWBlue, thanks to `@Deng90 <https://twitter.com/deng90>`_
|
||||
* Now the changelog is written in an html File.
|
||||
* Added some missed dictionaries in last version for the spell checking feature.
|
||||
* Trimmed the beginnings of the sounds in the default soundpack. Thanks to `@masonasons <https://github.com/masonasons>`_
|
||||
* Added Opus support for sound playback in TWBlue. Thanks to `@masonasons <https://github.com/masonasons>`_
|
||||
* Added a source field in view tweet dialogue. Thanks to `@masonasons <https://github.com/masonasons>`_
|
||||
* You can load previous items in followers and friend buffers for others.
|
||||
* The Spell Checker dialogue should not display an error message when you have set "default language" in the global settings dialogue if your language is supported.
|
||||
* Updated romanian translation.
|
||||
* Some code cleanups.
|
||||
* The bug reports feature is fully operational again.
|
||||
* TWBlue should work again for users that contains special characters in windows usernames.
|
||||
* Added more options for the tweet searches.
|
||||
* Added play_audio to the keymap editor.
|
||||
* Windows key is no longer required in the keymap editor
|
||||
* Switched to the Microsoft translator.
|
||||
* You can update the current buffer by pressing ctrl+win+shift+u in the default keymap or in the buffer menu.
|
||||
* Changed some keystrokes in the windows 10 default keymap
|
||||
* New followers and friends buffer for user timelines.
|
56
documentation/source/conf.py
Normal file
56
documentation/source/conf.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'TWBlue'
|
||||
copyright = '2013 - 2022, MCV Software'
|
||||
author = 'MCV Software'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '2022.12.9'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx_rtd_theme',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
20
documentation/source/credits.rst
Normal file
20
documentation/source/credits.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
Credits, contact and acknowledgments
|
||||
--------------------------------------
|
||||
|
||||
Tw Blue is free software, licensed under the GNU GPL license, either version 2 or, at your option, any later version. You can view the license in the file named license.txt, or online at http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
|
||||
|
||||
The source code of the program is available on GitHub at https://www.github.com/mcv-software/twblue.
|
||||
|
||||
If you want to donate to the project, you can do so at https://twblue.es/donate. Thank you for your support!
|
||||
|
||||
Contact
|
||||
========
|
||||
|
||||
If you still have questions after reading this document, if you wish to collaborate to the project in some other way, or if you simply want to get in touch with the application developers, you can follow the accounts of TWBlue (`@tw_blue2 in Twitter <https://twitter.com/tw_blue2>`_ and `@twblue@maaw.social in mastodon <https://maaw.social/@twble>`_).
|
||||
|
||||
Credits
|
||||
---------
|
||||
|
||||
TWBlue is developed and maintained by `Manuel Cortéz <https://twitter.com/manuelcortez00>`_.
|
||||
|
||||
We would also like to thank the translators of TWBlue, who have allowed the spreading of the application.
|
3
documentation/source/global_settings.rst
Normal file
3
documentation/source/global_settings.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
global settings
|
||||
----------------
|
||||
|
22
documentation/source/index.rst
Normal file
22
documentation/source/index.rst
Normal file
@@ -0,0 +1,22 @@
|
||||
.. TWBlue documentation master file, created by
|
||||
sphinx-quickstart on Mon Dec 12 18:50:18 2022.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
TWBlue documentation
|
||||
==========================================
|
||||
|
||||
This is the user guide for the latest available version of TWBlue. The purpose of this manual, which has been divided into several chapters, is to inform about the use of this application. It is possible that this documentation will be updated throughout the development of TWBlue with new features. You can always find the latest version of this guide at its URL address: https://twblue.es/documentation
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Topics:
|
||||
|
||||
introduction
|
||||
system_requirements
|
||||
installation
|
||||
basic_concepts
|
||||
usage
|
||||
global_settings
|
||||
credits
|
||||
changelog
|
20
documentation/source/installation.rst
Normal file
20
documentation/source/installation.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
Installation
|
||||
-------------
|
||||
|
||||
TWBlue is available from its `Download page. <https://twblue.es/downloads>`_ On this page, you can always find the latest version of the application, completely free of charge. Currently, 4 different packages are distributed: two portable packages, for 32-bit and 64-bit systems; an installer, which selects the version to install depending on the architecture of the system where it runs, and a portable package for 32-bit, specially generated to be compatible with Windows 7.
|
||||
|
||||
:note: Windows 7 users must download the Windows 7 compatible package. Any other TWBlue package will be impossible to run under this operating system.
|
||||
|
||||
If you downloaded a portable version, simply unzip the file into an empty directory and run the twblue.exe file.
|
||||
|
||||
If you downloaded the installer, open the twblue_setup.exe file and follow the instructions to install the software. The installer will select the best version for your operating system architecture. When finished, you will be able to run TWBlue from the desktop or the windows start menu.
|
||||
|
||||
Troubleshooting
|
||||
++++++++++++++++
|
||||
|
||||
In case TWBlue doesn't work for any reason, please verify the following before getting in touch, just to make sure everything is alright in your system:
|
||||
|
||||
* for windows 7 Users, please have in mind that you will need to apply all available Windows Updates. Also you'll need the Service Pack 1 installed.
|
||||
* If TWBlue is unable to run and you are using Windows 8.1 or earlier, please make sure you have installed the Microsoft Visual C++ 2019 redistributable package. Download the `32 bit <https://aka.ms/vs/16/release/vc_redist.x86.exe>`_ or `64 bit <https://aka.ms/vs/16/release/vc_redist.x64.exe>`_ versiom.
|
||||
|
||||
If the application still doesn't work after you have checked this, please `Get in touch <contact.rst>`_ so we can help to determine what might b the issue and fix it for future versions.
|
6
documentation/source/introduction.rst
Normal file
6
documentation/source/introduction.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
Introduction
|
||||
==========================================
|
||||
|
||||
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 two different interfaces specially designed for screen reader users.
|
||||
|
||||
Please note that in order to use TWBlue, as well as to read this documentation, an internet connection and a Windows computer are required.
|
62
documentation/source/mastodon.rst
Normal file
62
documentation/source/mastodon.rst
Normal file
@@ -0,0 +1,62 @@
|
||||
Mastodon
|
||||
--------
|
||||
|
||||
Authorizing TWBlue
|
||||
==================
|
||||
|
||||
Buffer types
|
||||
============
|
||||
|
||||
Actions
|
||||
=======
|
||||
|
||||
Context menus
|
||||
==============
|
||||
|
||||
Posts
|
||||
++++++
|
||||
|
||||
Conversations
|
||||
++++++++++++++++
|
||||
|
||||
Users
|
||||
++++++
|
||||
|
||||
Hashtags
|
||||
+++++++++++++++
|
||||
|
||||
Composing posts
|
||||
==============
|
||||
|
||||
Sending Direct messages
|
||||
=======================
|
||||
|
||||
User actions
|
||||
============
|
||||
|
||||
Timelines
|
||||
=========
|
||||
|
||||
Hashtags
|
||||
======
|
||||
|
||||
Searches
|
||||
========
|
||||
|
||||
User aliases
|
||||
==============
|
||||
|
||||
Lists management
|
||||
===================
|
||||
|
||||
Account settings
|
||||
=================
|
||||
|
||||
User Autocomplete
|
||||
==================
|
||||
|
||||
Template editor
|
||||
================
|
||||
|
||||
Filters
|
||||
=======
|
11
documentation/source/system_requirements.rst
Normal file
11
documentation/source/system_requirements.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
System requirements
|
||||
==========================================
|
||||
|
||||
In order to run TWBlue successfully on a computer, these are the minimum system requirements. Please note that while it may be possible to use TWBlue under more limited resources, it is not recommended because the experience may be degraded or the application may not work as expected:
|
||||
|
||||
* Processor: Dual core processor, 1.3 GHZ or higher.
|
||||
* Operating system: Windows 7 (service pack 1), Windows 8/8.1, Windows 10 or Windows 11.
|
||||
* Hard Drive: Minimum 200 MB of free space.
|
||||
* RAM memory: 4 GB.
|
||||
|
||||
:note: TWBlue is compatible with Windows 7 systems (requires service pack 1 to be installed) in a special version, available on the `Project downloads page. <https://twblue.es/downloads>`_ This version is only available in a portable package, for 32-bit systems, and cannot be updated automatically. Windows 7 users will need to update TWBlue manually every time there is a new version to keep using the application.
|
153
documentation/source/twitter.rst
Normal file
153
documentation/source/twitter.rst
Normal file
@@ -0,0 +1,153 @@
|
||||
Twitter
|
||||
-------
|
||||
|
||||
Twitter is a social networking or micro-blogging tool which allows you to compose short status updates of your activities in 280 characters or less. Twitter is a way for friends, family and co-workers to communicate and stay connected through the exchange of quick, frequent messages. You can restrict delivery of updates to those in your circle of friends or, by default, allow anyone to access them.
|
||||
|
||||
You can monitor the status of updates from your friends, family or co-workers (known as following), and they in turn can read any updates you create, (known as followers). The updates are referred to as Tweets. The Tweets are posted to your Twitter profile or Blog and are searchable using Twitter Search.
|
||||
|
||||
In order to use TWBlue, you must first have created an account on the Twitter website. The process for signing up for a Twitter account is very accessible. During the account registration, you will need to choose a Twitter username. This serves two purposes. This is the method through which people will comunicate with you, but most importantly, your username and password will be required to connect TWBlue to your Twitter account. We suggest you choose a username which is memorable both to you and the people you hope will follow you.
|
||||
|
||||
We'll start from the premise that you have a Twitter account with its corresponding username and password.
|
||||
|
||||
authorizing TWBlue
|
||||
==================
|
||||
|
||||
First of all, it's necessary to authorise the program so it can access your Twitter account and act on your behalf. The authorisation process is quite simple, and the program never retains data such as your password. In order to authorise the application, you just need to run the main executable file, called TWBlue.exe (on some computers it may appear simply as TWBlue if Windows Explorer is not set to display file extensions). We suggest you may like to place a Windows shortcut on your Desktop pointing to this executable file for quick and easy location.
|
||||
|
||||
You can log into several Twitter accounts simultaneously. The program refers to each Twitter account you have configured as a "Session". If this is the first time you have launched TWBlue, and if no Twitter session exists, you will see the Session Manager. This dialogue box allows you to authorise as many accounts as you wish. If you press the Tab key to reach the "new account" button and activate it by pressing the Space Bar, you will see a menu with all available services to configure in TWBlue. By choosing Twitter, a dialogue box will advise you that your default internet browser will be opened in order to authorise the application and you will be asked if you would like to continue. Activate the "yes" Button by pressing the letter "Y" so the process may start.
|
||||
|
||||
Your default browser will open on the Twitter page to request authorisation. Enter your username and password into the appropriate edit fields if you're not already logged in, select the authorise button, and press it.
|
||||
|
||||
Once you've authorised your twitter account, the website will redirect you to a page which will notify you that TWBlue has been authorised successfully. On this page, you will be shown a code composed of several numbers that you must paste in the TWBlue authorization dialogue in order to allow the application to access your account. Once you have pasted the code in the corresponding text field, press enter to finish the account setup and go back to the session manager. On the session list, you will see a new item temporarily called "Authorised account x" -where x is a number. The session name will change once you open that session.
|
||||
|
||||
To start running TWBlue, press the Ok button in the Session Manager dialogue. By default, the program starts all the configured sessions automatically, however, you can change this behavior.
|
||||
|
||||
If all went well, the application will start playing sounds, indicating your data is being updated.
|
||||
|
||||
When the process is finished, by default the program will play another sound, and the screen reader will say "ready" (this behaviour can be configured).
|
||||
|
||||
Buffer types
|
||||
============
|
||||
|
||||
The following is a description for every one of TWBlue's buffers available for Twitter sessions, and the kind of items they work with.
|
||||
|
||||
* Home: this shows all the tweets on the main timeline. These are the tweets by users you follow.
|
||||
* Mentions: if a user, whether you follow them or not, mentions you on Twitter, you will find it in this list.
|
||||
* Direct messages: here you will find the private direct messages you exchange with users who follow you , or with any user, if you allow direct messages from everyone (this setting is configurable from Twitter). This list only shows received messages.
|
||||
* Sent direct messages: this buffer shows all the direct messages sent from your account.
|
||||
* Sent tweets: this shows all the tweets sent from your account.
|
||||
* Likes: here you will see all the tweets you have liked.
|
||||
* Followers: when users follow you, you'll be able to see them on this buffer, with some of their account details.
|
||||
* Following: the same as the previous buffer, but these are the users you follow.
|
||||
* Muted users: when you mute someone in Twitter, you no longer see their tweets in any of your buffers. However, you still can receive direct messages from them. This buffer contains the list of your muted users.
|
||||
* Blocked users: Contains a list with all people you have blocked in Twitter. When blocking an user, they cannot see your profile, timelines or exchange direct messages with your account.
|
||||
* User timelines: these are buffers you may create. They contain only the tweets by a specific user. They're used so you can see the tweets by a single person and you don't want to look all over your timeline. You may create as many as you like.
|
||||
* Lists: A list is similar to a user timeline, except that you can configure it to contain tweets from multiple users.
|
||||
* Search: A search buffer contains the results of a search operation.
|
||||
* User likes: You can have the program create a buffer containing tweets liked by a particular user.
|
||||
* Followers or following timeline: You can have TWBlue create a buffer containing all users who follow, or are followed by a specific user.
|
||||
* Trending Topics: a trend buffer shows the top 50 most used terms in a geographical region. This region may be a country or a city. Trends are updated every five minutes.
|
||||
|
||||
If a tweet contains a URL, you can press enter in the GUI to open it. If it contains video or audio, including live stream content, you can press Control + Enter to play it, respectively. TWBlue will play a sound if the tweet contains video metadata or the \#audio hashtag, but there may be tweets which contain media without this.
|
||||
|
||||
Actions
|
||||
=======
|
||||
|
||||
The available actions for each buffer may vary, and are found by pressing the Tab key, once a specific buffer has been focused. Note that these are not all the actions available for the Twitter session in TWBlue. Generally, you can perform the actions you want from 3 different ways:
|
||||
|
||||
* In each buffer, by pressing the Tab key until you find the action panel.
|
||||
* By using the menu bar, which can be accessed by pressing the Alt key.
|
||||
* Using the keyboard shortcuts, either from the graphical interface or the invisible interface.
|
||||
|
||||
You can find more information on the meaning of these actions later in this topic:
|
||||
|
||||
* For all sessions, you can log in or log out of TWBlue, as well as control whether the session should start automatically when you start the application.
|
||||
* For buffers containing tweets (home, mentions, favorites, timelines and searches), you can write a new tweet, reply to the currently selected tweet, retweet it or send a direct message to the user who wrote it.
|
||||
* For buffers containing direct messages, you can reply to the selected direct message.
|
||||
* For buffers containing users (followers, following, blocked users, muted, timelines and searches), the available actions are to mention the selected user, send a direct message or open the actions dialog for that user.
|
||||
* Buffers showing trends allow you to write a Tweet about the selected trend.
|
||||
|
||||
Context menus
|
||||
==============
|
||||
|
||||
By pressing the contextual menu key, or the right mouse button on a focused item, TWBlue will display a list of some actions that can be performed on it. These actions will be different depending on the type of item that is selected (for example, tweets allow you to retweet them, while a trend will allow you to perform a Twitter search on it). Below are the options that you will be able to see in the menus, sorted according to the type of element that is selected when displayed:
|
||||
|
||||
Tweets
|
||||
++++++
|
||||
|
||||
* Retweet: this option retweets the message you're reading. After you press it, if you haven't configured the application not to do so, you'll be asked if you want to add a comment or simply send it as written. If you choose to add a comment, it will post a quoted tweet, that is, the comment with a link to the originating tweet.
|
||||
* Reply: when you're viewing a tweet, you can reply to the user who sent it by pressing this option. A dialogue will open up similar to the one for tweeting. If there are more users referred to in the tweet, you can press tab and activate the mention to all checkbox, or enabling checkbox for the users you want to mention separately. Note, however, that sometimes -especially when replying to a retweet or quoted tweet, the user who made the retweet or quote may also be mentioned. This is done by Twitter automatically.
|
||||
* Like: Adds the tweet you're viewing to your likes list.
|
||||
* Unlike: removes the tweet from your likes, but not from Twitter.
|
||||
* Open URL: if the focused tweet contains URL addresses, shows up a dialogue where you can choose which one to open in the default web browser. Bear in mind that if the tweet contains only one URL, it will open it automatically.
|
||||
* Open in Twitter: Opens the focused tweet in Twitter's website.
|
||||
* Play audio: if the focused tweet contains audio or video streams, shows up a dialogue where you can choose which one to play. TWBlue uses a reduced version of VLC media player to play most audio and video streams. Bear in mind that if the tweet contains only one audio or video URL, it will play it automatically.
|
||||
* Show tweet: opens up a dialogue box where you can read the item which has focus. You can read the text with the arrow keys. It's a similar dialog box as used for composing tweets, without the ability to send the tweet, file attachment and autocompleting capabilities. It does however include a retweets and likes count.
|
||||
* Copy to clipboard: copies focused item text to clipboard.
|
||||
* Delete: permanently removes the tweet which has focus from Twitter and from your lists. Bear in mind that Twitter only allows you to delete tweets you have posted yourself.
|
||||
* User actions: Opens a dialogue where you can interact with a user. This dialogue box will be populated with the user who sent the item in focus. User actions are described later.
|
||||
|
||||
Direct messages
|
||||
++++++++++++++++
|
||||
|
||||
* Reply: Opens a direct message dialogue, from where you can reply to the focused direct message. Sending direct messages will be described later.
|
||||
* Open URL: if the focused item contains URL addresses, shows up a dialogue where you can choose which one to open in the default web browser. Bear in mind that if the item contains only one URL, it will open it automatically.
|
||||
* Play audio: if the focused item contains audio or video streams, shows up a dialogue where you can choose which one to play. TWBlue uses a reduced version of VLC media player to play most audio and video streams. Bear in mind that if the item contains only one audio or video URL, it will play it automatically.
|
||||
* Show direct message: opens up a dialogue box where you can read the item which has focus. You can read the text with the arrow keys. It's a dialogue with the text to be read and a close button.
|
||||
* Copy to clipboard: copies focused item text to clipboard.
|
||||
* Delete: permanently removes the direct message which has focus from your account. Note that this will not remove the message from its recipient's account.
|
||||
* User actions: Opens a dialogue where you can interact with a user. This dialogue box will be populated with the user who sent the item in focus. User actions are described later.
|
||||
|
||||
Users
|
||||
++++++
|
||||
|
||||
* Direct message: Opens up a dialogue box from where it is possible to send a private message to the focused user.
|
||||
* Show user profile: Opens up a dialog box from where it is possible to see details for the focused user. You can read the dialog by using the up and down arrow keys. If the user has a website in their profile, you can press tab and find a button to open it in your default browser.
|
||||
* Show user:
|
||||
* Open in Twitter:
|
||||
* Copy to clipboard:
|
||||
* User actions:
|
||||
|
||||
Trending topics
|
||||
+++++++++++++++
|
||||
|
||||
* Search topic:
|
||||
* Tweet about this trend:
|
||||
* Show item:
|
||||
* Copy to clipboard:
|
||||
|
||||
Posting Tweets
|
||||
==============
|
||||
|
||||
Sending Direct messages
|
||||
=======================
|
||||
|
||||
User actions
|
||||
============
|
||||
|
||||
Timelines
|
||||
=========
|
||||
|
||||
Trends
|
||||
======
|
||||
|
||||
Searches
|
||||
========
|
||||
|
||||
User aliases
|
||||
==============
|
||||
|
||||
Lists management
|
||||
===================
|
||||
|
||||
Account settings
|
||||
=================
|
||||
|
||||
User Autocomplete
|
||||
==================
|
||||
|
||||
Template editor
|
||||
================
|
||||
|
||||
Filters
|
||||
=======
|
11
documentation/source/usage.rst
Normal file
11
documentation/source/usage.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
Usage
|
||||
-----
|
||||
|
||||
TWBlue can create Twitter or Mastodon sessions. In this chapter, you can choose to visit the topic of your interest, depending on the account you are setting up. You will find specific information about the social network of your choice in the following topics:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Session types:
|
||||
|
||||
twitter
|
||||
mastodon
|
@@ -1,9 +1,10 @@
|
||||
wxpython==4.1.1
|
||||
wxpython
|
||||
pytest
|
||||
coverage
|
||||
wheel
|
||||
six
|
||||
future
|
||||
configobj
|
||||
markdown
|
||||
future
|
||||
requests
|
||||
oauthlib
|
||||
requests-oauthlib
|
||||
@@ -12,7 +13,6 @@ pypubsub
|
||||
geopy
|
||||
arrow
|
||||
python-dateutil
|
||||
futures
|
||||
winpaths
|
||||
PySocks
|
||||
win_inet_pton
|
||||
@@ -31,6 +31,7 @@ backports.functools_lru_cache
|
||||
cx_freeze
|
||||
tweepy
|
||||
twitter-text-parser
|
||||
mastodon.py
|
||||
pyenchant
|
||||
sqlitedict
|
||||
cx-Logging
|
||||
@@ -49,6 +50,7 @@ numpy
|
||||
pillow
|
||||
charset-normalizer
|
||||
demoji
|
||||
psutil
|
||||
git+https://github.com/accessibleapps/libloader
|
||||
git+https://github.com/accessibleapps/platform_utils
|
||||
git+https://github.com/accessibleapps/accessible_output2
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,16 @@
|
||||
# -*- 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'
|
||||
short_name='twblue'
|
||||
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"]
|
||||
authorEmail = "manuel@manuelcortez.net"
|
||||
copyright = "Copyright (C) 2013-2022, MCV Software."
|
||||
|
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import base as base
|
||||
from . import twitter as twitter
|
||||
from . import twitter as twitter
|
||||
from . import mastodon as mastodon
|
@@ -138,4 +138,7 @@ class Buffer(object):
|
||||
try:
|
||||
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
|
||||
except AttributeError:
|
||||
pass
|
||||
pass
|
||||
|
||||
def view_item(self):
|
||||
pass
|
@@ -1,2 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .base import BaseBuffer
|
||||
from .base import BaseBuffer
|
||||
from .mentions import MentionsBuffer
|
||||
from .conversations import ConversationBuffer, ConversationListBuffer
|
||||
from .users import UserBuffer
|
||||
from .notifications import NotificationsBuffer
|
@@ -1,141 +1,541 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Common logic to all buffers in TWBlue."""
|
||||
import logging
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import wx
|
||||
import output
|
||||
import sound
|
||||
import widgetUtils
|
||||
import arrow
|
||||
import webbrowser
|
||||
import output
|
||||
import config
|
||||
import sound
|
||||
import languageHandler
|
||||
import logging
|
||||
from mastodon import MastodonNotFoundError
|
||||
from audio_services import youtube_utils
|
||||
from controller.buffers.base import base
|
||||
from controller.mastodon import messages
|
||||
from sessions.mastodon import compose, utils, templates
|
||||
from mysc.thread_utils import call_threaded
|
||||
from pubsub import pub
|
||||
from extra import ocr
|
||||
from wxUI import buffers, dialogs, commonMessageDialogs
|
||||
from wxUI.dialogs.mastodon import menus
|
||||
from wxUI.dialogs.mastodon import dialogs as mastodon_dialogs
|
||||
|
||||
log = logging.getLogger("controller.buffers.base.base")
|
||||
log = logging.getLogger("controller.buffers.mastodon.base")
|
||||
|
||||
class Buffer(object):
|
||||
""" A basic buffer object. This should be the base class for all other derived buffers."""
|
||||
class BaseBuffer(base.Buffer):
|
||||
def __init__(self, parent, function, name, sessionObject, account, sound=None, compose_func="compose_post", *args, **kwargs):
|
||||
super(BaseBuffer, self).__init__(parent, function, *args, **kwargs)
|
||||
log.debug("Initializing buffer %s, account %s" % (name, account,))
|
||||
self.create_buffer(parent, name)
|
||||
self.invisible = True
|
||||
self.name = name
|
||||
self.type = self.buffer.type
|
||||
self.session = sessionObject
|
||||
self.compose_function = getattr(compose, compose_func)
|
||||
log.debug("Compose_function: %s" % (self.compose_function,))
|
||||
self.account = account
|
||||
self.buffer.account = account
|
||||
self.bind_events()
|
||||
self.sound = sound
|
||||
if "-timeline" in self.name or "-followers" in self.name or "-following" in self.name:
|
||||
self.finished_timeline = False
|
||||
|
||||
def __init__(self, parent=None, function=None, session=None, *args, **kwargs):
|
||||
"""Inits the main controller for this buffer:
|
||||
@ parent wx.Treebook object: Container where we will put this buffer.
|
||||
@ function str or None: function to be called periodically and update items on this buffer.
|
||||
@ session sessionmanager.session object or None: Session handler for settings, database and data access.
|
||||
"""
|
||||
super(Buffer, self).__init__()
|
||||
self.function = function
|
||||
# Compose_function will be used to render an object on this buffer. Normally, signature is as follows:
|
||||
# compose_function(item, db, relative_times, show_screen_names=False, session=None)
|
||||
# Read more about compose functions in sessions/twitter/compose.py.
|
||||
self.compose_function = None
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
# This will be used as a reference to the wx.Panel object wich stores the buffer GUI.
|
||||
self.buffer = None
|
||||
# This should countains the account associated to this buffer.
|
||||
self.account = ""
|
||||
# This controls whether the start_stream function should be called when starting the program.
|
||||
self.needs_init = True
|
||||
# if this is set to False, the buffer will be ignored on the invisible interface.
|
||||
self.invisible = False
|
||||
# Control variable, used to track time of execution for calls to start_stream.
|
||||
self.execution_time = 0
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = buffers.mastodon.basePanel(parent, name)
|
||||
|
||||
def clear_list(self):
|
||||
pass
|
||||
|
||||
def get_event(self, ev):
|
||||
""" Catch key presses in the WX interface and generate the corresponding event names."""
|
||||
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio"
|
||||
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url"
|
||||
elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
|
||||
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
|
||||
elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list"
|
||||
elif ev.GetKeyCode() == wx.WXK_DELETE: event = "destroy_status"
|
||||
# Raise a Special event when pressed Shift+F10 because Wx==4.1.x does not seems to trigger this by itself.
|
||||
# See https://github.com/manuelcortez/TWBlue/issues/353
|
||||
elif ev.GetKeyCode() == wx.WXK_F10 and ev.ShiftDown(): event = "show_menu"
|
||||
else:
|
||||
event = None
|
||||
ev.Skip()
|
||||
if event != None:
|
||||
try:
|
||||
### ToDo: Remove after WX fixes issue #353 in the widgets.
|
||||
if event == "show_menu":
|
||||
return self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
|
||||
getattr(self, event)()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def volume_down(self):
|
||||
""" Decreases volume by 5%"""
|
||||
if self.session.settings["sound"]["volume"] > 0.0:
|
||||
if self.session.settings["sound"]["volume"] <= 0.05:
|
||||
self.session.settings["sound"]["volume"] = 0.0
|
||||
else:
|
||||
self.session.settings["sound"]["volume"] -=0.05
|
||||
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0))
|
||||
self.session.sound.play("volume_changed.ogg")
|
||||
self.session.settings.write()
|
||||
|
||||
def volume_up(self):
|
||||
""" Increases volume by 5%."""
|
||||
if self.session.settings["sound"]["volume"] < 1.0:
|
||||
if self.session.settings["sound"]["volume"] >= 0.95:
|
||||
self.session.settings["sound"]["volume"] = 1.0
|
||||
else:
|
||||
self.session.settings["sound"]["volume"] +=0.05
|
||||
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100))
|
||||
self.session.sound.play("volume_changed.ogg")
|
||||
self.session.settings.write()
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True):
|
||||
pass
|
||||
|
||||
def get_more_items(self):
|
||||
output.speak(_(u"This action is not supported for this buffer"), True)
|
||||
|
||||
def put_items_on_list(self, items):
|
||||
pass
|
||||
|
||||
def remove_buffer(self):
|
||||
return False
|
||||
|
||||
def remove_item(self, item):
|
||||
f = self.buffer.list.get_selected()
|
||||
self.buffer.list.remove_item(item)
|
||||
self.buffer.list.select_item(f)
|
||||
|
||||
def bind_events(self):
|
||||
pass
|
||||
|
||||
def get_object(self):
|
||||
return self.buffer
|
||||
|
||||
def get_message(self):
|
||||
pass
|
||||
|
||||
def set_list_position(self, reversed=False):
|
||||
if reversed == False:
|
||||
self.buffer.list.select_item(-1)
|
||||
else:
|
||||
self.buffer.list.select_item(0)
|
||||
|
||||
def reply(self):
|
||||
pass
|
||||
|
||||
def send_message(self):
|
||||
pass
|
||||
|
||||
def share_item(self):
|
||||
pass
|
||||
|
||||
def can_share(self):
|
||||
pass
|
||||
|
||||
def destroy_status(self):
|
||||
pass
|
||||
def get_buffer_name(self):
|
||||
""" Get buffer name from a set of different techniques."""
|
||||
# firstly let's take the easier buffers.
|
||||
basic_buffers = dict(home_timeline=_("Home"), local_timeline=_("Local"), federated_timeline=_("Federated"), mentions=_("Mentions"), bookmarks=_("Bookmarks"), direct_messages=_("Direct messages"), sent=_("Sent"), favorites=_("Favorites"), followers=_("Followers"), following=_("Following"), blocked=_("Blocked users"), muted=_("Muted users"), notifications=_("Notifications"))
|
||||
if self.name in list(basic_buffers.keys()):
|
||||
return basic_buffers[self.name]
|
||||
# Check user timelines
|
||||
elif hasattr(self, "username"):
|
||||
if "-timeline" in self.name:
|
||||
return _(u"{username}'s timeline").format(username=self.username,)
|
||||
elif "-followers" in self.name:
|
||||
return _(u"{username}'s followers").format(username=self.username,)
|
||||
elif "-following" in self.name:
|
||||
return _(u"{username}'s following").format(username=self.username,)
|
||||
log.error("Error getting name for buffer %s" % (self.name,))
|
||||
return _(u"Unknown buffer")
|
||||
|
||||
def post_status(self, *args, **kwargs):
|
||||
title = _("Post")
|
||||
caption = _("Write your post here")
|
||||
post = messages.post(session=self.session, title=title, caption=caption)
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, posts=post_data, visibility=post.get_visibility(), **kwargs)
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def get_formatted_message(self):
|
||||
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):
|
||||
post = self.get_item()
|
||||
if post == None:
|
||||
return
|
||||
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"])
|
||||
return t
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
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("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
min_id = None
|
||||
# toDo: Implement reverse timelines properly here.
|
||||
if (self.name != "favorites" and self.name != "bookmarks") and self.name in self.session.db and len(self.session.db[self.name]) > 0:
|
||||
if self.session.settings["general"]["reverse_timelines"]:
|
||||
min_id = self.session.db[self.name][0].id
|
||||
else:
|
||||
min_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
results = getattr(self.session.api, self.function)(min_id=min_id, limit=count, *self.args, **self.kwargs)
|
||||
results.reverse()
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
|
||||
if "-timeline" in self.name:
|
||||
self.username = self.session.db[self.name][0]["account"].username
|
||||
self.finished_timeline = True
|
||||
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:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def auto_read(self, number_of_items):
|
||||
if 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:
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
post = self.session.db[self.name][-1]
|
||||
else:
|
||||
post = self.session.db[self.name][0]
|
||||
output.speak(_("New post in {0}").format(self.get_buffer_name()))
|
||||
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:
|
||||
output.speak(_("{0} new posts in {1}.").format(number_of_items, self.get_buffer_name()))
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
max_id = self.session.db[self.name][0].id
|
||||
else:
|
||||
max_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
items = getattr(self.session.api, self.function)(max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], *self.args, **self.kwargs)
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
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:
|
||||
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"], safe=safe)
|
||||
self.buffer.list.insert_item(True, *post)
|
||||
else:
|
||||
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"], safe=safe)
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
self.buffer.list.select_item(selection)
|
||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||
|
||||
def remove_buffer(self, force=False):
|
||||
if "-timeline" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.kwargs.get("id") in self.session.settings["other_buffers"]["timelines"]:
|
||||
self.session.settings["other_buffers"]["timelines"].remove(self.kwargs.get("id"))
|
||||
self.session.settings.write()
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
else:
|
||||
output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True)
|
||||
return False
|
||||
|
||||
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, 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, 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, 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, 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"])
|
||||
|
||||
def update_item(self, item, position):
|
||||
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])
|
||||
|
||||
def bind_events(self):
|
||||
log.debug("Binding events...")
|
||||
self.buffer.set_focus_function(self.onFocus)
|
||||
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.share_item, self.buffer.boost)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.send_message, self.buffer.dm)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.toggle_favorite, self.buffer.fav)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.toggle_bookmark, self.buffer.bookmark)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
def show_menu(self, ev, pos=0, *args, **kwargs):
|
||||
if self.buffer.list.get_count() == 0:
|
||||
return
|
||||
menu = menus.base()
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.audio, menuitem=menu.play)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.destroy_status, menuitem=menu.remove)
|
||||
if hasattr(menu, "openInBrowser"):
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.open_in_browser, menuitem=menu.openInBrowser)
|
||||
if pos != 0:
|
||||
self.buffer.PopupMenu(menu, pos)
|
||||
else:
|
||||
self.buffer.PopupMenu(menu, self.buffer.list.list.GetPosition())
|
||||
|
||||
def view(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="view_item")
|
||||
|
||||
def copy(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="copy_to_clipboard")
|
||||
|
||||
def user_actions(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="follow")
|
||||
|
||||
def fav(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="add_to_favourites")
|
||||
|
||||
def unfav(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="remove_from_favourites")
|
||||
|
||||
def delete_item_(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="delete_item")
|
||||
|
||||
def url_(self, *args, **kwargs):
|
||||
self.url()
|
||||
|
||||
def show_menu_by_key(self, ev):
|
||||
if self.buffer.list.get_count() == 0:
|
||||
return
|
||||
if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU:
|
||||
self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
|
||||
|
||||
def get_item(self):
|
||||
index = self.buffer.list.get_selected()
|
||||
if index > -1 and self.session.db.get(self.name) != None:
|
||||
return self.session.db[self.name][index]
|
||||
|
||||
def can_share(self):
|
||||
post = self.get_item()
|
||||
if post.visibility == "direct":
|
||||
return False
|
||||
return True
|
||||
|
||||
def reply(self, *args):
|
||||
item = self.get_item()
|
||||
visibility = item.visibility
|
||||
if visibility == "direct":
|
||||
title = _("Conversation with {}").format(item.account.username)
|
||||
caption = _("Write your message here")
|
||||
else:
|
||||
title = _("Reply to {}").format(item.account.username)
|
||||
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:
|
||||
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:
|
||||
users.append("@{} ".format(item.reblog.account.acct))
|
||||
else:
|
||||
users = ["@{} ".format(user.acct) for user in item.mentions if user.id != self.session.db["user_id"]]
|
||||
if "@{} ".format(item.account.acct) not in users and item.account.id != self.session.db["user_id"]:
|
||||
users.insert(0, "@{} ".format(item.account.acct))
|
||||
users_str = "".join(users)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
visibility_settings = dict(public=0, unlisted=1, private=2, direct=3)
|
||||
post.message.visibility.SetSelection(visibility_settings.get(visibility))
|
||||
# Respect content warning settings.
|
||||
if item.sensitive:
|
||||
post.message.sensitive.SetValue(item.sensitive)
|
||||
post.message.spoiler.ChangeValue(item.spoiler_text)
|
||||
post.message.on_sensitivity_changed()
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, reply_to=item.id, posts=post_data, visibility=post.get_visibility())
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
title = _("Conversation with {}").format(item.account.username)
|
||||
caption = _("Write your message here")
|
||||
if item.reblog != None:
|
||||
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:
|
||||
users.append("@{} ".format(item.reblog.account.acct))
|
||||
else:
|
||||
users = ["@{} ".format(user.acct) for user in item.mentions if user.id != self.session.db["user_id"]]
|
||||
if item.account.acct not in users and item.account.id != self.session.db["user_id"]:
|
||||
users.insert(0, "@{} ".format(item.account.acct))
|
||||
users_str = "".join(users)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
post.message.visibility.SetSelection(3)
|
||||
if item.sensitive:
|
||||
post.message.sensitive.SetValue(item.sensitive)
|
||||
post.message.spoiler.ChangeValue(item.spoiler_text)
|
||||
post.message.on_sensitivity_changed()
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, posts=post_data, visibility="direct", reply_to=item.id)
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def share_item(self, *args, **kwargs):
|
||||
if self.can_share() == False:
|
||||
return output.speak(_("This action is not supported on conversations."))
|
||||
post = self.get_item()
|
||||
id = post.id
|
||||
if self.session.settings["general"]["boost_mode"] == "ask":
|
||||
answer = mastodon_dialogs.boost_question()
|
||||
if answer == True:
|
||||
self._direct_boost(id)
|
||||
else:
|
||||
self._direct_boost(id)
|
||||
|
||||
def _direct_boost(self, id):
|
||||
item = self.session.api_call(call_name="status_reblog", _sound="retweet_send.ogg", id=id)
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
post = 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(), 2, ts)
|
||||
if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post):
|
||||
self.session.sound.play("audio.ogg")
|
||||
if self.session.settings['sound']['indicate_img'] and utils.is_image(post):
|
||||
self.session.sound.play("image.ogg")
|
||||
can_share = self.can_share()
|
||||
pub.sendMessage("toggleShare", shareable=can_share)
|
||||
self.buffer.boost.Enable(can_share)
|
||||
|
||||
def audio(self, url='', *args, **kwargs):
|
||||
if sound.URLPlayer.player.is_playing():
|
||||
return sound.URLPlayer.stop_audio()
|
||||
item = self.get_item()
|
||||
if item == None:
|
||||
return
|
||||
urls = utils.get_media_urls(item)
|
||||
if len(urls) == 1:
|
||||
url=urls[0]
|
||||
elif len(urls) > 1:
|
||||
urls_list = dialogs.urlList.urlList()
|
||||
urls_list.populate_list(urls)
|
||||
if urls_list.get_response() == widgetUtils.OK:
|
||||
url=urls_list.get_string()
|
||||
if hasattr(urls_list, "destroy"): urls_list.destroy()
|
||||
if url != '':
|
||||
# try:
|
||||
sound.URLPlayer.play(url, self.session.settings["sound"]["volume"])
|
||||
# except:
|
||||
# log.error("Exception while executing audio method.")
|
||||
|
||||
def url(self, url='', announce=True, *args, **kwargs):
|
||||
if url == '':
|
||||
post = self.get_item()
|
||||
if post.reblog != None:
|
||||
urls = utils.find_urls(post.reblog)
|
||||
else:
|
||||
urls = utils.find_urls(post)
|
||||
if len(urls) == 1:
|
||||
url=urls[0]
|
||||
elif len(urls) > 1:
|
||||
urls_list = dialogs.urlList.urlList()
|
||||
urls_list.populate_list(urls)
|
||||
if urls_list.get_response() == widgetUtils.OK:
|
||||
url=urls_list.get_string()
|
||||
if hasattr(urls_list, "destroy"): urls_list.destroy()
|
||||
if url != '':
|
||||
if announce:
|
||||
output.speak(_(u"Opening URL..."), True)
|
||||
webbrowser.open_new_tab(url)
|
||||
|
||||
def clear_list(self):
|
||||
dlg = commonMessageDialogs.clear_list()
|
||||
if dlg == widgetUtils.YES:
|
||||
self.session.db[self.name] = []
|
||||
self.buffer.list.clear()
|
||||
|
||||
def destroy_status(self, *args, **kwargs):
|
||||
index = self.buffer.list.get_selected()
|
||||
item = self.session.db[self.name][index]
|
||||
if item.account.id != self.session.db["user_id"] or item.reblog != None:
|
||||
output.speak(_("You can delete only your own posts."))
|
||||
return
|
||||
answer = mastodon_dialogs.delete_post_dialog()
|
||||
if answer == True:
|
||||
items = self.session.db[self.name]
|
||||
try:
|
||||
self.session.api.status_delete(id=item.id)
|
||||
items.pop(index)
|
||||
self.buffer.list.remove_item(index)
|
||||
except Exception as e:
|
||||
self.session.sound.play("error.ogg")
|
||||
log.exception("")
|
||||
self.session.db[self.name] = items
|
||||
|
||||
def user_details(self):
|
||||
item = self.get_item()
|
||||
pass
|
||||
|
||||
def save_positions(self):
|
||||
def get_item_url(self):
|
||||
post = self.get_item()
|
||||
if post.reblog != None:
|
||||
return post.reblog.url
|
||||
return post.url
|
||||
|
||||
def open_in_browser(self, *args, **kwargs):
|
||||
url = self.get_item_url()
|
||||
output.speak(_("Opening item in web browser..."))
|
||||
webbrowser.open(url)
|
||||
|
||||
def add_to_favorites(self):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def remove_from_favorites(self):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def toggle_favorite(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
try:
|
||||
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
|
||||
except AttributeError:
|
||||
pass
|
||||
item = self.session.api.status(item.id)
|
||||
except MastodonNotFoundError:
|
||||
output.speak(_("No status found with that ID"))
|
||||
return
|
||||
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)
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def toggle_bookmark(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
try:
|
||||
item = self.session.api.status(item.id)
|
||||
except MastodonNotFoundError:
|
||||
output.speak(_("No status found with that ID"))
|
||||
return
|
||||
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)
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="status_unbookmark", preexec_message=_("Removing from bookmarks..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def view_item(self):
|
||||
post = self.get_item()
|
||||
# Update object so we can retrieve newer stats
|
||||
try:
|
||||
post = self.session.api.status(id=post.id)
|
||||
except MastodonNotFoundError:
|
||||
output.speak(_("No status found with that ID"))
|
||||
return
|
||||
# print(post)
|
||||
msg = messages.viewPost(post, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url())
|
||||
|
||||
def ocr_image(self):
|
||||
post = self.get_item()
|
||||
media_list = []
|
||||
pass
|
243
src/controller/buffers/mastodon/conversations.py
Normal file
243
src/controller/buffers/mastodon/conversations.py
Normal file
@@ -0,0 +1,243 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import logging
|
||||
import wx
|
||||
import widgetUtils
|
||||
import output
|
||||
from mastodon import MastodonNotFoundError
|
||||
from controller.mastodon import messages
|
||||
from controller.buffers.mastodon.base import BaseBuffer
|
||||
from mysc.thread_utils import call_threaded
|
||||
from sessions.mastodon import utils, templates
|
||||
from wxUI import buffers, commonMessageDialogs
|
||||
log = logging.getLogger("controller.buffers.mastodon.conversations")
|
||||
|
||||
class ConversationListBuffer(BaseBuffer):
|
||||
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = buffers.mastodon.conversationListPanel(parent, name)
|
||||
|
||||
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]["last_status"]
|
||||
|
||||
def get_conversation(self):
|
||||
index = self.buffer.list.get_selected()
|
||||
if index > -1 and self.session.db.get(self.name) != None:
|
||||
return self.session.db[self.name][index]
|
||||
|
||||
def get_formatted_message(self):
|
||||
return self.compose_function(self.get_conversation(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])[1]
|
||||
|
||||
def get_message(self):
|
||||
conversation = self.get_conversation()
|
||||
if conversation == None:
|
||||
return
|
||||
template = self.session.settings["templates"]["conversation"]
|
||||
post_template = self.session.settings["templates"]["post"]
|
||||
t = templates.render_conversation(conversation=conversation, template=template, post_template=post_template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
||||
return t
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
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("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
min_id = None
|
||||
try:
|
||||
results = getattr(self.session.api, self.function)(min_id=min_id, limit=count, *self.args, **self.kwargs)
|
||||
results.reverse()
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
new_position, number_of_items = self.order_buffer(results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
self.put_items_on_list(number_of_items)
|
||||
if new_position > -1:
|
||||
self.buffer.list.select_item(new_position)
|
||||
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:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
max_id = self.session.db[self.name][0].last_status.id
|
||||
else:
|
||||
max_id = self.session.db[self.name][-1].last_status.id
|
||||
try:
|
||||
items = getattr(self.session.api, self.function)(max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], *self.args, **self.kwargs)
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
for i in elements:
|
||||
conversation = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(True, *conversation)
|
||||
else:
|
||||
for i in elements:
|
||||
conversation = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(False, *conversation)
|
||||
self.buffer.list.select_item(selection)
|
||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||
|
||||
def get_item_position(self, conversation):
|
||||
for i in range(len(self.session.db[self.name])):
|
||||
if self.session.db[self.name][i].id == conversation.id:
|
||||
return i
|
||||
|
||||
def order_buffer(self, data):
|
||||
num = 0
|
||||
focus_object = None
|
||||
if self.session.db.get(self.name) == None:
|
||||
self.session.db[self.name] = []
|
||||
objects = self.session.db[self.name]
|
||||
for i in data:
|
||||
position = self.get_item_position(i)
|
||||
if position != None:
|
||||
conversation = self.session.db[self.name][position]
|
||||
if conversation.last_status.id != i.last_status.id:
|
||||
focus_object = i
|
||||
objects.pop(position)
|
||||
self.buffer.list.remove_item(position)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
objects.append(i)
|
||||
else:
|
||||
objects.insert(0, i)
|
||||
num = num+1
|
||||
else:
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
objects.append(i)
|
||||
else:
|
||||
objects.insert(0, i)
|
||||
num = num+1
|
||||
self.session.db[self.name] = objects
|
||||
if focus_object == None:
|
||||
return (-1, num)
|
||||
new_position = self.get_item_position(focus_object)
|
||||
if new_position != None:
|
||||
return (new_position, num)
|
||||
return (-1, num)
|
||||
|
||||
def bind_events(self):
|
||||
log.debug("Binding events...")
|
||||
self.buffer.set_focus_function(self.onFocus)
|
||||
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.reply, self.buffer.reply)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
def fav(self):
|
||||
pass
|
||||
|
||||
def unfav(self):
|
||||
pass
|
||||
|
||||
def can_share(self):
|
||||
return False
|
||||
|
||||
def send_message(self):
|
||||
return self.reply()
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
post = self.get_item()
|
||||
if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post):
|
||||
self.session.sound.play("audio.ogg")
|
||||
if self.session.settings['sound']['indicate_img'] and utils.is_image(post):
|
||||
self.session.sound.play("image.ogg")
|
||||
|
||||
def destroy_status(self):
|
||||
pass
|
||||
|
||||
def reply(self, *args):
|
||||
item = self.get_item()
|
||||
conversation = self.get_conversation()
|
||||
visibility = item.visibility
|
||||
title = _("Reply to conversation with {}").format(conversation.accounts[0].username)
|
||||
caption = _("Write your message here")
|
||||
users = ["@{} ".format(user.acct) for user in conversation.accounts]
|
||||
users_str = "".join(users)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
visibility_settings = dict(public=0, unlisted=1, private=2, direct=3)
|
||||
post.message.visibility.SetSelection(visibility_settings.get(visibility))
|
||||
if item.sensitive:
|
||||
post.message.sensitive.SetValue(item.sensitive)
|
||||
post.message.spoiler.ChangeValue(item.spoiler_text)
|
||||
post.message.on_sensitivity_changed()
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, reply_to=item.id, posts=post_data, visibility=visibility)
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
class ConversationBuffer(BaseBuffer):
|
||||
|
||||
def __init__(self, post, *args, **kwargs):
|
||||
self.post = post
|
||||
super(ConversationBuffer, self).__init__(*args, **kwargs)
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
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("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
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.
|
||||
try:
|
||||
results = []
|
||||
items = getattr(self.session.api, self.function)(*self.args, **self.kwargs)
|
||||
[results.append(item) for item in items.ancestors]
|
||||
results.append(self.post)
|
||||
[results.append(item) for item in items.descendants]
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, results)
|
||||
log.debug("Number of items retrieved: %d" % (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:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
|
||||
def get_more_items(self):
|
||||
output.speak(_(u"This action is not supported for this buffer"), True)
|
||||
|
||||
def remove_buffer(self, force=False):
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
115
src/controller/buffers/mastodon/mentions.py
Normal file
115
src/controller/buffers/mastodon/mentions.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import logging
|
||||
import output
|
||||
from controller.buffers.mastodon.base import BaseBuffer
|
||||
from sessions.mastodon import utils
|
||||
|
||||
log = logging.getLogger("controller.buffers.mastodon.mentions")
|
||||
|
||||
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):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
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("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
min_id = None
|
||||
try:
|
||||
items = getattr(self.session.api, self.function)(min_id=min_id, limit=count, types=["mention"], *self.args, **self.kwargs)
|
||||
items.reverse()
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, items)
|
||||
log.debug("Number of items retrieved: %d" % (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:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
max_id = self.session.db[self.name][0].id
|
||||
else:
|
||||
max_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
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)
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
print(items)
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
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:
|
||||
for i in elements:
|
||||
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)
|
||||
else:
|
||||
for i in elements:
|
||||
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.select_item(selection)
|
||||
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
|
201
src/controller/buffers/mastodon/users.py
Normal file
201
src/controller/buffers/mastodon/users.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import logging
|
||||
import wx
|
||||
import widgetUtils
|
||||
import output
|
||||
from mysc.thread_utils import call_threaded
|
||||
from controller.buffers.mastodon.base import BaseBuffer
|
||||
from controller.mastodon import messages
|
||||
from sessions.mastodon import templates, utils
|
||||
from wxUI import buffers, commonMessageDialogs
|
||||
|
||||
log = logging.getLogger("controller.buffers.mastodon.conversations")
|
||||
|
||||
class UserBuffer(BaseBuffer):
|
||||
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = buffers.mastodon.userPanel(parent, name)
|
||||
|
||||
def get_message(self):
|
||||
user = self.get_item()
|
||||
if user == None:
|
||||
return
|
||||
template = self.session.settings["templates"]["person"]
|
||||
t = templates.render_user(user=user, template=template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
||||
return t
|
||||
|
||||
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.send_message, self.buffer.message)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.user_actions, self.buffer.actions)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
def fav(self):
|
||||
pass
|
||||
|
||||
def unfav(self):
|
||||
pass
|
||||
|
||||
def can_share(self):
|
||||
return False
|
||||
|
||||
def reply(self, *args, **kwargs):
|
||||
return self.send_message()
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
title = _("New conversation with {}").format(item.username)
|
||||
caption = _("Write your message here")
|
||||
users_str = "@{} ".format(item.acct)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
post.message.visibility.SetSelection(3)
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, posts=post_data, visibility="direct")
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def audio(self):
|
||||
pass
|
||||
|
||||
def url(self):
|
||||
pass
|
||||
|
||||
def destroy_status(self):
|
||||
pass
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
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("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
try:
|
||||
results = getattr(self.session.api, self.function)(limit=count, *self.args, **self.kwargs)
|
||||
if hasattr(results, "_pagination_next") and self.name not in self.session.db["pagination_info"]:
|
||||
self.session.db["pagination_info"][self.name] = results._pagination_next
|
||||
results.reverse()
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
|
||||
if "-followers" in self.name or "-following" in self.name:
|
||||
self.username = self.session.api.account(id=self.kwargs.get("id")).username
|
||||
self.finished_timeline = True
|
||||
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:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
pagination_info = self.session.db["pagination_info"].get(self.name)
|
||||
if pagination_info == None:
|
||||
output.speak(_("There are no more items in this buffer."))
|
||||
return
|
||||
try:
|
||||
items = self.session.api.fetch_next(pagination_info)
|
||||
if hasattr(items, "_pagination_next"):
|
||||
self.session.db["pagination_info"][self.name] = items._pagination_next
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
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"])
|
||||
self.buffer.list.insert_item(True, *post)
|
||||
else:
|
||||
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"])
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
self.buffer.list.select_item(selection)
|
||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||
|
||||
def get_item_url(self):
|
||||
item = self.get_item()
|
||||
return item.url
|
||||
|
||||
def user_details(self):
|
||||
item = self.get_item()
|
||||
pass
|
||||
|
||||
def add_to_favorites(self):
|
||||
pass
|
||||
|
||||
def remove_from_favorites(self):
|
||||
pass
|
||||
|
||||
def toggle_favorite(self):
|
||||
pass
|
||||
|
||||
def view_item(self):
|
||||
item = self.get_item()
|
||||
print(item)
|
||||
|
||||
def ocr_image(self):
|
||||
pass
|
||||
|
||||
def remove_buffer(self, force=False):
|
||||
if "-followers" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.kwargs.get("id") in self.session.settings["other_buffers"]["followers_timelines"]:
|
||||
self.session.settings["other_buffers"]["followers_timelines"].remove(self.kwargs.get("id"))
|
||||
self.session.settings.write()
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
elif "-following" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.kwargs.get("id") in self.session.settings["other_buffers"]["following_timelines"]:
|
||||
self.session.settings["other_buffers"]["following_timelines"].remove(self.kwargs.get("id"))
|
||||
self.session.settings.write()
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
elif "-searchUser" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
else:
|
||||
output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True)
|
||||
return False
|
@@ -1,14 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
import wx
|
||||
from wxUI import buffers, dialogs, commonMessageDialogs, menus
|
||||
from controller import user
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import buffers, dialogs, commonMessageDialogs
|
||||
from controller import messages
|
||||
import wx
|
||||
import widgetUtils
|
||||
import arrow
|
||||
import webbrowser
|
||||
@@ -24,7 +16,10 @@ from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from tweepy.cursor import Cursor
|
||||
from pubsub import pub
|
||||
from extra import ocr
|
||||
from sessions.twitter.long_tweets import twishort, tweets
|
||||
from wxUI import buffers, dialogs, commonMessageDialogs, menus
|
||||
from controller.twitter import user, messages
|
||||
|
||||
log = logging.getLogger("controller.buffers")
|
||||
|
||||
@@ -40,9 +35,9 @@ class BaseBuffer(base.Buffer):
|
||||
super(BaseBuffer, self).__init__(parent, function, *args, **kwargs)
|
||||
log.debug("Initializing buffer %s, account %s" % (name, account,))
|
||||
if bufferType != None:
|
||||
self.buffer = getattr(buffers, bufferType)(parent, name)
|
||||
self.buffer = getattr(buffers.twitter, bufferType)(parent, name)
|
||||
else:
|
||||
self.buffer = buffers.basePanel(parent, name)
|
||||
self.buffer = buffers.twitter.basePanel(parent, name)
|
||||
self.invisible = True
|
||||
self.name = name
|
||||
self.type = self.buffer.type
|
||||
@@ -309,9 +304,6 @@ class BaseBuffer(base.Buffer):
|
||||
self.buffer.list.insert_item(True, *tweet)
|
||||
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(tweet[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
|
||||
#Improve performance on Windows
|
||||
# if platform.system() == "Windows":
|
||||
# call_threaded(utils.is_audio,item)
|
||||
|
||||
def bind_events(self):
|
||||
log.debug("Binding events...")
|
||||
@@ -322,7 +314,6 @@ class BaseBuffer(base.Buffer):
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.share_item, self.buffer.retweet)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.send_message, self.buffer.dm)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
|
||||
# Replace for the correct way in other platforms.
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
@@ -466,7 +457,7 @@ class BaseBuffer(base.Buffer):
|
||||
def _retweet_with_comment(self, tweet, id):
|
||||
if hasattr(tweet, "retweeted_status"):
|
||||
tweet = tweet.retweeted_status
|
||||
retweet = messages.tweet(session=self.session, title=_("Quote"), caption=_("Add your comment to the tweet"), max=256, thread_mode=False)
|
||||
retweet = messages.tweet(session=self.session, title=_("Quote"), caption=_("Add your comment to the tweet"), thread_mode=False)
|
||||
if retweet.message.ShowModal() == widgetUtils.OK:
|
||||
text = retweet.message.text.GetValue()
|
||||
tweet_data = dict(text=text, attachments=retweet.attachments, poll_period=retweet.poll_period, poll_options=retweet.poll_options)
|
||||
@@ -480,7 +471,7 @@ class BaseBuffer(base.Buffer):
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
tweet = self.get_tweet()
|
||||
if platform.system() == "Windows" and self.session.settings["general"]["relative_times"] == True:
|
||||
if self.session.settings["general"]["relative_times"] == True:
|
||||
# fix this:
|
||||
original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at, locale="en")
|
||||
ts = original_date.humanize(locale=languageHandler.getLanguage())
|
||||
@@ -589,4 +580,84 @@ class BaseBuffer(base.Buffer):
|
||||
def open_in_browser(self, *args, **kwargs):
|
||||
url = self.get_item_url()
|
||||
output.speak(_(u"Opening item in web browser..."))
|
||||
webbrowser.open(url)
|
||||
webbrowser.open(url)
|
||||
|
||||
def add_to_favorites(self):
|
||||
id = self.get_tweet().id
|
||||
call_threaded(self.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id)
|
||||
|
||||
def remove_from_favorites(self):
|
||||
id = self.get_tweet().id
|
||||
call_threaded(self.session.api_call, call_name="destroy_favorite", id=id)
|
||||
|
||||
def toggle_favorite(self):
|
||||
id = self.get_tweet().id
|
||||
tweet = self.session.twitter.get_status(id=id, include_ext_alt_text=True, tweet_mode="extended")
|
||||
if tweet.favorited == False:
|
||||
call_threaded(self.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id)
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="destroy_favorite", id=id)
|
||||
|
||||
def view_item(self):
|
||||
if self.type == "dm" or self.name == "direct_messages":
|
||||
non_tweet = self.get_formatted_message()
|
||||
item = self.get_right_tweet()
|
||||
original_date = arrow.get(int(item.created_timestamp))
|
||||
date = original_date.shift(seconds=self.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
|
||||
msg = messages.viewTweet(non_tweet, [], False, date=date)
|
||||
else:
|
||||
tweet, tweetsList = self.get_full_tweet()
|
||||
msg = messages.viewTweet(tweet, tweetsList, utc_offset=self.session.db["utc_offset"], item_url=self.get_item_url())
|
||||
|
||||
def reverse_geocode(self, geocoder):
|
||||
try:
|
||||
tweet = self.get_tweet()
|
||||
if tweet.coordinates != None:
|
||||
x = tweet.coordinates["coordinates"][0]
|
||||
y = tweet.coordinates["coordinates"][1]
|
||||
address = geocoder.reverse_geocode(y, x, language = languageHandler.curLang)
|
||||
return address
|
||||
else:
|
||||
output.speak(_("There are no coordinates in this tweet"))
|
||||
# except GeocoderError:
|
||||
# output.speak(_(u"There are no results for the coordinates in this tweet"))
|
||||
except ValueError:
|
||||
output.speak(_(u"Error decoding coordinates. Try again later."))
|
||||
except KeyError:
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def ocr_image(self):
|
||||
tweet = self.get_tweet()
|
||||
media_list = []
|
||||
if hasattr(tweet, "entities") and tweet.entities.get("media") != None:
|
||||
[media_list.append(i) for i in tweet.entities["media"] if i not in media_list]
|
||||
elif hasattr(tweet, "retweeted_status") and tweet.retweeted_status.get("media") != None:
|
||||
[media_list.append(i) for i in tweet.retweeted_status.entities["media"] if i not in media_list]
|
||||
elif hasattr(tweet, "quoted_status") and tweet.quoted_status.entities.get("media") != None:
|
||||
[media_list.append(i) for i in tweet.quoted_status.entities["media"] if i not in media_list]
|
||||
if len(media_list) > 1:
|
||||
image_list = [_(u"Picture {0}").format(i,) for i in range(0, len(media_list))]
|
||||
dialog = dialogs.urlList.urlList(title=_(u"Select the picture"))
|
||||
if dialog.get_response() == widgetUtils.OK:
|
||||
img = media_list[dialog.get_item()]
|
||||
else:
|
||||
return
|
||||
elif len(media_list) == 1:
|
||||
img = media_list[0]
|
||||
else:
|
||||
output.speak(_(u"Invalid buffer"))
|
||||
return
|
||||
if self.session.settings["mysc"]["ocr_language"] != "":
|
||||
ocr_lang = self.session.settings["mysc"]["ocr_language"]
|
||||
else:
|
||||
ocr_lang = ocr.OCRSpace.short_langs.index(tweet.lang)
|
||||
ocr_lang = ocr.OCRSpace.OcrLangs[ocr_lang]
|
||||
api = ocr.OCRSpace.OCRSpaceAPI()
|
||||
try:
|
||||
text = api.OCR_URL(img["media_url"], lang=ocr_lang)
|
||||
except ocr.OCRSpace.APIError as er:
|
||||
output.speak(_(u"Unable to extract text"))
|
||||
return
|
||||
msg = messages.viewTweet(text["ParsedText"], [], False)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import platform
|
||||
import widgetUtils
|
||||
import arrow
|
||||
import webbrowser
|
||||
@@ -7,12 +6,12 @@ import output
|
||||
import config
|
||||
import languageHandler
|
||||
import logging
|
||||
from controller import messages
|
||||
from sessions.twitter import compose, utils, templates
|
||||
from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from pubsub import pub
|
||||
from wxUI import commonMessageDialogs
|
||||
from controller.twitter import messages
|
||||
from . import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.dmBuffer")
|
||||
@@ -70,7 +69,7 @@ class DirectMessagesBuffer(base.BaseBuffer):
|
||||
self.session.db["sent_direct_messages"] = sent_dms
|
||||
user_ids = [item.message_create["sender_id"] for item in items]
|
||||
self.session.save_users(user_ids)
|
||||
pub.sendMessage("more-sent-dms", data=sent, account=self.session.db["user_name"])
|
||||
pub.sendMessage("twitter.more_sent_dms", data=sent, account=self.session.db["user_name"])
|
||||
selected = self.buffer.list.get_selected()
|
||||
if self.session.settings["general"]["reverse_timelines"] == True:
|
||||
for i in received:
|
||||
@@ -88,18 +87,11 @@ class DirectMessagesBuffer(base.BaseBuffer):
|
||||
output.speak(_(u"%s items retrieved") % (total), True)
|
||||
|
||||
def reply(self, *args, **kwargs):
|
||||
tweet = self.get_right_tweet()
|
||||
screen_name = self.session.get_user(tweet.message_create["sender_id"]).screen_name
|
||||
message = messages.reply(session=self.session, title=_("Mention"), caption=_("Mention to %s") % (screen_name,), text="@%s " % (screen_name,), thread_mode=False, users=[screen_name,])
|
||||
if message.message.ShowModal() == widgetUtils.OK:
|
||||
tweet_data = message.get_tweet_data()
|
||||
call_threaded(self.session.send_tweet, tweet_data)
|
||||
if hasattr(message.message, "destroy"):
|
||||
message.message.destroy()
|
||||
return self.send_message()
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
tweet = self.get_tweet()
|
||||
if platform.system() == "Windows" and self.session.settings["general"]["relative_times"] == True:
|
||||
if self.session.settings["general"]["relative_times"] == True:
|
||||
# fix this:
|
||||
original_date = arrow.get(int(tweet.created_timestamp))
|
||||
ts = original_date.humanize(locale=languageHandler.getLanguage())
|
||||
@@ -163,3 +155,10 @@ class SentDirectMessagesBuffer(DirectMessagesBuffer):
|
||||
dm = self.get_right_tweet()
|
||||
t = templates.render_dm(dm, template, self.session, relative_times=self.session.settings["general"]["relative_times"], offset_seconds=self.session.db["utc_offset"])
|
||||
return t
|
||||
|
||||
def view_item(self):
|
||||
non_tweet = self.get_formatted_message()
|
||||
item = self.get_right_tweet()
|
||||
original_date = arrow.get(int(item.created_timestamp))
|
||||
date = original_date.shift(seconds=self.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
|
||||
msg = messages.viewTweet(non_tweet, [], False, date=date)
|
||||
|
@@ -1,13 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
from wxUI import dialogs, commonMessageDialogs
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import dialogs, commonMessageDialogs
|
||||
import widgetUtils
|
||||
import logging
|
||||
from tweepy.cursor import Cursor
|
||||
from wxUI import dialogs, commonMessageDialogs
|
||||
from . import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.listBuffer")
|
||||
|
@@ -1,13 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
from wxUI import commonMessageDialogs, menus
|
||||
from controller import user
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import dialogs, commonMessageDialogs
|
||||
from controller import messages
|
||||
import widgetUtils
|
||||
import webbrowser
|
||||
import output
|
||||
@@ -16,7 +8,9 @@ import logging
|
||||
from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from pubsub import pub
|
||||
from controller.twitter import user, messages
|
||||
from sessions.twitter import compose, templates
|
||||
from wxUI import commonMessageDialogs, menus
|
||||
from . import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.peopleBuffer")
|
||||
@@ -252,4 +246,9 @@ class PeopleBuffer(base.BaseBuffer):
|
||||
def get_item_url(self, *args, **kwargs):
|
||||
tweet = self.get_tweet()
|
||||
url = "https://twitter.com/{screen_name}".format(screen_name=tweet.screen_name)
|
||||
return url
|
||||
return url
|
||||
|
||||
def view_item(self):
|
||||
item_url = self.get_item_url()
|
||||
non_tweet = self.get_formatted_message()
|
||||
msg = messages.viewTweet(non_tweet, [], False, item_url=item_url)
|
@@ -1,15 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
import locale
|
||||
if platform.system() == "Windows":
|
||||
from wxUI import commonMessageDialogs
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import commonMessageDialogs
|
||||
import widgetUtils
|
||||
import logging
|
||||
from tweepy.errors import TweepyException
|
||||
from wxUI import commonMessageDialogs
|
||||
from . import base, people
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.searchBuffer")
|
||||
@@ -67,6 +62,10 @@ class ConversationBuffer(SearchBuffer):
|
||||
last_thread_id = None
|
||||
last_reply_id = None
|
||||
|
||||
def __init__(self, tweet, *args, **kwargs):
|
||||
self.tweet = tweet
|
||||
super(ConversationBuffer, self).__init__(*args, **kwargs)
|
||||
|
||||
def start_stream(self, start=False, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory == True:
|
||||
|
@@ -1,20 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
import wx
|
||||
from wxUI import buffers, commonMessageDialogs, menus
|
||||
from controller import user, messages
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import buffers, commonMessageDialogs
|
||||
from controller import messages
|
||||
import wx
|
||||
import widgetUtils
|
||||
import output
|
||||
import logging
|
||||
from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from pubsub import pub
|
||||
from wxUI import buffers, commonMessageDialogs, menus
|
||||
from controller.twitter import user, messages
|
||||
from controller.buffers import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.trends")
|
||||
@@ -26,7 +20,7 @@ class TrendsBuffer(base.Buffer):
|
||||
self.session = sessionObject
|
||||
self.account = account
|
||||
self.invisible = True
|
||||
self.buffer = buffers.trendsPanel(parent, name)
|
||||
self.buffer = buffers.twitter.trendsPanel(parent, name)
|
||||
self.buffer.account = account
|
||||
self.type = self.buffer.type
|
||||
self.bind_events()
|
||||
|
File diff suppressed because it is too large
Load Diff
1
src/controller/mastodon/__init__.py
Normal file
1
src/controller/mastodon/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
229
src/controller/mastodon/handler.py
Normal file
229
src/controller/mastodon/handler.py
Normal file
@@ -0,0 +1,229 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import wx
|
||||
import logging
|
||||
from pubsub import pub
|
||||
from mysc import restart
|
||||
from wxUI.dialogs.mastodon import dialogs
|
||||
from wxUI.dialogs.mastodon import search as search_dialogs
|
||||
from wxUI.dialogs.mastodon import dialogs
|
||||
from wxUI import commonMessageDialogs
|
||||
from sessions.twitter import utils
|
||||
from . import userActions, settings
|
||||
|
||||
log = logging.getLogger("controller.mastodon.handler")
|
||||
|
||||
class Handler(object):
|
||||
|
||||
def __init__(self):
|
||||
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):
|
||||
session.get_user_info()
|
||||
name = session.get_name()
|
||||
controller.accounts.append(name)
|
||||
if createAccounts == True:
|
||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
||||
root_position =controller.view.search(name, name)
|
||||
for i in session.settings['general']['buffer_order']:
|
||||
if i == 'home':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Home"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="timeline_home", name="home_timeline", sessionObject=session, account=name, sound="tweet_received.ogg"))
|
||||
elif i == 'local':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Local"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="timeline_local", name="local_timeline", sessionObject=session, account=name, sound="tweet_received.ogg"))
|
||||
elif i == 'federated':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Federated"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="timeline_public", name="federated_timeline", sessionObject=session, account=name, sound="tweet_received.ogg"))
|
||||
elif i == 'mentions':
|
||||
pub.sendMessage("createBuffer", buffer_type="MentionsBuffer", session_type=session.type, buffer_title=_("Mentions"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="notifications", name="mentions", sessionObject=session, account=name, sound="mention_received.ogg"))
|
||||
elif i == 'direct_messages':
|
||||
pub.sendMessage("createBuffer", buffer_type="ConversationListBuffer", session_type=session.type, buffer_title=_("Direct messages"), parent_tab=root_position, start=False, kwargs=dict(compose_func="compose_conversation", parent=controller.view.nb, function="conversations", name="direct_messages", sessionObject=session, account=name, sound="dm_received.ogg"))
|
||||
elif i == 'sent':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Sent"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="sent", sessionObject=session, account=name, sound="tweet_received.ogg", id=session.db["user_id"]))
|
||||
elif i == 'favorites':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Favorites"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="favourites", name="favorites", sessionObject=session, account=name, sound="favourite.ogg"))
|
||||
elif i == 'bookmarks':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Bookmarks"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="bookmarks", name="bookmarks", sessionObject=session, account=name, sound="favourite.ogg"))
|
||||
elif i == 'followers':
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Followers"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="followers", sessionObject=session, account=name, sound="update_followers.ogg", id=session.db["user_id"]))
|
||||
elif i == 'following':
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Following"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="following", sessionObject=session, account=name, sound="update_followers.ogg", id=session.db["user_id"]))
|
||||
elif i == 'muted':
|
||||
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':
|
||||
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))
|
||||
timelines_position =controller.view.search("timelines", name)
|
||||
for i in session.settings["other_buffers"]["timelines"]:
|
||||
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"]:
|
||||
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"]:
|
||||
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))
|
||||
# lists_position =controller.view.search("lists", session.db["user_name"])
|
||||
# for i in session.settings["other_buffers"]["lists"]:
|
||||
# pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=session.type, buffer_title=_(u"List for {}").format(i), parent_tab=lists_position, start=False, kwargs=dict(parent=controller.view.nb, function="list_timeline", name="%s-list" % (i,), sessionObject=session, name, bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Searches"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="searches", account=name))
|
||||
# searches_position =controller.view.search("searches", session.db["user_name"])
|
||||
# for i in session.settings["other_buffers"]["tweet_searches"]:
|
||||
# pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_(u"Search for {}").format(i), parent_tab=searches_position, start=False, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (i,), sessionObject=session, name, bufferType="searchPanel", sound="search_updated.ogg", q=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
# for i in session.settings["other_buffers"]["trending_topic_buffers"]:
|
||||
# pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (i), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (i,), sessionObject=session, name, trendsFor=i, sound="trends_updated.ogg"))
|
||||
|
||||
def start_buffer(self, controller, buffer):
|
||||
if hasattr(buffer, "finished_timeline") and buffer.finished_timeline == False:
|
||||
change_title = True
|
||||
else:
|
||||
change_title = False
|
||||
try:
|
||||
buffer.start_stream(play_sound=False)
|
||||
except Exception as err:
|
||||
log.exception("Error %s starting buffer %s on account %s, with args %r and kwargs %r." % (str(err), buffer.name, buffer.account, buffer.args, buffer.kwargs))
|
||||
if change_title:
|
||||
pub.sendMessage("buffer-title-changed", buffer=buffer)
|
||||
|
||||
def open_conversation(self, controller, buffer):
|
||||
post = buffer.get_item()
|
||||
if post.reblog != None:
|
||||
post = post.reblog
|
||||
conversations_position =controller.view.search("direct_messages", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="ConversationBuffer", session_type=buffer.session.type, buffer_title=_("Conversation with {0}").format(post.account.acct), parent_tab=conversations_position, start=True, kwargs=dict(parent=controller.view.nb, function="status_context", name="%s-conversation" % (post.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="search_updated.ogg", post=post, id=post.id))
|
||||
|
||||
def follow(self, buffer):
|
||||
if not hasattr(buffer, "get_item"):
|
||||
return
|
||||
item = buffer.get_item()
|
||||
if buffer.type == "user":
|
||||
users = [item.acct]
|
||||
elif buffer.type == "baseBuffer":
|
||||
if item.reblog != None:
|
||||
users = [user.acct for user in item.reblog.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.reblog.account.acct not in users and item.account.id != buffer.session.db["user_id"]:
|
||||
users.insert(0, item.reblog.account.acct)
|
||||
else:
|
||||
users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.account.acct not in users:
|
||||
users.insert(0, item.account.acct)
|
||||
u = userActions.userActions(buffer.session, users)
|
||||
|
||||
def search(self, controller, session, value):
|
||||
log.debug("Creating a new search...")
|
||||
dlg = search_dialogs.searchDialog(value)
|
||||
if dlg.ShowModal() == wx.ID_OK and dlg.term.GetValue() != "":
|
||||
term = dlg.term.GetValue()
|
||||
searches_position =controller.view.search("searches", session.get_name())
|
||||
if dlg.posts.GetValue() == True:
|
||||
if term not in session.settings["other_buffers"]["post_searches"]:
|
||||
session.settings["other_buffers"]["post_searches"].append(term)
|
||||
session.settings.write()
|
||||
# pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (term,), sessionObject=session, account=session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", q=term, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
else:
|
||||
log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,))
|
||||
return
|
||||
elif dlg.users.GetValue() == True:
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_search", name="%s-searchUser" % (term,), sessionObject=session, account=session.get_name(), sound="search_updated.ogg", q=term))
|
||||
dlg.Destroy()
|
||||
|
||||
# ToDo: explore how to play sound & save config differently.
|
||||
# currently, TWBlue will play the sound and save the config for the timeline even if the buffer did not load or something else.
|
||||
def open_timeline(self, controller, buffer):
|
||||
if not hasattr(buffer, "get_item"):
|
||||
return
|
||||
item = buffer.get_item()
|
||||
if buffer.type == "user":
|
||||
users = [item.acct]
|
||||
elif buffer.type == "baseBuffer":
|
||||
if item.reblog != None:
|
||||
users = [user.acct for user in item.reblog.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.reblog.account.acct not in users and item.account.id != buffer.session.db["user_id"]:
|
||||
users.insert(0, item.reblog.account.acct)
|
||||
else:
|
||||
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"]:
|
||||
users.insert(0, item.account.acct)
|
||||
u = userActions.UserTimeline(buffer.session, users)
|
||||
if u.dialog.ShowModal() == wx.ID_OK:
|
||||
action = u.process_action()
|
||||
if action == None:
|
||||
return
|
||||
user = u.user
|
||||
if action == "posts":
|
||||
if user.statuses_count == 0:
|
||||
dialogs.no_posts()
|
||||
return
|
||||
if user.id in buffer.session.settings["other_buffers"]["timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
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))
|
||||
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif action == "followers":
|
||||
if user.followers_count == 0:
|
||||
dialogs.no_followers()
|
||||
return
|
||||
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
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))
|
||||
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif action == "following":
|
||||
if user.following_count == 0:
|
||||
dialogs.no_following()
|
||||
return
|
||||
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
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))
|
||||
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
buffer.session.settings.write()
|
||||
|
||||
def account_settings(self, buffer, controller):
|
||||
d = settings.accountSettingsController(buffer, controller)
|
||||
if d.response == wx.ID_OK:
|
||||
d.save_configuration()
|
||||
if d.needs_restart == True:
|
||||
commonMessageDialogs.needs_restart()
|
||||
buffer.session.settings.write()
|
||||
buffer.session.save_persistent_data()
|
||||
restart.restart_program()
|
232
src/controller/mastodon/messages.py
Normal file
232
src/controller/mastodon/messages.py
Normal file
@@ -0,0 +1,232 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import wx
|
||||
import widgetUtils
|
||||
import config
|
||||
import output
|
||||
from controller.twitter import messages
|
||||
from sessions.mastodon import templates
|
||||
from wxUI.dialogs.mastodon import postDialogs
|
||||
|
||||
class post(messages.basicTweet):
|
||||
def __init__(self, session, title, caption, text="", *args, **kwargs):
|
||||
# take max character limit from session as this might be different for some instances.
|
||||
self.max = session.char_limit
|
||||
self.title = title
|
||||
self.session = session
|
||||
self.message = postDialogs.Post(caption=caption, text=text, *args, **kwargs)
|
||||
self.message.SetTitle(title)
|
||||
self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
|
||||
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
|
||||
widgetUtils.connect_event(self.message.text, widgetUtils.ENTERED_TEXT, self.text_processor)
|
||||
widgetUtils.connect_event(self.message.translate, widgetUtils.BUTTON_PRESSED, self.translate)
|
||||
widgetUtils.connect_event(self.message.add, widgetUtils.BUTTON_PRESSED, self.on_attach)
|
||||
widgetUtils.connect_event(self.message.remove_attachment, widgetUtils.BUTTON_PRESSED, self.remove_attachment)
|
||||
# ToDo: Add autocomplete feature to mastodon and uncomment this.
|
||||
# widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
|
||||
widgetUtils.connect_event(self.message.add_post, widgetUtils.BUTTON_PRESSED, self.add_post)
|
||||
widgetUtils.connect_event(self.message.remove_post, widgetUtils.BUTTON_PRESSED, self.remove_post)
|
||||
self.attachments = []
|
||||
self.thread = []
|
||||
self.text_processor()
|
||||
|
||||
def add_post(self, event, update_gui=True, *args, **kwargs):
|
||||
text = self.message.text.GetValue()
|
||||
attachments = self.attachments[::]
|
||||
postdata = dict(text=text, attachments=attachments, sensitive=self.message.sensitive.GetValue(), spoiler_text=None)
|
||||
if postdata.get("sensitive") == True:
|
||||
postdata.update(spoiler_text=self.message.spoiler.GetValue())
|
||||
self.thread.append(postdata)
|
||||
self.attachments = []
|
||||
if update_gui:
|
||||
self.message.reset_controls()
|
||||
self.message.add_item(item=[text, len(attachments)], list_type="post")
|
||||
self.message.text.SetFocus()
|
||||
self.text_processor()
|
||||
|
||||
def get_post_data(self):
|
||||
self.add_post(event=None, update_gui=False)
|
||||
return self.thread
|
||||
|
||||
def text_processor(self, *args, **kwargs):
|
||||
super(post, self).text_processor(*args, **kwargs)
|
||||
if len(self.thread) > 0:
|
||||
if hasattr(self.message, "posts"):
|
||||
self.message.posts.Enable(True)
|
||||
self.message.remove_post.Enable(True)
|
||||
else:
|
||||
self.message.posts.Enable(False)
|
||||
self.message.remove_post.Enable(False)
|
||||
if len(self.attachments) > 0:
|
||||
self.message.attachments.Enable(True)
|
||||
self.message.remove_attachment.Enable(True)
|
||||
else:
|
||||
self.message.attachments.Enable(False)
|
||||
self.message.remove_attachment.Enable(False)
|
||||
if len(self.message.text.GetValue()) > 0 or len(self.attachments) > 0:
|
||||
self.message.add_post.Enable(True)
|
||||
else:
|
||||
self.message.add_post.Enable(False)
|
||||
|
||||
def remove_post(self, *args, **kwargs):
|
||||
post = self.message.posts.GetFocusedItem()
|
||||
if post > -1 and len(self.thread) > post:
|
||||
self.thread.pop(post)
|
||||
self.message.remove_item(list_type="post")
|
||||
self.text_processor()
|
||||
self.message.text.SetFocus()
|
||||
|
||||
def can_attach(self):
|
||||
if len(self.attachments) == 0:
|
||||
return True
|
||||
elif len(self.attachments) == 1 and (self.attachments[0]["type"] == "poll" or self.attachments[0]["type"] == "video" or self.attachments[0]["type"] == "audio"):
|
||||
return False
|
||||
elif len(self.attachments) < 4:
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_attach(self, *args, **kwargs):
|
||||
can_attach = self.can_attach()
|
||||
menu = self.message.attach_menu(can_attach)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_image, self.message.add_image)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_video, self.message.add_video)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_audio, self.message.add_audio)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_poll, self.message.add_poll)
|
||||
self.message.PopupMenu(menu, self.message.add.GetPosition())
|
||||
|
||||
def on_attach_image(self, *args, **kwargs):
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
image, description = self.message.get_image()
|
||||
if image != None:
|
||||
if image.endswith("gif"):
|
||||
image_type = "gif"
|
||||
else:
|
||||
image_type = "photo"
|
||||
imageInfo = {"type": image_type, "file": image, "description": description}
|
||||
if len(self.attachments) > 0 and image_type == "gif":
|
||||
return self.message.unable_to_attach_file()
|
||||
self.attachments.append(imageInfo)
|
||||
self.message.add_item(item=[os.path.basename(imageInfo["file"]), imageInfo["type"], imageInfo["description"]])
|
||||
self.text_processor()
|
||||
|
||||
def on_attach_video(self, *args, **kwargs):
|
||||
if len(self.attachments) >= 4:
|
||||
return self.message.unable_to_attach_file()
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
video = self.message.get_video()
|
||||
if video != None:
|
||||
videoInfo = {"type": "video", "file": video, "description": ""}
|
||||
self.attachments.append(videoInfo)
|
||||
self.message.add_item(item=[os.path.basename(videoInfo["file"]), videoInfo["type"], videoInfo["description"]])
|
||||
self.text_processor()
|
||||
|
||||
def on_attach_audio(self, *args, **kwargs):
|
||||
if len(self.attachments) >= 4:
|
||||
return self.message.unable_to_attach_file()
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
audio = self.message.get_audio()
|
||||
if audio != None:
|
||||
audioInfo = {"type": "audio", "file": audio, "description": ""}
|
||||
self.attachments.append(audioInfo)
|
||||
self.message.add_item(item=[os.path.basename(audioInfo["file"]), audioInfo["type"], audioInfo["description"]])
|
||||
self.text_processor()
|
||||
|
||||
def on_attach_poll(self, *args, **kwargs):
|
||||
if len(self.attachments) > 0:
|
||||
return self.message.unable_to_attach_poll()
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
dlg = postDialogs.poll()
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
day = 86400
|
||||
periods = [300, 1800, 3600, 21600, day, day*2, day*3, day*4, day*5, day*6, day*7]
|
||||
period = periods[dlg.period.GetSelection()]
|
||||
poll_options = dlg.get_options()
|
||||
multiple = dlg.multiple.GetValue()
|
||||
hide_totals = dlg.hide_votes.GetValue()
|
||||
data = dict(type="poll", file="", description=_("Poll with {} options").format(len(poll_options)), options=poll_options, expires_in=period, multiple=multiple, hide_totals=hide_totals)
|
||||
self.attachments.append(data)
|
||||
self.message.add_item(item=[data["file"], data["type"], data["description"]])
|
||||
self.text_processor()
|
||||
dlg.Destroy()
|
||||
|
||||
def get_data(self):
|
||||
self.add_post(event=None, update_gui=False)
|
||||
return self.thread
|
||||
|
||||
def get_visibility(self):
|
||||
visibility_settings = ["public", "unlisted", "private", "direct"]
|
||||
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):
|
||||
def __init__(self, post, offset_hours=0, date="", item_url=""):
|
||||
if post.reblog != None:
|
||||
post = post.reblog
|
||||
author = post.account.display_name if post.account.display_name != "" else post.account.username
|
||||
title = _(u"Post from {}").format(author)
|
||||
image_description = templates.process_image_descriptions(post.media_attachments)
|
||||
text = templates.process_text(post, safe=False)
|
||||
date = templates.process_date(post.created_at, relative_times=False, offset_hours=offset_hours)
|
||||
privacy_settings = dict(public=_("Public"), unlisted=_("Not listed"), private=_("followers only"), direct=_("Direct"))
|
||||
privacy = privacy_settings.get(post.visibility)
|
||||
boost_count = str(post.reblogs_count)
|
||||
favs_count = str(post.favourites_count)
|
||||
# Gets the client from where this post was made.
|
||||
source_obj = post.get("application")
|
||||
if source_obj == None:
|
||||
source = _("Remote instance")
|
||||
else:
|
||||
source = source_obj.get("name")
|
||||
self.message = postDialogs.viewPost(text=text, boosts_count=boost_count, favs_count=favs_count, source=source, date=date, privacy=privacy)
|
||||
self.message.SetTitle(title)
|
||||
if image_description != "":
|
||||
self.message.image_description.Enable(True)
|
||||
self.message.image_description.ChangeValue(image_description)
|
||||
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
|
||||
if item_url != "":
|
||||
self.message.enable_button("share")
|
||||
widgetUtils.connect_event(self.message.share, widgetUtils.BUTTON_PRESSED, self.share)
|
||||
self.item_url = item_url
|
||||
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
|
||||
self.message.ShowModal()
|
||||
|
||||
# We won't need text_processor in this dialog, so let's avoid it.
|
||||
def text_processor(self):
|
||||
pass
|
||||
|
||||
def share(self, *args, **kwargs):
|
||||
if hasattr(self, "item_url"):
|
||||
output.copy(self.item_url)
|
||||
output.speak(_("Link copied to clipboard."))
|
218
src/controller/mastodon/settings.py
Normal file
218
src/controller/mastodon/settings.py
Normal file
@@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import threading
|
||||
import logging
|
||||
import sound_lib
|
||||
import paths
|
||||
import widgetUtils
|
||||
import output
|
||||
from collections import OrderedDict
|
||||
from wxUI import commonMessageDialogs
|
||||
from wxUI.dialogs.mastodon import configuration
|
||||
from extra.autocompletionUsers import scan, manage
|
||||
from extra.ocr import OCRSpace
|
||||
from controller.settings import globalSettingsController
|
||||
from . templateEditor import EditTemplate
|
||||
|
||||
log = logging.getLogger("Settings")
|
||||
|
||||
class accountSettingsController(globalSettingsController):
|
||||
def __init__(self, buffer, window):
|
||||
self.user = buffer.session.db["user_name"]
|
||||
self.buffer = buffer
|
||||
self.window = window
|
||||
self.config = buffer.session.settings
|
||||
self.dialog = configuration.configurationDialog()
|
||||
self.create_config()
|
||||
self.needs_restart = False
|
||||
self.is_started = True
|
||||
|
||||
def create_config(self):
|
||||
self.dialog.create_general_account()
|
||||
# widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
|
||||
# widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
|
||||
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
|
||||
self.dialog.set_value("general", "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", "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", "reverse_timelines", self.config["general"]["reverse_timelines"])
|
||||
boost_mode = self.config["general"]["boost_mode"]
|
||||
if boost_mode == "ask":
|
||||
self.dialog.set_value("general", "ask_before_boost", True)
|
||||
else:
|
||||
self.dialog.set_value("general", "ask_before_boost", False)
|
||||
self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"]))
|
||||
self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"])
|
||||
self.dialog.create_reporting()
|
||||
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"])
|
||||
self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
|
||||
post_template = self.config["templates"]["post"]
|
||||
conversation_template = self.config["templates"]["conversation"]
|
||||
person_template = self.config["templates"]["person"]
|
||||
self.dialog.create_templates(post_template=post_template, conversation_template=conversation_template, person_template=person_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.post, widgetUtils.BUTTON_PRESSED, self.edit_post_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.conversation, widgetUtils.BUTTON_PRESSED, self.edit_conversation_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.person, widgetUtils.BUTTON_PRESSED, self.edit_person_template)
|
||||
self.dialog.create_other_buffers()
|
||||
buffer_values = self.get_buffers_list()
|
||||
self.dialog.buffers.insert_buffers(buffer_values)
|
||||
self.dialog.buffers.connect_hook_func(self.toggle_buffer_active)
|
||||
widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state)
|
||||
widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up)
|
||||
widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down)
|
||||
self.input_devices = sound_lib.input.Input.get_device_names()
|
||||
self.output_devices = sound_lib.output.Output.get_device_names()
|
||||
self.soundpacks = []
|
||||
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ]
|
||||
self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks)
|
||||
self.dialog.set_value("sound", "volumeCtrl", int(self.config["sound"]["volume"]*100))
|
||||
self.dialog.set_value("sound", "input", self.config["sound"]["input_device"])
|
||||
self.dialog.set_value("sound", "output", self.config["sound"]["output_device"])
|
||||
self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"])
|
||||
self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"])
|
||||
self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"])
|
||||
self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"])
|
||||
self.dialog.create_extras(OCRSpace.translatable_langs)
|
||||
language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"])
|
||||
self.dialog.extras.ocr_lang.SetSelection(language_index)
|
||||
self.dialog.realize()
|
||||
self.dialog.set_title(_("Account settings for %s") % (self.user,))
|
||||
self.response = self.dialog.get_response()
|
||||
|
||||
def edit_post_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["post"]
|
||||
control = EditTemplate(template=template, type="post")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["post"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.post.SetLabel(_("Edit template for posts. Current template: {}").format(result))
|
||||
|
||||
def edit_conversation_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["conversation"]
|
||||
control = EditTemplate(template=template, type="conversation")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["conversation"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.conversation.SetLabel(_("Edit template for conversations. Current template: {}").format(result))
|
||||
|
||||
def edit_person_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["person"]
|
||||
control = EditTemplate(template=template, type="person")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["person"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.person.SetLabel(_("Edit template for persons. Current template: {}").format(result))
|
||||
|
||||
def save_configuration(self):
|
||||
if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"):
|
||||
self.needs_restart = True
|
||||
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"]["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"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
|
||||
self.config["general"]["max_posts_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
|
||||
if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
|
||||
self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in database strategy management.")
|
||||
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
|
||||
if self.dialog.get_value("general", "persist_size") == '':
|
||||
self.config["general"]["persist_size"] =-1
|
||||
else:
|
||||
try:
|
||||
self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size"))
|
||||
except ValueError:
|
||||
output.speak("Invalid cache size, setting to default.",True)
|
||||
self.config["general"]["persist_size"] =1764
|
||||
|
||||
if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in timeline order.")
|
||||
self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines")
|
||||
ask_before_boost = self.dialog.get_value("general", "ask_before_boost")
|
||||
if ask_before_boost == True:
|
||||
self.config["general"]["boost_mode"] = "ask"
|
||||
else:
|
||||
self.config["general"]["boost_mode"] = "direct"
|
||||
buffers_list = self.dialog.buffers.get_list()
|
||||
if buffers_list != self.config["general"]["buffer_order"]:
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in buffer ordering.")
|
||||
self.config["general"]["buffer_order"] = buffers_list
|
||||
self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting")
|
||||
self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting")
|
||||
self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()]
|
||||
if self.config["sound"]["input_device"] != self.dialog.sound.get("input"):
|
||||
self.config["sound"]["input_device"] = self.dialog.sound.get("input")
|
||||
try:
|
||||
self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"]))
|
||||
except:
|
||||
self.config["sound"]["input_device"] = "default"
|
||||
if self.config["sound"]["output_device"] != self.dialog.sound.get("output"):
|
||||
self.config["sound"]["output_device"] = self.dialog.sound.get("output")
|
||||
try:
|
||||
self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"]))
|
||||
except:
|
||||
self.config["sound"]["output_device"] = "default"
|
||||
self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0
|
||||
self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute")
|
||||
self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack")
|
||||
self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio")
|
||||
self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img")
|
||||
self.buffer.session.sound.config = self.config["sound"]
|
||||
self.buffer.session.sound.check_soundpack()
|
||||
self.config.write()
|
||||
|
||||
def toggle_state(self,*args,**kwargs):
|
||||
return self.dialog.buffers.change_selected_item()
|
||||
|
||||
def on_autocompletion_scan(self, *args, **kwargs):
|
||||
configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window)
|
||||
to_scan = configuration.show_dialog()
|
||||
if to_scan == True:
|
||||
configuration.prepare_progress_dialog()
|
||||
t = threading.Thread(target=configuration.scan)
|
||||
t.start()
|
||||
|
||||
def on_autocompletion_manage(self, *args, **kwargs):
|
||||
configuration = manage.autocompletionManage(self.buffer.session)
|
||||
configuration.show_settings()
|
||||
|
||||
def get_buffers_list(self):
|
||||
all_buffers=OrderedDict()
|
||||
all_buffers['home']=_("Home")
|
||||
all_buffers['local'] = _("Local")
|
||||
all_buffers['federated'] = _("Federated")
|
||||
all_buffers['mentions']=_("Mentions")
|
||||
all_buffers['direct_messages']=_("Direct Messages")
|
||||
all_buffers['sent']=_("Sent")
|
||||
all_buffers['favorites']=_("Favorites")
|
||||
all_buffers['bookmarks']=_("Bookmarks")
|
||||
all_buffers['followers']=_("Followers")
|
||||
all_buffers['following']=_("Following")
|
||||
all_buffers['blocked']=_("Blocked users")
|
||||
all_buffers['muted']=_("Muted users")
|
||||
all_buffers['notifications']=_("Notifications")
|
||||
list_buffers = []
|
||||
hidden_buffers=[]
|
||||
all_buffers_keys = list(all_buffers.keys())
|
||||
# Check buffers shown first.
|
||||
for i in self.config["general"]["buffer_order"]:
|
||||
if i in all_buffers_keys:
|
||||
list_buffers.append((i, all_buffers[i], True))
|
||||
# This second pass will retrieve all hidden buffers.
|
||||
for i in all_buffers_keys:
|
||||
if i not in self.config["general"]["buffer_order"]:
|
||||
hidden_buffers.append((i, all_buffers[i], False))
|
||||
list_buffers.extend(hidden_buffers)
|
||||
return list_buffers
|
||||
|
||||
def toggle_buffer_active(self, ev):
|
||||
change = self.dialog.buffers.get_event(ev)
|
||||
if change == True:
|
||||
self.dialog.buffers.change_selected_item()
|
40
src/controller/mastodon/templateEditor.py
Normal file
40
src/controller/mastodon/templateEditor.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import wx
|
||||
from typing import List
|
||||
from sessions.mastodon.templates import post_variables, conversation_variables, person_variables
|
||||
from wxUI.dialogs.twitterDialogs import templateDialogs
|
||||
|
||||
class EditTemplate(object):
|
||||
def __init__(self, template: str, type: str) -> None:
|
||||
super(EditTemplate, self).__init__()
|
||||
self.default_template = template
|
||||
if type == "post":
|
||||
self.variables = post_variables
|
||||
elif type == "conversation":
|
||||
self.variables = conversation_variables
|
||||
else:
|
||||
self.variables = person_variables
|
||||
self.template: str = template
|
||||
|
||||
def validate_template(self, template: str) -> bool:
|
||||
used_variables: List[str] = re.findall("\$\w+", template)
|
||||
validated: bool = True
|
||||
for var in used_variables:
|
||||
if var[1:] not in self.variables:
|
||||
validated = False
|
||||
return validated
|
||||
|
||||
def run_dialog(self) -> str:
|
||||
dialog = templateDialogs.EditTemplateDialog(template=self.template, variables=self.variables, default_template=self.default_template)
|
||||
response = dialog.ShowModal()
|
||||
if response == wx.ID_SAVE:
|
||||
validated: bool = self.validate_template(dialog.template.GetValue())
|
||||
if validated == False:
|
||||
templateDialogs.invalid_template()
|
||||
self.template = dialog.template.GetValue()
|
||||
return self.run_dialog()
|
||||
else:
|
||||
return dialog.template.GetValue()
|
||||
else:
|
||||
return ""
|
101
src/controller/mastodon/userActions.py
Normal file
101
src/controller/mastodon/userActions.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import widgetUtils
|
||||
import output
|
||||
from wxUI.dialogs.mastodon import userActions as userActionsDialog
|
||||
from wxUI.dialogs.mastodon import userTimeline as userTimelineDialog
|
||||
from pubsub import pub
|
||||
from mastodon import MastodonError, MastodonNotFoundError
|
||||
from extra.autocompletionUsers import completion
|
||||
|
||||
log = logging.getLogger("controller.mastodon.userActions")
|
||||
|
||||
class BasicUserSelector(object):
|
||||
def __init__(self, session, users=[]):
|
||||
super(BasicUserSelector, self).__init__()
|
||||
self.session = session
|
||||
self.create_dialog(users=users)
|
||||
|
||||
def create_dialog(self, users):
|
||||
pass
|
||||
|
||||
def autocomplete_users(self, *args, **kwargs):
|
||||
c = completion.autocompletionUsers(self.dialog, self.session.session_id)
|
||||
c.show_menu("dm")
|
||||
|
||||
def search_user(self, user):
|
||||
try:
|
||||
user = self.session.api.account_lookup(user)
|
||||
return user
|
||||
except MastodonError:
|
||||
log.exception("Error searching for user %s.".format(user))
|
||||
|
||||
class userActions(BasicUserSelector):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(userActions, self).__init__(*args, **kwargs)
|
||||
if self.dialog.get_response() == widgetUtils.OK:
|
||||
self.process_action()
|
||||
|
||||
def create_dialog(self, users):
|
||||
self.dialog = userActionsDialog.UserActionsDialog(users)
|
||||
widgetUtils.connect_event(self.dialog.autocompletion, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
|
||||
|
||||
def process_action(self):
|
||||
action = self.dialog.get_action()
|
||||
user = self.dialog.get_user()
|
||||
user = self.search_user(user)
|
||||
if user == None:
|
||||
return
|
||||
getattr(self, action)(user)
|
||||
|
||||
def follow(self, user):
|
||||
try:
|
||||
self.session.api.account_follow(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unfollow(self, user):
|
||||
try:
|
||||
result = self.session.api.account_unfollow(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def mute(self, user):
|
||||
try:
|
||||
id = self.session.api.account_mute(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unmute(self, user):
|
||||
try:
|
||||
id = self.session.api.account_unmute(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def block(self, user):
|
||||
try:
|
||||
id = self.session.api.account_block(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unblock(self, user):
|
||||
try:
|
||||
id = self.session.api.account_unblock(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
class UserTimeline(BasicUserSelector):
|
||||
|
||||
def create_dialog(self, users):
|
||||
self.dialog = userTimelineDialog.UserTimeline(users)
|
||||
widgetUtils.connect_event(self.dialog.autocompletion, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
|
||||
|
||||
def process_action(self):
|
||||
action = self.dialog.get_action()
|
||||
user = self.dialog.get_user()
|
||||
user = self.search_user(user)
|
||||
if user == None:
|
||||
return
|
||||
self.user = user
|
||||
return action
|
@@ -1,25 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import webbrowser
|
||||
import threading
|
||||
import logging
|
||||
import sound_lib
|
||||
import paths
|
||||
import widgetUtils
|
||||
import config
|
||||
import languageHandler
|
||||
import output
|
||||
import application
|
||||
import config_utils
|
||||
import keys
|
||||
from collections import OrderedDict
|
||||
from pubsub import pub
|
||||
from mysc import autostart as autostart_windows
|
||||
from wxUI.dialogs import configuration
|
||||
from wxUI import commonMessageDialogs
|
||||
from extra.autocompletionUsers import scan, manage
|
||||
from extra.ocr import OCRSpace
|
||||
from .editTemplateController import EditTemplate
|
||||
|
||||
log = logging.getLogger("Settings")
|
||||
|
||||
@@ -132,234 +121,3 @@ class globalSettingsController(object):
|
||||
config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user")
|
||||
config.app["proxy"]["password"] = self.dialog.get_value("proxy", "password")
|
||||
config.app.write()
|
||||
|
||||
class accountSettingsController(globalSettingsController):
|
||||
def __init__(self, buffer, window):
|
||||
self.user = buffer.session.db["user_name"]
|
||||
self.buffer = buffer
|
||||
self.window = window
|
||||
self.config = buffer.session.settings
|
||||
super(accountSettingsController, self).__init__()
|
||||
|
||||
def create_config(self):
|
||||
self.dialog.create_general_account()
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
|
||||
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
|
||||
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
|
||||
self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"])
|
||||
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_tweets_per_call"])
|
||||
self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"])
|
||||
rt = self.config["general"]["retweet_mode"]
|
||||
if rt == "ask":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Ask"))
|
||||
elif rt == "direct":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet without comments"))
|
||||
else:
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments"))
|
||||
self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"]))
|
||||
self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"])
|
||||
self.dialog.create_reporting()
|
||||
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"])
|
||||
self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
|
||||
tweet_template = self.config["templates"]["tweet"]
|
||||
dm_template = self.config["templates"]["dm"]
|
||||
sent_dm_template = self.config["templates"]["dm_sent"]
|
||||
person_template = self.config["templates"]["person"]
|
||||
self.dialog.create_templates(tweet_template=tweet_template, dm_template=dm_template, sent_dm_template=sent_dm_template, person_template=person_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.tweet, widgetUtils.BUTTON_PRESSED, self.edit_tweet_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.dm, widgetUtils.BUTTON_PRESSED, self.edit_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.sent_dm, widgetUtils.BUTTON_PRESSED, self.edit_sent_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.person, widgetUtils.BUTTON_PRESSED, self.edit_person_template)
|
||||
self.dialog.create_other_buffers()
|
||||
buffer_values = self.get_buffers_list()
|
||||
self.dialog.buffers.insert_buffers(buffer_values)
|
||||
self.dialog.buffers.connect_hook_func(self.toggle_buffer_active)
|
||||
widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state)
|
||||
widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up)
|
||||
widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down)
|
||||
|
||||
self.dialog.create_ignored_clients(self.config["twitter"]["ignored_clients"])
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.add, widgetUtils.BUTTON_PRESSED, self.add_ignored_client)
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.remove, widgetUtils.BUTTON_PRESSED, self.remove_ignored_client)
|
||||
self.input_devices = sound_lib.input.Input.get_device_names()
|
||||
self.output_devices = sound_lib.output.Output.get_device_names()
|
||||
self.soundpacks = []
|
||||
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ]
|
||||
self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks)
|
||||
self.dialog.set_value("sound", "volumeCtrl", self.config["sound"]["volume"]*100)
|
||||
self.dialog.set_value("sound", "input", self.config["sound"]["input_device"])
|
||||
self.dialog.set_value("sound", "output", self.config["sound"]["output_device"])
|
||||
self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"])
|
||||
self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"])
|
||||
self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"])
|
||||
self.dialog.set_value("sound", "indicate_geo", self.config["sound"]["indicate_geo"])
|
||||
self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"])
|
||||
self.dialog.create_extras(OCRSpace.translatable_langs)
|
||||
self.dialog.set_value("extras", "sndup_apiKey", self.config["sound"]["sndup_api_key"])
|
||||
language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"])
|
||||
self.dialog.extras.ocr_lang.SetSelection(language_index)
|
||||
self.dialog.realize()
|
||||
self.dialog.set_title(_(u"Account settings for %s") % (self.user,))
|
||||
self.response = self.dialog.get_response()
|
||||
|
||||
def edit_tweet_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["tweet"]
|
||||
control = EditTemplate(template=template, type="tweet")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["tweet"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.tweet.SetLabel(_("Edit template for tweets. Current template: {}").format(result))
|
||||
|
||||
def edit_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.dm.SetLabel(_("Edit template for direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_sent_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm_sent"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm_sent"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.sent_dm.SetLabel(_("Edit template for sent direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_person_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["person"]
|
||||
control = EditTemplate(template=template, type="person")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["person"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.person.SetLabel(_("Edit template for persons. Current template: {}").format(result))
|
||||
|
||||
def save_configuration(self):
|
||||
if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in relative times.")
|
||||
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
|
||||
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
|
||||
self.config["general"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
|
||||
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
|
||||
if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
|
||||
self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in database strategy management.")
|
||||
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
|
||||
if self.dialog.get_value("general", "persist_size") == '':
|
||||
self.config["general"]["persist_size"] =-1
|
||||
else:
|
||||
try:
|
||||
self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size"))
|
||||
except ValueError:
|
||||
output.speak("Invalid cache size, setting to default.",True)
|
||||
self.config["general"]["persist_size"] =1764
|
||||
|
||||
if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in timeline order.")
|
||||
self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines")
|
||||
rt = self.dialog.get_value("general", "retweet_mode")
|
||||
if rt == _(u"Ask"):
|
||||
self.config["general"]["retweet_mode"] = "ask"
|
||||
elif rt == _(u"Retweet without comments"):
|
||||
self.config["general"]["retweet_mode"] = "direct"
|
||||
else:
|
||||
self.config["general"]["retweet_mode"] = "comment"
|
||||
buffers_list = self.dialog.buffers.get_list()
|
||||
if buffers_list != self.config["general"]["buffer_order"]:
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in buffer ordering.")
|
||||
self.config["general"]["buffer_order"] = buffers_list
|
||||
self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting")
|
||||
self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting")
|
||||
self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()]
|
||||
if self.config["sound"]["input_device"] != self.dialog.sound.get("input"):
|
||||
self.config["sound"]["input_device"] = self.dialog.sound.get("input")
|
||||
try:
|
||||
self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"]))
|
||||
except:
|
||||
self.config["sound"]["input_device"] = "default"
|
||||
if self.config["sound"]["output_device"] != self.dialog.sound.get("output"):
|
||||
self.config["sound"]["output_device"] = self.dialog.sound.get("output")
|
||||
try:
|
||||
self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"]))
|
||||
except:
|
||||
self.config["sound"]["output_device"] = "default"
|
||||
self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0
|
||||
self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute")
|
||||
self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack")
|
||||
self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio")
|
||||
self.config["sound"]["indicate_geo"] = self.dialog.get_value("sound", "indicate_geo")
|
||||
self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img")
|
||||
self.config["sound"]["sndup_api_key"] = self.dialog.get_value("extras", "sndup_apiKey")
|
||||
self.buffer.session.sound.config = self.config["sound"]
|
||||
self.buffer.session.sound.check_soundpack()
|
||||
self.config.write()
|
||||
|
||||
def toggle_state(self,*args,**kwargs):
|
||||
return self.dialog.buffers.change_selected_item()
|
||||
|
||||
def on_autocompletion_scan(self, *args, **kwargs):
|
||||
configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window)
|
||||
to_scan = configuration.show_dialog()
|
||||
if to_scan == True:
|
||||
configuration.prepare_progress_dialog()
|
||||
t = threading.Thread(target=configuration.scan)
|
||||
t.start()
|
||||
|
||||
|
||||
|
||||
def on_autocompletion_manage(self, *args, **kwargs):
|
||||
configuration = manage.autocompletionManage(self.buffer.session)
|
||||
configuration.show_settings()
|
||||
|
||||
def add_ignored_client(self, *args, **kwargs):
|
||||
client = commonMessageDialogs.get_ignored_client()
|
||||
if client == None: return
|
||||
if client not in self.config["twitter"]["ignored_clients"]:
|
||||
self.config["twitter"]["ignored_clients"].append(client)
|
||||
self.dialog.ignored_clients.append(client)
|
||||
|
||||
def remove_ignored_client(self, *args, **kwargs):
|
||||
if self.dialog.ignored_clients.get_clients() == 0: return
|
||||
id = self.dialog.ignored_clients.get_client_id()
|
||||
self.config["twitter"]["ignored_clients"].pop(id)
|
||||
self.dialog.ignored_clients.remove_(id)
|
||||
|
||||
def get_buffers_list(self):
|
||||
all_buffers=OrderedDict()
|
||||
all_buffers['home']=_(u"Home")
|
||||
all_buffers['mentions']=_(u"Mentions")
|
||||
all_buffers['dm']=_(u"Direct Messages")
|
||||
all_buffers['sent_dm']=_(u"Sent direct messages")
|
||||
all_buffers['sent_tweets']=_(u"Sent tweets")
|
||||
all_buffers['favorites']=_(u"Likes")
|
||||
all_buffers['followers']=_(u"Followers")
|
||||
all_buffers['friends']=_(u"Friends")
|
||||
all_buffers['blocks']=_(u"Blocked users")
|
||||
all_buffers['muted']=_(u"Muted users")
|
||||
list_buffers = []
|
||||
hidden_buffers=[]
|
||||
all_buffers_keys = list(all_buffers.keys())
|
||||
# Check buffers shown first.
|
||||
for i in self.config["general"]["buffer_order"]:
|
||||
if i in all_buffers_keys:
|
||||
list_buffers.append((i, all_buffers[i], True))
|
||||
# This second pass will retrieve all hidden buffers.
|
||||
for i in all_buffers_keys:
|
||||
if i not in self.config["general"]["buffer_order"]:
|
||||
hidden_buffers.append((i, all_buffers[i], False))
|
||||
list_buffers.extend(hidden_buffers)
|
||||
return list_buffers
|
||||
|
||||
def toggle_buffer_active(self, ev):
|
||||
change = self.dialog.buffers.get_event(ev)
|
||||
if change == True:
|
||||
self.dialog.buffers.change_selected_item()
|
||||
|
1
src/controller/twitter/__init__.py
Normal file
1
src/controller/twitter/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
377
src/controller/twitter/handler.py
Normal file
377
src/controller/twitter/handler.py
Normal file
@@ -0,0 +1,377 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import widgetUtils
|
||||
import output
|
||||
from pubsub import pub
|
||||
from tweepy.errors import TweepyException, Forbidden
|
||||
from mysc import restart
|
||||
from sessions.twitter import utils, compose
|
||||
from controller import userSelector
|
||||
from wxUI import dialogs, commonMessageDialogs
|
||||
from . import filters, lists, settings, userActions, trendingTopics, user
|
||||
|
||||
log = logging.getLogger("controller.twitter.handler")
|
||||
|
||||
class Handler(object):
|
||||
|
||||
def __init__(self):
|
||||
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):
|
||||
session.get_user_info()
|
||||
name = session.get_name()
|
||||
controller.accounts.append(name)
|
||||
if createAccounts == True:
|
||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
||||
root_position =controller.view.search(name, name)
|
||||
for i in session.settings['general']['buffer_order']:
|
||||
if i == 'home':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Home"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="home_timeline", name="home_timeline", sessionObject=session, account=session.get_name(), sound="tweet_received.ogg", include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'mentions':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Mentions"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="mentions_timeline", name="mentions", sessionObject=session, account=session.get_name(), sound="mention_received.ogg", include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'dm':
|
||||
pub.sendMessage("createBuffer", buffer_type="DirectMessagesBuffer", session_type=session.type, buffer_title=_("Direct messages"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_direct_messages", name="direct_messages", sessionObject=session, account=session.get_name(), bufferType="dmPanel", compose_func="compose_direct_message", sound="dm_received.ogg"))
|
||||
elif i == 'sent_dm':
|
||||
pub.sendMessage("createBuffer", buffer_type="SentDirectMessagesBuffer", session_type=session.type, buffer_title=_("Sent direct messages"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function=None, name="sent_direct_messages", sessionObject=session, account=session.get_name(), bufferType="dmPanel", compose_func="compose_direct_message"))
|
||||
elif i == 'sent_tweets':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Sent tweets"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="user_timeline", name="sent_tweets", sessionObject=session, account=session.get_name(), screen_name=session.db["user_name"], include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'favorites':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Likes"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_favorites", name="favourites", sessionObject=session, account=session.get_name(), sound="favourite.ogg", include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'followers':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Followers"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_followers", name="followers", sessionObject=session, account=session.get_name(), sound="update_followers.ogg", screen_name=session.db["user_name"]))
|
||||
elif i == 'friends':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Following"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_friends", name="friends", sessionObject=session, account=session.get_name(), screen_name=session.db["user_name"]))
|
||||
elif i == 'blocks':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Blocked users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_blocks", name="blocked", sessionObject=session, account=session.get_name()))
|
||||
elif i == 'muted':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Muted users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_mutes", name="muted", sessionObject=session, account=session.get_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)
|
||||
for i in session.settings["other_buffers"]["timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_(u"Timeline for {}").format(i,), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="user_timeline", name="%s-timeline" % (i,), sessionObject=session, account=session.get_name(), sound="tweet_timeline.ogg", bufferType=None, user_id=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Likes timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="favs_timelines", account=name))
|
||||
favs_timelines_position =controller.view.search("favs_timelines", name)
|
||||
for i in session.settings["other_buffers"]["favourites_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Likes for {}").format(i,), parent_tab=favs_timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_favorites", name="%s-favorite" % (i,), sessionObject=session, account=name, bufferType=None, sound="favourites_timeline_updated.ogg", user_id=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Followers timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="followers_timelines", account=session.get_name()))
|
||||
followers_timelines_position =controller.view.search("followers_timelines", name)
|
||||
for i in session.settings["other_buffers"]["followers_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Followers for {}").format(i,), parent_tab=followers_timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_followers", name="%s-followers" % (i,), sessionObject=session, account=session.get_name(), sound="new_event.ogg", user_id=i))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Following timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="friends_timelines", account=name))
|
||||
friends_timelines_position =controller.view.search("friends_timelines", name)
|
||||
for i in session.settings["other_buffers"]["friends_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_(u"Friends for {}").format(i,), parent_tab=friends_timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_friends", name="%s-friends" % (i,), sessionObject=session, account=session.get_name(), sound="new_event.ogg", user_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", account=name))
|
||||
lists_position =controller.view.search("lists", name)
|
||||
for i in session.settings["other_buffers"]["lists"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=session.type, buffer_title=_(u"List for {}").format(i), parent_tab=lists_position, start=False, kwargs=dict(parent=controller.view.nb, function="list_timeline", name="%s-list" % (i,), sessionObject=session, account=session.get_name(), bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Searches"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="searches", account=name))
|
||||
searches_position =controller.view.search("searches", name)
|
||||
for i in session.settings["other_buffers"]["tweet_searches"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_(u"Search for {}").format(i), parent_tab=searches_position, start=False, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (i,), sessionObject=session, account=session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", q=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
for i in session.settings["other_buffers"]["trending_topic_buffers"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (i), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (i,), sessionObject=session, account=session.get_name(), trendsFor=i, sound="trends_updated.ogg"))
|
||||
|
||||
def filter(self, buffer):
|
||||
# Let's prevent filtering of some buffers (people buffers, direct messages, events and sent items).
|
||||
if (buffer.name == "direct_messages" or buffer.name == "sent_tweets") or buffer.type == "people":
|
||||
output.speak(_("Filters cannot be applied on this buffer"))
|
||||
return
|
||||
new_filter = filters.filter(buffer)
|
||||
|
||||
def manage_filters(self, session):
|
||||
manage_filters = filters.filterManager(session)
|
||||
|
||||
def view_user_lists(self, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
selector = userSelector.userSelector(users=users, session_id=buffer.session.session_id)
|
||||
user = selector.get_user()
|
||||
if user == None:
|
||||
return
|
||||
l = lists.listsController(buffer.session, user=user)
|
||||
|
||||
def add_to_list(self, controller, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
selector = userSelector.userSelector(users=users, session_id=buffer.session.session_id)
|
||||
user = selector.get_user()
|
||||
if user == None:
|
||||
return
|
||||
dlg = dialogs.lists.addUserListDialog()
|
||||
dlg.populate_list([compose.compose_list(item) for item in buffer.session.db["lists"]])
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
try:
|
||||
list = buffer.session.twitter.add_list_member(list_id=buffer.session.db["lists"][dlg.get_item()].id, screen_name=user)
|
||||
older_list = utils.find_item(buffer.session.db["lists"][dlg.get_item()].id, buffer.session.db["lists"])
|
||||
listBuffer = controller.search_buffer("%s-list" % (buffer.session.db["lists"][dlg.get_item()].name.lower()), buff.session.get_name())
|
||||
if listBuffer != None:
|
||||
listBuffer.get_user_ids()
|
||||
buffer.session.db["lists"].pop(older_list)
|
||||
buffer.session.db["lists"].append(list)
|
||||
except TweepyException as e:
|
||||
log.exception("error %s" % (str(e)))
|
||||
output.speak("error %s" % (str(e)))
|
||||
|
||||
def remove_from_list(self, controller, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
selector = userSelector.userSelector(users=users, session_id=buffer.session.session_id)
|
||||
user = selector.get_user()
|
||||
if user == None:
|
||||
return
|
||||
dlg = dialogs.lists.removeUserListDialog()
|
||||
dlg.populate_list([compose.compose_list(item) for item in buffer.session.db["lists"]])
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
try:
|
||||
list = buffer.session.twitter.remove_list_member(list_id=buffer.session.db["lists"][dlg.get_item()].id, screen_name=user)
|
||||
older_list = utils.find_item(buffer.session.db["lists"][dlg.get_item()].id, buffer.session.db["lists"])
|
||||
listBuffer = controller.search_buffer("%s-list" % (buffer.session.db["lists"][dlg.get_item()].name.lower()), buffer.session.get_name())
|
||||
if listBuffer != None:
|
||||
listBuffer.get_user_ids()
|
||||
buffer.session.db["lists"].pop(older_list)
|
||||
buffer.session.db["lists"].append(list)
|
||||
except TweepyException as e:
|
||||
output.speak("error %s" % (str(e)))
|
||||
log.exception("error %s" % (str(e)))
|
||||
|
||||
def list_manager(self, session, lists_buffer_position):
|
||||
return lists.listsController(session=session, lists_buffer_position=lists_buffer_position)
|
||||
|
||||
def account_settings(self, buffer, controller):
|
||||
d = settings.accountSettingsController(buffer, controller)
|
||||
if d.response == widgetUtils.OK:
|
||||
d.save_configuration()
|
||||
if d.needs_restart == True:
|
||||
commonMessageDialogs.needs_restart()
|
||||
buffer.session.settings.write()
|
||||
buffer.session.save_persistent_data()
|
||||
restart.restart_program()
|
||||
|
||||
def follow(self, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
u = userActions.userActionsController(buffer, users)
|
||||
|
||||
def add_alias(self, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
dlg = dialogs.userAliasDialogs.addAliasDialog(_("Add an user alias"), users)
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
user, alias = dlg.get_user()
|
||||
if user == "" or alias == "":
|
||||
return
|
||||
user_id = buffer.session.get_user_by_screen_name(user)
|
||||
buffer.session.settings["user-aliases"][str(user_id)] = alias
|
||||
buffer.session.settings.write()
|
||||
output.speak(_("Alias has been set correctly for {}.").format(user))
|
||||
pub.sendMessage("alias-added")
|
||||
|
||||
# ToDo: explore how to play sound & save config differently.
|
||||
# currently, TWBlue will play the sound and save the config for the timeline even if the buffer did not load or something else.
|
||||
def open_timeline(self, controller, buffer, default="tweets"):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
dlg = dialogs.userSelection.selectUserDialog(users=users, default=default)
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
usr = utils.if_user_exists(buffer.session.twitter, dlg.get_user())
|
||||
if usr != None:
|
||||
if usr == dlg.get_user():
|
||||
commonMessageDialogs.suspended_user()
|
||||
return
|
||||
if usr.protected == True:
|
||||
if usr.following == False:
|
||||
commonMessageDialogs.no_following()
|
||||
return
|
||||
tl_type = dlg.get_action()
|
||||
if tl_type == "tweets":
|
||||
if usr.statuses_count == 0:
|
||||
commonMessageDialogs.no_tweets()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
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(usr.screen_name,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="user_timeline", name="%s-timeline" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", bufferType=None, user_id=usr.id_str, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
buffer.session.settings["other_buffers"]["timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif tl_type == "favourites":
|
||||
if usr.favourites_count == 0:
|
||||
commonMessageDialogs.no_favs()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["favourites_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
favs_timelines_position =controller.view.search("favs_timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Likes for {}").format(usr.screen_name,), parent_tab=favs_timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="get_favorites", name="%s-favorite" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), bufferType=None, sound="favourites_timeline_updated.ogg", user_id=usr.id_str, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
buffer.session.settings["other_buffers"]["favourites_timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif tl_type == "followers":
|
||||
if usr.followers_count == 0:
|
||||
commonMessageDialogs.no_followers()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["followers_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
followers_timelines_position =controller.view.search("followers_timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(usr.screen_name,), parent_tab=followers_timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="get_followers", name="%s-followers" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", user_id=usr.id_str))
|
||||
buffer.session.settings["other_buffers"]["followers_timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif tl_type == "friends":
|
||||
if usr.friends_count == 0:
|
||||
commonMessageDialogs.no_friends()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["friends_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
friends_timelines_position =controller.view.search("friends_timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=buffer.session.type, buffer_title=_("Friends for {}").format(usr.screen_name,), parent_tab=friends_timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="get_friends", name="%s-friends" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", user_id=usr.id_str))
|
||||
buffer.session.settings["other_buffers"]["friends_timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
else:
|
||||
commonMessageDialogs.user_not_exist()
|
||||
buffer.session.settings.write()
|
||||
|
||||
def open_conversation(self, controller, buffer):
|
||||
tweet = buffer.get_right_tweet()
|
||||
if hasattr(tweet, "retweeted_status") and tweet.retweeted_status != None:
|
||||
tweet = tweet.retweeted_status
|
||||
user = buffer.session.get_user(tweet.user).screen_name
|
||||
searches_position =controller.view.search("searches", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="ConversationBuffer", session_type=buffer.session.type, buffer_title=_(u"Conversation with {0}").format(user), parent_tab=searches_position, start=True, kwargs=dict(tweet=tweet, parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (tweet.id,), sessionObject=buffer.session, account=buffer.session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", since_id=tweet.id, q="@{0}".format(user)))
|
||||
|
||||
def get_trending_topics(self, controller, session):
|
||||
trends = trendingTopics.trendingTopicsController(session)
|
||||
if trends.dialog.get_response() == widgetUtils.OK:
|
||||
woeid = trends.get_woeid()
|
||||
if woeid in session.settings["other_buffers"]["trending_topic_buffers"]:
|
||||
return
|
||||
root_position =controller.view.search(session.get_name(), session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (trends.get_string()), parent_tab=root_position, start=True, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (woeid,), sessionObject=session, account=session.get_name(), trendsFor=woeid, sound="trends_updated.ogg"))
|
||||
session.settings["other_buffers"]["trending_topic_buffers"].append(str(woeid))
|
||||
session.settings.write()
|
||||
|
||||
def start_buffer(self, controller, buffer):
|
||||
if hasattr(buffer, "finished_timeline") and buffer.finished_timeline == False:
|
||||
change_title = True
|
||||
else:
|
||||
change_title = False
|
||||
try:
|
||||
if "mentions" in buffer.name or "direct_messages" in buffer.name:
|
||||
buffer.start_stream()
|
||||
else:
|
||||
buffer.start_stream(play_sound=False)
|
||||
except TweepyException as err:
|
||||
log.exception("Error %s starting buffer %s on account %s, with args %r and kwargs %r." % (str(err), buffer.name, buffer.account, buffer.args, buffer.kwargs))
|
||||
# Determine if this error was caused by a block applied to the current user (IE permission errors).
|
||||
if type(err) == Forbidden:
|
||||
buff = controller.view.search(buffer.name, buffer.account)
|
||||
buffer.remove_buffer(force=True)
|
||||
commonMessageDialogs.blocked_timeline()
|
||||
if controller.get_current_buffer() == buffer:
|
||||
controller.right()
|
||||
controller.view.delete_buffer(buff)
|
||||
controller.buffers.remove(buffer)
|
||||
del buffer
|
||||
if change_title:
|
||||
pub.sendMessage("buffer-title-changed", buffer=buffer)
|
||||
|
||||
def update_profile(self, session):
|
||||
r = user.profileController(session)
|
||||
|
||||
def search(self, controller, session, value):
|
||||
log.debug("Creating a new search...")
|
||||
dlg = dialogs.search.searchDialog(value)
|
||||
if dlg.get_response() == widgetUtils.OK and dlg.get("term") != "":
|
||||
term = dlg.get("term")
|
||||
searches_position =controller.view.search("searches", session.get_name())
|
||||
if dlg.get("tweets") == True:
|
||||
if term not in session.settings["other_buffers"]["tweet_searches"]:
|
||||
session.settings["other_buffers"]["tweet_searches"].append(term)
|
||||
session.settings.write()
|
||||
args = {"lang": dlg.get_language(), "result_type": dlg.get_result_type()}
|
||||
pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (term,), sessionObject=session, account=session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", q=term, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
else:
|
||||
log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,))
|
||||
return
|
||||
elif dlg.get("users") == True:
|
||||
pub.sendMessage("createBuffer", buffer_type="SearchPeopleBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, function="search_users", name="%s-searchUser" % (term,), sessionObject=session, account=session.get_name(), bufferType=None, sound="search_updated.ogg", q=term))
|
||||
dlg.Destroy()
|
@@ -10,9 +10,10 @@ from pubsub import pub
|
||||
log = logging.getLogger("controller.listsController")
|
||||
|
||||
class listsController(object):
|
||||
def __init__(self, session, user=None):
|
||||
def __init__(self, session, user=None, lists_buffer_position=0):
|
||||
super(listsController, self).__init__()
|
||||
self.session = session
|
||||
self.lists_buffer_position = lists_buffer_position
|
||||
if user == None:
|
||||
self.dialog = lists.listViewer()
|
||||
self.dialog.populate_list(self.get_all_lists())
|
||||
@@ -90,7 +91,9 @@ class listsController(object):
|
||||
def open_list_as_buffer(self, *args, **kwargs):
|
||||
if self.dialog.lista.get_count() == 0: return
|
||||
list = self.session.db["lists"][self.dialog.get_item()]
|
||||
pub.sendMessage("create-new-buffer", buffer="list", account=self.session.db["user_name"], create=list.name)
|
||||
pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=self.session.type, buffer_title=_("List for {}").format(list.name), parent_tab=self.lists_buffer_position, start=True, kwargs=dict(function="list_timeline", name="%s-list" % (list.name,), sessionObject=self.session, account=self.session.get_name(), bufferType=None, sound="list_tweet.ogg", list_id=list.id, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
self.session.settings["other_buffers"]["lists"].append(list.name)
|
||||
self.session.settings.write()
|
||||
|
||||
def subscribe(self, *args, **kwargs):
|
||||
if self.dialog.lista.get_count() == 0: return
|
@@ -8,7 +8,7 @@ import output
|
||||
import sound
|
||||
import config
|
||||
from pubsub import pub
|
||||
from twitter_text import parse_tweet
|
||||
from twitter_text.parse_tweet import parse_tweet
|
||||
from wxUI.dialogs import twitterDialogs, urlList
|
||||
from wxUI import commonMessageDialogs
|
||||
from extra import translator, SpellChecker
|
||||
@@ -23,7 +23,7 @@ class basicTweet(object):
|
||||
self.max = max
|
||||
self.title = title
|
||||
self.session = session
|
||||
self.message = getattr(twitterDialogs, messageType)(title=title, caption=caption, message=text, *args, **kwargs)
|
||||
self.message = getattr(twitterDialogs, messageType)(title=title, caption=caption, message=text, max_length=max, *args, **kwargs)
|
||||
self.message.text.SetValue(text)
|
||||
self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
|
||||
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
|
||||
@@ -185,11 +185,12 @@ class tweet(basicTweet):
|
||||
def text_processor(self, *args, **kwargs):
|
||||
super(tweet, self).text_processor(*args, **kwargs)
|
||||
if len(self.thread) > 0:
|
||||
self.message.tweets.Enable(True)
|
||||
self.message.remove_tweet.Enable(True)
|
||||
else:
|
||||
self.message.tweets.Enable(False)
|
||||
self.message.remove_tweet.Enable(False)
|
||||
if hasattr(self.message, "tweets"):
|
||||
self.message.tweets.Enable(True)
|
||||
self.message.remove_tweet.Enable(True)
|
||||
else:
|
||||
self.message.tweets.Enable(False)
|
||||
self.message.remove_tweet.Enable(False)
|
||||
if len(self.attachments) > 0:
|
||||
self.message.attachments.Enable(True)
|
||||
self.message.remove_attachment.Enable(True)
|
247
src/controller/twitter/settings.py
Normal file
247
src/controller/twitter/settings.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import threading
|
||||
import logging
|
||||
import sound_lib
|
||||
import paths
|
||||
import widgetUtils
|
||||
import output
|
||||
from collections import OrderedDict
|
||||
from wxUI import commonMessageDialogs
|
||||
from extra.autocompletionUsers import scan, manage
|
||||
from extra.ocr import OCRSpace
|
||||
from controller.settings import globalSettingsController
|
||||
from . templateEditor import EditTemplate
|
||||
|
||||
log = logging.getLogger("Settings")
|
||||
|
||||
class accountSettingsController(globalSettingsController):
|
||||
def __init__(self, buffer, window):
|
||||
self.user = buffer.session.db["user_name"]
|
||||
self.buffer = buffer
|
||||
self.window = window
|
||||
self.config = buffer.session.settings
|
||||
super(accountSettingsController, self).__init__()
|
||||
|
||||
def create_config(self):
|
||||
self.dialog.create_general_account()
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
|
||||
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
|
||||
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
|
||||
self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"])
|
||||
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_tweets_per_call"])
|
||||
self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"])
|
||||
rt = self.config["general"]["retweet_mode"]
|
||||
if rt == "ask":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Ask"))
|
||||
elif rt == "direct":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet without comments"))
|
||||
else:
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments"))
|
||||
self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"]))
|
||||
self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"])
|
||||
self.dialog.create_reporting()
|
||||
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"])
|
||||
self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
|
||||
tweet_template = self.config["templates"]["tweet"]
|
||||
dm_template = self.config["templates"]["dm"]
|
||||
sent_dm_template = self.config["templates"]["dm_sent"]
|
||||
person_template = self.config["templates"]["person"]
|
||||
self.dialog.create_templates(tweet_template=tweet_template, dm_template=dm_template, sent_dm_template=sent_dm_template, person_template=person_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.tweet, widgetUtils.BUTTON_PRESSED, self.edit_tweet_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.dm, widgetUtils.BUTTON_PRESSED, self.edit_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.sent_dm, widgetUtils.BUTTON_PRESSED, self.edit_sent_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.person, widgetUtils.BUTTON_PRESSED, self.edit_person_template)
|
||||
self.dialog.create_other_buffers()
|
||||
buffer_values = self.get_buffers_list()
|
||||
self.dialog.buffers.insert_buffers(buffer_values)
|
||||
self.dialog.buffers.connect_hook_func(self.toggle_buffer_active)
|
||||
widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state)
|
||||
widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up)
|
||||
widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down)
|
||||
|
||||
self.dialog.create_ignored_clients(self.config["twitter"]["ignored_clients"])
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.add, widgetUtils.BUTTON_PRESSED, self.add_ignored_client)
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.remove, widgetUtils.BUTTON_PRESSED, self.remove_ignored_client)
|
||||
self.input_devices = sound_lib.input.Input.get_device_names()
|
||||
self.output_devices = sound_lib.output.Output.get_device_names()
|
||||
self.soundpacks = []
|
||||
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ]
|
||||
self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks)
|
||||
self.dialog.set_value("sound", "volumeCtrl", int(self.config["sound"]["volume"]*100))
|
||||
self.dialog.set_value("sound", "input", self.config["sound"]["input_device"])
|
||||
self.dialog.set_value("sound", "output", self.config["sound"]["output_device"])
|
||||
self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"])
|
||||
self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"])
|
||||
self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"])
|
||||
self.dialog.set_value("sound", "indicate_geo", self.config["sound"]["indicate_geo"])
|
||||
self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"])
|
||||
self.dialog.create_extras(OCRSpace.translatable_langs)
|
||||
self.dialog.set_value("extras", "sndup_apiKey", self.config["sound"]["sndup_api_key"])
|
||||
language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"])
|
||||
self.dialog.extras.ocr_lang.SetSelection(language_index)
|
||||
self.dialog.realize()
|
||||
self.dialog.set_title(_(u"Account settings for %s") % (self.user,))
|
||||
self.response = self.dialog.get_response()
|
||||
|
||||
def edit_tweet_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["tweet"]
|
||||
control = EditTemplate(template=template, type="tweet")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["tweet"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.tweet.SetLabel(_("Edit template for tweets. Current template: {}").format(result))
|
||||
|
||||
def edit_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.dm.SetLabel(_("Edit template for direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_sent_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm_sent"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm_sent"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.sent_dm.SetLabel(_("Edit template for sent direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_person_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["person"]
|
||||
control = EditTemplate(template=template, type="person")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["person"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.person.SetLabel(_("Edit template for persons. Current template: {}").format(result))
|
||||
|
||||
def save_configuration(self):
|
||||
if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in relative times.")
|
||||
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
|
||||
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
|
||||
self.config["general"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
|
||||
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
|
||||
if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
|
||||
self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in database strategy management.")
|
||||
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
|
||||
if self.dialog.get_value("general", "persist_size") == '':
|
||||
self.config["general"]["persist_size"] =-1
|
||||
else:
|
||||
try:
|
||||
self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size"))
|
||||
except ValueError:
|
||||
output.speak("Invalid cache size, setting to default.",True)
|
||||
self.config["general"]["persist_size"] =1764
|
||||
|
||||
if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in timeline order.")
|
||||
self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines")
|
||||
rt = self.dialog.get_value("general", "retweet_mode")
|
||||
if rt == _(u"Ask"):
|
||||
self.config["general"]["retweet_mode"] = "ask"
|
||||
elif rt == _(u"Retweet without comments"):
|
||||
self.config["general"]["retweet_mode"] = "direct"
|
||||
else:
|
||||
self.config["general"]["retweet_mode"] = "comment"
|
||||
buffers_list = self.dialog.buffers.get_list()
|
||||
if buffers_list != self.config["general"]["buffer_order"]:
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in buffer ordering.")
|
||||
self.config["general"]["buffer_order"] = buffers_list
|
||||
self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting")
|
||||
self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting")
|
||||
self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()]
|
||||
if self.config["sound"]["input_device"] != self.dialog.sound.get("input"):
|
||||
self.config["sound"]["input_device"] = self.dialog.sound.get("input")
|
||||
try:
|
||||
self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"]))
|
||||
except:
|
||||
self.config["sound"]["input_device"] = "default"
|
||||
if self.config["sound"]["output_device"] != self.dialog.sound.get("output"):
|
||||
self.config["sound"]["output_device"] = self.dialog.sound.get("output")
|
||||
try:
|
||||
self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"]))
|
||||
except:
|
||||
self.config["sound"]["output_device"] = "default"
|
||||
self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0
|
||||
self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute")
|
||||
self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack")
|
||||
self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio")
|
||||
self.config["sound"]["indicate_geo"] = self.dialog.get_value("sound", "indicate_geo")
|
||||
self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img")
|
||||
self.config["sound"]["sndup_api_key"] = self.dialog.get_value("extras", "sndup_apiKey")
|
||||
self.buffer.session.sound.config = self.config["sound"]
|
||||
self.buffer.session.sound.check_soundpack()
|
||||
self.config.write()
|
||||
|
||||
def toggle_state(self,*args,**kwargs):
|
||||
return self.dialog.buffers.change_selected_item()
|
||||
|
||||
def on_autocompletion_scan(self, *args, **kwargs):
|
||||
configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window)
|
||||
to_scan = configuration.show_dialog()
|
||||
if to_scan == True:
|
||||
configuration.prepare_progress_dialog()
|
||||
t = threading.Thread(target=configuration.scan)
|
||||
t.start()
|
||||
|
||||
|
||||
|
||||
def on_autocompletion_manage(self, *args, **kwargs):
|
||||
configuration = manage.autocompletionManage(self.buffer.session)
|
||||
configuration.show_settings()
|
||||
|
||||
def add_ignored_client(self, *args, **kwargs):
|
||||
client = commonMessageDialogs.get_ignored_client()
|
||||
if client == None: return
|
||||
if client not in self.config["twitter"]["ignored_clients"]:
|
||||
self.config["twitter"]["ignored_clients"].append(client)
|
||||
self.dialog.ignored_clients.append(client)
|
||||
|
||||
def remove_ignored_client(self, *args, **kwargs):
|
||||
if self.dialog.ignored_clients.get_clients() == 0: return
|
||||
id = self.dialog.ignored_clients.get_client_id()
|
||||
self.config["twitter"]["ignored_clients"].pop(id)
|
||||
self.dialog.ignored_clients.remove_(id)
|
||||
|
||||
def get_buffers_list(self):
|
||||
all_buffers=OrderedDict()
|
||||
all_buffers['home']=_(u"Home")
|
||||
all_buffers['mentions']=_(u"Mentions")
|
||||
all_buffers['dm']=_(u"Direct Messages")
|
||||
all_buffers['sent_dm']=_(u"Sent direct messages")
|
||||
all_buffers['sent_tweets']=_(u"Sent tweets")
|
||||
all_buffers['favorites']=_(u"Likes")
|
||||
all_buffers['followers']=_(u"Followers")
|
||||
all_buffers['friends']=_(u"Friends")
|
||||
all_buffers['blocks']=_(u"Blocked users")
|
||||
all_buffers['muted']=_(u"Muted users")
|
||||
list_buffers = []
|
||||
hidden_buffers=[]
|
||||
all_buffers_keys = list(all_buffers.keys())
|
||||
# Check buffers shown first.
|
||||
for i in self.config["general"]["buffer_order"]:
|
||||
if i in all_buffers_keys:
|
||||
list_buffers.append((i, all_buffers[i], True))
|
||||
# This second pass will retrieve all hidden buffers.
|
||||
for i in all_buffers_keys:
|
||||
if i not in self.config["general"]["buffer_order"]:
|
||||
hidden_buffers.append((i, all_buffers[i], False))
|
||||
list_buffers.extend(hidden_buffers)
|
||||
return list_buffers
|
||||
|
||||
def toggle_buffer_active(self, ev):
|
||||
change = self.dialog.buffers.get_event(ev)
|
||||
if change == True:
|
||||
self.dialog.buffers.change_selected_item()
|
@@ -29,42 +29,42 @@ class userActionsController(object):
|
||||
def follow(self, user):
|
||||
try:
|
||||
self.session.twitter.create_friendship(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unfollow(self, user):
|
||||
try:
|
||||
id = self.session.twitter.destroy_friendship(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def mute(self, user):
|
||||
try:
|
||||
id = self.session.twitter.create_mute(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unmute(self, user):
|
||||
try:
|
||||
id = self.session.twitter.destroy_mute(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def report(self, user):
|
||||
try:
|
||||
id = self.session.twitter.report_spam(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def block(self, user):
|
||||
try:
|
||||
id = self.session.twitter.create_block(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
@@ -1,3 +1 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from .soundsTutorial import soundsTutorial
|
||||
|
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from gi.repository import Gtk
|
||||
import widgetUtils
|
||||
|
||||
class soundsTutorialDialog(Gtk.Dialog):
|
||||
def __init__(self, actions):
|
||||
super(soundsTutorialDialog, self).__init__("Sounds tutorial", None, 0, (Gtk.STOCK_CANCEL, widgetUtils.CANCEL))
|
||||
box = self.get_content_area()
|
||||
label = Gtk.Label("Press enter for listen the sound")
|
||||
self.list = widgetUtils.list("Action")
|
||||
self.populate_actions(actions)
|
||||
lBox = Gtk.Box(spacing=6)
|
||||
lBox.add(label)
|
||||
lBox.add(self.list.list)
|
||||
box.add(lBox)
|
||||
self.play = Gtk.Button("Play")
|
||||
box.add(self.play)
|
||||
self.show_all()
|
||||
|
||||
def populate_actions(self, actions):
|
||||
for i in actions:
|
||||
self.list.insert_item(i)
|
||||
|
||||
def get_selected(self):
|
||||
return self.list.get_selected()
|
@@ -1,4 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
#Reverse sort, by Bill Dengler <codeofdusk@gmail.com> for use in TWBlue http://twblue.es
|
||||
def invert_tuples(t):
|
||||
"Invert a list of tuples, so that the 0th element becomes the -1th, and the -1th becomes the 0th."
|
||||
|
@@ -1,7 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from builtins import object
|
||||
import platform
|
||||
import widgetUtils
|
||||
import os
|
||||
@@ -9,10 +6,7 @@ import paths
|
||||
import logging
|
||||
log = logging.getLogger("extra.SoundsTutorial.soundsTutorial")
|
||||
from . import soundsTutorial_constants
|
||||
if platform.system() == "Windows":
|
||||
from . import wx_ui as UI
|
||||
elif platform.system() == "Linux":
|
||||
from . import gtk_ui as UI
|
||||
from . import wx_ui as UI
|
||||
|
||||
class soundsTutorial(object):
|
||||
def __init__(self, sessionObject):
|
||||
|
@@ -1,7 +1,4 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
#-*- coding: utf-8 -*-
|
||||
from . import reverse_sort
|
||||
import application
|
||||
actions = reverse_sort.reverse_sort([ ("audio", _(u"Audio tweet.")),
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import wx
|
||||
import widgetUtils
|
||||
|
||||
|
@@ -1,8 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from builtins import next
|
||||
from builtins import object
|
||||
import os
|
||||
import logging
|
||||
from . import wx_ui
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
from enchant.tokenize import Filter
|
||||
|
||||
|
@@ -16,7 +16,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
############################################################
|
||||
from __future__ import unicode_literals
|
||||
import wx
|
||||
import application
|
||||
|
||||
|
@@ -2,7 +2,4 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from . import translator
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
from . import wx_ui as gui
|
||||
|
||||
from . import wx_ui as gui
|
@@ -4,15 +4,15 @@ from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
from . import fix_arrow # A few new locales for Three languages in arrow.
|
||||
from . import fix_libloader # Regenerates comcache properly.
|
||||
#from . import fix_libloader # Regenerates comcache properly.
|
||||
from . import fix_urllib3_warnings # Avoiding some SSL warnings related to Twython.
|
||||
from . import fix_win32com
|
||||
#from . import fix_win32com
|
||||
#from . import fix_requests #fix cacert.pem location for TWBlue binary copies
|
||||
def setup():
|
||||
fix_arrow.fix()
|
||||
if hasattr(sys, "frozen"):
|
||||
fix_libloader.fix()
|
||||
fix_win32com.fix()
|
||||
# if hasattr(sys, "frozen"):
|
||||
# fix_libloader.fix()
|
||||
# fix_win32com.fix()
|
||||
# fix_requests.fix()
|
||||
# else:
|
||||
# fix_requests.fix(False)
|
||||
|
@@ -21,7 +21,7 @@ def parse(s):
|
||||
lst.remove(item)
|
||||
#end if
|
||||
if len(lst) > 1: #more than one key, parse error
|
||||
raise ValueError, 'unknown modifier %s' % lst[0]
|
||||
raise ValueError('unknown modifier %s' % lst[0])
|
||||
return (m, lst[0].lower())
|
||||
class AtspiThread(threading.Thread):
|
||||
def run(self):
|
||||
|
@@ -1,3 +1 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
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"),
|
||||
"list_manager": _(u"Opens the list manager"),
|
||||
"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."),
|
||||
"ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."),
|
||||
"add_alias": _("Adds an alias to an user"),
|
@@ -2,18 +2,19 @@
|
||||
import widgetUtils
|
||||
import config
|
||||
from . import wx_ui
|
||||
from . import constants
|
||||
from . import actions
|
||||
from pubsub import pub
|
||||
|
||||
class KeystrokeEditor(object):
|
||||
def __init__(self):
|
||||
def __init__(self, session_type="twitter"):
|
||||
super(KeystrokeEditor, self).__init__()
|
||||
self.actions = getattr(actions, session_type).actions
|
||||
self.changed = False # Change it if the keyboard shorcuts are reassigned.
|
||||
self.dialog = wx_ui.keystrokeEditorDialog()
|
||||
self.map = config.keymap["keymap"]
|
||||
# we need to copy the keymap before modify it, for unregistering the old keystrokes if is needed.
|
||||
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.undefine, widgetUtils.BUTTON_PRESSED, self.undefine_keystroke)
|
||||
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]:
|
||||
self.changed = True
|
||||
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):
|
||||
action = self.dialog.actions[self.dialog.get_action()]
|
||||
@@ -40,7 +41,7 @@ class KeystrokeEditor(object):
|
||||
if answer == widgetUtils.YES:
|
||||
self.map[action] = ""
|
||||
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):
|
||||
for i in keystroke.split("+"):
|
||||
|
@@ -6,27 +6,26 @@ from wxUI.dialogs import baseDialog
|
||||
class keystrokeEditorDialog(baseDialog.BaseWXDialog):
|
||||
def __init__(self):
|
||||
super(keystrokeEditorDialog, self).__init__(parent=None, id=-1, title=_(u"Keystroke editor"))
|
||||
panel = wx.Panel(self)
|
||||
self.actions = []
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
keysText = wx.StaticText(panel, -1, _(u"Select a keystroke to edit"))
|
||||
keysText = wx.StaticText(self, -1, _(u"Select a keystroke to edit"))
|
||||
self.keys = widgets.list(self, _(u"Action"), _(u"Keystroke"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL, size=(400, 450))
|
||||
self.keys.list.SetFocus()
|
||||
firstSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
firstSizer.Add(keysText, 0, wx.ALL, 5)
|
||||
firstSizer.Add(self.keys.list, 0, wx.ALL, 5)
|
||||
self.edit = wx.Button(panel, -1, _(u"Edit"))
|
||||
self.edit = wx.Button(self, -1, _(u"Edit"))
|
||||
self.edit.SetDefault()
|
||||
self.undefine = wx.Button(panel, -1, _("Undefine keystroke"))
|
||||
self.execute = wx.Button(panel, -1, _(u"Execute action"))
|
||||
close = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||
self.undefine = wx.Button(self, -1, _("Undefine keystroke"))
|
||||
self.execute = wx.Button(self, -1, _(u"Execute action"))
|
||||
close = wx.Button(self, wx.ID_CANCEL, _(u"Close"))
|
||||
secondSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
secondSizer.Add(self.edit, 0, wx.ALL, 5)
|
||||
secondSizer.Add(self.execute, 0, wx.ALL, 5)
|
||||
secondSizer.Add(close, 0, wx.ALL, 5)
|
||||
sizer.Add(firstSizer, 0, wx.ALL, 5)
|
||||
sizer.Add(secondSizer, 0, wx.ALL, 5)
|
||||
panel.SetSizer(sizer)
|
||||
self.SetSizer(sizer)
|
||||
self.SetClientSize(sizer.CalcMin())
|
||||
|
||||
def put_keystrokes(self, actions, keystrokes):
|
||||
|
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user