Compare commits

...

301 Commits

Author SHA1 Message Date
5eb942981c Added some changes to setup file for the CI config 2021-10-29 10:58:30 -05:00
6739045cce Fixed manual addition of users to the autocomplete users database 2021-10-28 13:18:02 -05:00
307ed093af Merge pull request #414 from manuelcortez/improved_conversations
Improve conversation Support
2021-10-28 12:45:32 -05:00
d11fc44772 Read keys from environment 2021-10-28 12:44:50 -05:00
a3e5eec6de Merge branch 'next-gen' into improved_conversations 2021-10-28 12:39:55 -05:00
41a0935121 Merge branch 'next-gen' of github.com:manuelcortez/twblue into next-gen 2021-10-28 12:39:09 -05:00
0b03e7505f Conversation support improved via V2 searches 2021-10-28 12:28:19 -05:00
José Manuel Delicado
81c364c4e1 Merge pull request #412 from Oreonan/fr_281021-2
Small french fixes
2021-10-28 18:45:44 +02:00
Oreonan
483b196203 Fix small error 2021-10-28 18:30:05 +02:00
Oreonan
b512c69447 Remove old translations 2021-10-28 18:28:34 +02:00
2c1608322e Merge pull request #410 from Oreonan/fr_281021
Update french interface
2021-10-28 10:14:49 -05:00
c6bb851bce Merge pull request #411 from nidza07/SerbianTranslation
Updated Serbian translation
2021-10-28 10:14:25 -05:00
Nikola Jovic
66581f8b1c Clarify a string better 2021-10-28 17:03:52 +02:00
Nikola Jovic
c6a3a44c21 Updated Serbian translation 2021-10-28 16:58:11 +02:00
bd25cfa59b Merge branch 'next-gen' of github.com:manuelcortez/twblue into next-gen 2021-10-28 08:38:05 -05:00
Oreonan
39a02ea33a Update french interface 2021-10-28 12:33:52 +02:00
e23a52e38f Changes in TWBlue keys 2021-10-27 15:29:15 -05:00
José Manuel Delicado Alcolea
d888563fda Updated Api keys 2021-10-27 22:00:58 +02:00
a5ba80feee Final touches 2021-10-26 16:18:53 -05:00
528ecc2a33 Updated NSI file 2021-10-26 16:16:03 -05:00
3519746078 Move installer Nsis file to a new name 2021-10-26 15:47:03 -05:00
ef79e0696e Move updated Nsis script to artifacts in build stage 2021-10-26 15:19:57 -05:00
b9ee0dae5b Fixed a typo in installer script 2021-10-26 14:48:58 -05:00
f31575a733 Merge pull request #408 from manuelcortez/calendar_versioning
Switch to Calendar versioning
2021-10-26 13:52:21 -05:00
e451bbd5e9 Merge pull request #407 from manuelcortez/alias_manager
Alias manager Dialog. Closes #401
2021-10-26 13:51:09 -05:00
f7f303929e Updated CI config 2021-10-26 13:41:22 -05:00
9f48784ce4 Added script to add next version in application.py and installer generation files 2021-10-26 13:39:50 -05:00
cb1312d0c9 Changed version in installer so it will be easier to replace it later by the authomatic script 2021-10-26 13:38:21 -05:00
a82efd4dcc Fixed a typo 2021-10-26 13:16:31 -05:00
72e6d030d5 Updater no longer will try to compare float numbers for updates 2021-10-26 13:10:57 -05:00
d222740887 Updated gitlab CI 2021-10-26 13:04:49 -05:00
2b059ee42e Modified Nsis scripts to remove references to old snapshots code 2021-10-26 13:02:24 -05:00
daac312658 Removes snapshot information from app info 2021-10-26 12:43:03 -05:00
b23be9c896 Started to remove stable and snapshot update information files 2021-10-26 12:40:57 -05:00
61b0dc34b8 Merge branch 'next-gen' into alias_manager 2021-10-26 10:48:43 -05:00
c5d13369eb Merge branch 'next-gen' of github.com:manuelcortez/twblue into next-gen 2021-10-26 10:47:55 -05:00
856ecf5eb9 Merge branch 'next-gen' of github.com:manuelcortez/twblue into alias_manager 2021-10-26 10:46:30 -05:00
e3e0ac9457 Added an alias manager dialog in the application menu 2021-10-26 10:45:11 -05:00
34c1f69ec1 Merge pull request #404 from nidza07/SerbianTranslation
Updated Serbian translation
2021-10-22 12:57:58 -05:00
7326ff88f9 Merge pull request #405 from Oreonan/fr_211021
Update french interface
2021-10-22 12:56:55 -05:00
Oreonan
a8d876a7b7 Fix last-translator e-mail 2021-10-21 17:02:03 +02:00
Oreonan
89fa6435b4 Update french interface 2021-10-21 16:58:32 +02:00
Nikola Jovic
d1bd393be2 Updated Serbian translation 2021-10-21 16:30:46 +02:00
301bd5fd39 Pushed a new snapshot 2021-10-20 15:52:58 -05:00
José Manuel Delicado Alcolea
a2f25bfbb5 Added charset-normalizer to requirements 2021-10-12 17:05:09 +02:00
286e030f40 Handle new Tweepy exceptions properly. #403 2021-10-07 09:20:06 -05:00
d8fca3b31a Initial work to Support Tweepy 4 2021-09-26 03:58:25 -05:00
0c27427843 Merge branch 'next-gen' of github.com:manuelcortez/twblue into next-gen 2021-09-10 15:05:28 -05:00
dfdbe3c5f4 Close VLC window after video playback ends. Close #399 2021-09-10 15:05:05 -05:00
José Manuel Delicado
2222a97451 Merge pull request #394 from Oreonan/fr_020821
Update french interface translation
2021-09-03 11:38:50 +02:00
fbe93ea4be Made FreakyBlue The default soundpack for new twitter sessions 2021-09-02 09:59:13 -05:00
4bcae1aa97 Fixed search filters. Closes #397 2021-09-02 09:38:05 -05:00
4cabf5b9cd Added code to handle user timelines in buffer creator 2021-08-31 10:31:18 -05:00
a9a4189295 Added improvements to tweet condense feature 2021-08-31 10:05:21 -05:00
c7b6d69518 Fixed an error in new buffer creation. Closes #396 2021-08-30 13:19:15 -05:00
65512a9862 Added a pubsub event to create sessions buffers 2021-08-30 10:51:26 -05:00
Oreonan
17ea8af050 Update translation 2021-08-28 08:37:26 +02:00
Oreonan
43578a32eb Merge remote-tracking branch 'origin/next-gen' into fr_020821 2021-08-28 08:33:40 +02:00
7c34204d17 Allow to specify alternative configuration specs for sessions 2021-08-27 13:45:59 -05:00
3a5c1c10d3 Released a new snapshot 2021-08-26 15:13:34 -05:00
f9864a887d Fixed a small traceback that was happening when translating a direct message 2021-08-26 09:16:02 -05:00
3d8519313e Switched Geocoding library to OpenStreetMap's Nominatim API. Closes #390 2021-08-26 08:56:51 -05:00
f4ecf10885 Allow to copy tweet URLS from tweet displayer dialog 2021-08-25 16:30:37 -05:00
c67b415934 Updated changelog 2021-08-25 12:49:53 -05:00
10511d3022 Improvements to reading tweets with many mentions on them 2021-08-25 11:13:12 -05:00
Oreonan
ddc80a29fd Update french interface translation 2021-08-02 19:30:24 +02:00
9ea36a26d2 Updated changelog with windows 11 keymap 2021-07-31 22:33:32 -05:00
97286496fc fixed changelog in diverged commits 2021-07-31 22:28:46 -05:00
José Manuel Delicado
6436af76f5 Merge pull request #393 from jpavonabian/windows11
Added a fix.
2021-07-28 07:50:38 +02:00
Jesus
576b5064c0 Added a fix. 2021-07-28 07:23:57 +02:00
José Manuel Delicado
342265b3c0 Merge pull request #392 from jpavonabian/windows11
Fixed shortkuts
2021-07-26 13:38:32 +02:00
Jesus
54938ecb6c Fixed shortkuts 2021-07-26 12:47:51 +02:00
José Manuel Delicado
7aff8252d2 Merge pull request #391 from jpavonabian/windows11
Keypad for windows 11
2021-07-24 12:46:43 +02:00
Jesus
9c680130f7 Keypad para windows 11 2021-07-24 06:24:08 +02:00
24d1ad093d Streaming API: Ignore retweets if original tweet is present in a buffer 2021-07-16 10:22:51 -05:00
b2b9cd810f Fixed an issue when indefined keystrokes 2021-07-13 17:53:01 -05:00
582be54dea Added add_alias action to keymaps as undefined 2021-07-13 17:25:48 -05:00
ff0fbeafa3 Modified keystrokeEditor to allow undefined keystrokes 2021-07-13 17:22:20 -05:00
e314cf0599 Allow search users by names in autocomplete users 2021-07-10 05:24:56 -05:00
José Manuel Delicado Alcolea
e7b72112cf Merge branch 'next-gen' of github.com:manuelcortez/TWBlue into next-gen 2021-07-07 08:42:14 +02:00
José Manuel Delicado Alcolea
70c095febe setup.py: patch cx_freeze to include our Microsoft Visual C++ runtime files 2021-07-07 08:41:08 +02:00
6119b029f8 Merge pull request #388 from zstanecic/next-gen
Updated russian interface
2021-07-06 17:13:59 -05:00
b74cd9a73d Ignore undefined actions in keymaps 2021-07-06 17:01:42 -05:00
8ff6809f08 Updated changelog 2021-07-06 16:22:52 -05:00
39af9d8623 Merge pull request #389 from manuelcortez/user_alias
User aliases within TWBlue
2021-07-06 15:58:54 -05:00
3688d7548c Check alias before returning any user object 2021-07-06 13:59:34 -05:00
07f9afb14e Added option to user menu in the menu bar 2021-07-06 13:58:34 -05:00
de12dadac2 Added GUI for user alias addition 2021-07-06 13:58:13 -05:00
877c909482 Added user-aliases section on session configs 2021-07-06 13:56:56 -05:00
José Manuel Delicado Alcolea
1206aba83b Added a few more dependencies to requirements.txt 2021-07-06 20:42:03 +02:00
zstanecic
fcd631b2de Merge remote-tracking branch 'upstream/next-gen' into next-gen 2021-07-06 19:16:47 +02:00
Jose Manuel Delicado
86130954d7 Updated Windows dependencies. Latest versions of NSIS and VLC, among others 2021-07-06 12:39:47 +02:00
Jose Manuel Delicado
23a56c637d Add some indirect dependencies to requirements.txt in order to easily keep them updated 2021-07-05 23:03:40 +02:00
zstanecic
d5ac0db67b updated the russian localization. 2021-07-05 16:04:19 +02:00
bb4869b7be Pushed a new snapshot 2021-07-04 17:05:06 -05:00
44b6e82183 Fix reply dialog issue 2021-07-04 11:50:03 -05:00
Jose Manuel Delicado
5268f166f8 Use Python 3.8.10 for automated builds 2021-07-04 18:43:33 +02:00
Jose Manuel Delicado
37ad6b5fbf paths.py: replace socializer by TW Blue in data_path function definition 2021-07-04 18:32:17 +02:00
Jose Manuel Delicado
bcc72c932d Updated Windows dependencies. Python 3.8.10 2021-07-04 18:26:49 +02:00
b9a9bd03c2 Pushed a new snapshot 2021-07-04 09:47:33 -05:00
e6543bcf77 Allow streaming API support to be disabled from global settings dialog 2021-07-04 09:44:48 -05:00
03b61946f8 Fixed user searches 2021-07-04 09:43:53 -05:00
8fe2f4c64d Exclude muted users from Streaming API 2021-07-04 09:15:04 -05:00
37af722556 Fixed alpha version for snapshots 2021-07-04 06:15:40 -05:00
4312ad82e7 Fixed runner config 2021-07-04 05:51:59 -05:00
e9e8a8fba9 Pushed a new snapshot 2021-07-04 05:43:06 -05:00
5cad4ab2a7 Restore get_all_users and get_all_mentionned in nested tweets 2021-07-04 05:31:27 -05:00
01dd93e076 Show all current account replies in streaming API 2021-07-04 05:30:52 -05:00
d301f841e3 TWBlue should stop showing sent retweets as original tweets in Streaming API 2021-07-04 05:02:02 -05:00
81d18d4656 Updated changelog 2021-07-04 04:47:13 -05:00
ccba22cfd2 Show dialog on suspended users again. closes #387 2021-07-03 14:09:52 -05:00
465b550c30 Removed old code to deal with invalid users 2021-07-03 14:05:02 -05:00
788811bf6c Ensure a tweet is reduced when sent by streaming API 2021-07-03 14:04:14 -05:00
c926355048 Hide replies to users the current account doesn't follow in streaming API 2021-07-03 11:52:21 -05:00
84cbf5c497 Fix yet another error due to reduced tweets 2021-07-03 11:51:43 -05:00
7eb2d8930f Fixed traceback happened when attempting to send streaming data to not logged sessions 2021-07-03 11:51:11 -05:00
864ebdf96d Made widgetUtils available to searchBuffers 2021-07-02 17:57:39 -05:00
ee9a92bcb4 Makes time available on conversation buffers 2021-07-02 17:53:05 -05:00
818bc243e4 Handle Twyshort's detection for reduced tweets 2021-07-02 17:34:40 -05:00
062289a977 Merge pull request #385 from manuelcortez/streaming
Streaming API support
2021-07-02 17:29:10 -05:00
56a1c57e04 Merge pull request #386 from manuelcortez/buffer_modularization
Separate all buffers in modules for an easier work with code
2021-07-02 17:27:48 -05:00
3c7063792c Separate all buffers in modules for an easier work with code 2021-07-02 17:22:24 -05:00
77eadb42bb Make sure to disconnect the streams as tweepy implements it 2021-07-02 10:35:20 -05:00
9053fcd5de Send Tweets to mentions properly 2021-07-02 10:11:50 -05:00
5f11467f27 Switched threads to our own facilities and attempts to improve thread management for streaming endpoints 2021-07-02 09:52:21 -05:00
55b1c7bdae Integrates Tweepy's 68e19cc for preventing Urllib3 ProtocolError 2021-07-02 09:50:22 -05:00
ba90842185 Initial work to put tweets in mentions, sent and timelines 2021-06-29 17:55:36 -05:00
8fd3041efd Added some reconnection code and logging 2021-06-29 17:16:53 -05:00
bb5ead80de Parse correctly incoming tweets from Streaming API 2021-06-29 05:05:20 -05:00
168c7e7a5d Initial test for supporting a subset of the Streaming API 2021-06-28 17:03:26 -05:00
a7838bbf7d Next snapshots will have installer and 64 bits version. Switched to Gitlab CI to generate snapshots. 2021-06-28 10:23:39 -05:00
fe8b58a7b9 Include quoted tweets and retweets in audio detection and playback 2021-06-28 10:20:39 -05:00
a9f52b3a94 Added convert_bytes function to show correctly FTP upload progress 2021-06-28 06:01:05 -05:00
13c47f7b9f Fixed branch name 2021-06-28 05:28:15 -05:00
3515df9b15 Upload files to FTP server after generating snapshots 2021-06-28 05:26:42 -05:00
f998fa62a6 Added missed move command 2021-06-28 04:50:36 -05:00
a6032cae46 Added make_archive as a script 2021-06-28 04:30:42 -05:00
7935f79d77 fixed a syntax error 2021-06-28 04:20:54 -05:00
ef443346d1 Try to generate zip versions and installers 2021-06-28 04:16:59 -05:00
a27eee1fa2 Add submodules 2021-06-28 03:40:00 -05:00
b839dc077c Updated a small typo 2021-06-28 03:29:27 -05:00
2b719858c2 Initial implementation of gitlab's CI file 2021-06-28 03:28:43 -05:00
97afc379e8 Pushed a new snapshot 2021-06-28 00:47:23 -05:00
7f401ba789 Removed unneeded locales 2021-06-28 00:39:45 -05:00
ff22ae5653 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2021-06-28 00:38:54 -05:00
02d94fcea0 Fixed a small issue when loading a conversation buffer 2021-06-28 00:36:53 -05:00
df2015f360 Merge pull request #384 from manuelcortez/twitter_videos
Support for Twitter video playback
2021-06-27 23:40:11 -05:00
64a14c831b Added initial support for playback of Twitter videos (only works in tweets so far) 2021-06-27 21:40:22 -05:00
fbbe7852c2 Added serbian to generate documentation 2021-06-27 20:57:13 -05:00
9dfccd2bd0 Updated documentation generators 2021-06-27 20:55:16 -05:00
969a75e9f3 Merge pull request #380 from manuelcortez/better_memory_management
Replace the cache database for a SQLite backed implementation
2021-06-27 19:10:50 -05:00
a59ba5ef78 Reset cache every time we call to save_persistent_data in tests 2021-06-27 19:09:49 -05:00
3ebfdbc48b Request for a restart when switching memory cache 2021-06-27 19:09:21 -05:00
8db14a95c1 Call reduce.reduce_tweet and session.save_users when retrieving previous items for a buffer 2021-06-27 18:07:56 -05:00
1b9062d86f aHandles deleted users for direct messages without wasting too many API calls 2021-06-27 18:05:35 -05:00
4b60a79e49 Made get_all_mentioned to take into account sometimes tweets might have no entities defined 2021-06-27 18:04:26 -05:00
002e1ccb55 If get_user is called with a full user object as an argument, logs it and changes the object to be only the user_id 2021-06-27 16:03:36 -05:00
0bcdf88290 Merge pull request #382 from nidza07/SerbianTranslation
Added Serbian documentation and changelog.
2021-06-27 02:35:55 -05:00
0612c653b8 Make TWBlue to respect persist_size when loading the cache database. Added tests for both reading from disk and loading in memory caches 2021-06-27 02:32:28 -05:00
nikola nikola
c5dadb063a Added Serbian documentation and changelog. 2021-06-27 04:22:02 +02:00
35d6010298 Save tweets cache by taking into account persist_size 2021-06-26 15:16:50 -05:00
40a63d9e16 Merge pull request #381 from nidza07/SerbianTranslation
Updated Serbian translation.
2021-06-26 02:16:13 -05:00
5712dd735b Added a new setting in account dialog to control whehter we load the cache db into memory or read it from disk 2021-06-26 01:52:07 -05:00
2c75ea5005 Fixed a small issue referencing an user in the old way while retrieving all mentioned users in a tweet 2021-06-26 01:50:47 -05:00
e35f37fcc2 Updated changelog 2021-06-26 01:49:55 -05:00
71358ea74d changed function names and cleaned code a little bit to reflect better the changes to object percistance 2021-06-25 23:35:33 -05:00
b8f822830f Added proper docstrings to reduce twitter objects 2021-06-25 22:47:10 -05:00
74e4fe6357 Ensure direct message buffers are correctly saved in database 2021-06-25 16:49:23 -05:00
77bee64421 Pass a null value to tweepy.models.Status as sometimes database saved objects might not include it 2021-06-25 16:48:44 -05:00
c761230566 Reduce the size of all tweets so it might make easier to handle those in a realtime database 2021-06-25 16:25:51 -05:00
49505fabcd Modify utils so those will take into account that entities might be not present in tweet objects 2021-06-25 13:14:01 -05:00
4ad01d7833 Retrieve user objects from the users list stored in SqlDict as opposed to loading it from tweet objects 2021-06-25 13:13:00 -05:00
ab1a13f886 Improve save_users() and get_user() as those will be used in more places later 2021-06-25 13:11:33 -05:00
nikola nikola
44c25e54f8 Updated Serbian translation. 2021-06-25 18:33:16 +02:00
cdabd6f055 Merge branch 'next-gen' into better_memory_management 2021-06-24 09:54:45 -05:00
60144a6b08 Added initial support to SqliteDict package 2021-06-24 09:52:10 -05:00
382acf7c8c Use slitedict to attempt to reduce memory usage when caching tweets 2021-06-23 13:40:21 -05:00
José Manuel Delicado
03ba59028f Merge pull request #379 from ivnc/next-gen
Fix arrow for Galician
2021-06-23 20:05:24 +02:00
Iván Novegil
50125fc55a Fix arrow for Galician. Modify some existing translations to localize them properly 2021-06-23 18:45:33 +02:00
39e1fb017c Made code indentation to comply with PEP8 2021-06-16 16:18:41 -05:00
2aaa4eced3 Removed Catala and Basque locale as they are in arrow already. Disabled Galician locale cause it's not fully implemented and fails 2021-06-16 16:17:16 -05:00
José Manuel
6d2eac5b1c Merge pull request #332 from Oreonan/fr-04062020
Some changes for french translation
2021-05-29 21:56:33 +02:00
Oreonan
40040d1b17 Merge branch 'next-gen' into fr-04062020 2021-05-14 21:24:46 +02:00
2a791d43bf Fixed an error when parsing a DM sent from an deleted account 2021-05-14 09:52:19 -05:00
b10aeb046d Changed label of direct message's text field so it will not reference any username in the hint. Closes #366 2021-05-07 17:18:21 -05:00
7d6e230fd9 Fix issue that avoids TWBlue to use Shift+F10 as menu key. Fixes #353 2021-05-07 16:52:10 -05:00
9346bba7a0 Fixed a small bug when sending long tweets via twyshort 2021-05-03 10:05:14 -05:00
30f739c42e Updated version to a new snapshot 2021-04-19 16:51:26 -05:00
eb0679cb96 Make unexistent users to throw an error when loading a timeline 2021-04-17 13:00:30 -05:00
45deae3402 Fixed rendering of retweets of quoted tweets. Fixes #365 2021-03-09 16:41:58 -06:00
5b0b26799d Install platform_utils, accessible_output2, libloader and sound_lib from upstream. Closes #369 2021-03-09 16:36:23 -06:00
ee234b80a7 Fixed error when parsing long tweets. 2021-03-09 11:35:52 -06:00
0065af2aef Avoid removing buffers when api_error=130 2021-02-25 16:57:32 -06:00
9c086cfa0f Prevend some errors to be identified as current user being blocked 2021-02-11 12:50:09 -06:00
2f263a23b7 Avoid giving false positive errors when buffers are updating 2021-02-10 09:34:30 -06:00
9cb6eafbbc Fixed issue in autocomplete users feature. closes #367 2021-02-04 12:30:20 -06:00
cba7c39a0e Updated to snapshot 4 2021-01-27 17:49:59 -06:00
e2e8b84e6a Fixed focus when search dialog opens. Fixes #364 2021-01-27 17:32:12 -06:00
eca0c0dbbd Fixed shelve method 2021-01-27 17:31:14 -06:00
36cc3f9365 Fix call to retrieve muted user IDS on twitter session 2021-01-27 17:30:10 -06:00
63d7cbe7c4 Implemented user searches. 2021-01-27 16:27:33 -06:00
ae57cc3404 Retrieve up to 5000 users when getting list IDS 2021-01-27 15:03:47 -06:00
23df8f8a7f Updated python version in CI config file 2021-01-27 14:00:29 -06:00
2f278b7f3c Removed yet another reference to Twython 2021-01-27 13:30:37 -06:00
9444939c35 Replaced cursored calls for manual calls to function with return_cursors. This way we will avoid hitting TWitter Rate limits accidentally 2021-01-27 13:20:47 -06:00
6688dc1163 Removed unneeded parameters when retrieving direct messages 2021-01-27 13:19:16 -06:00
6a7300b35f Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2021-01-27 10:38:21 -06:00
c95a2feb5e Faster implementation of dm loading due to a call to lookup_users instead to get_user 2021-01-27 10:36:44 -06:00
8042d28b13 Merge pull request #358 from riku22/update_japanese_translation
Update japanese translation
2021-01-26 17:46:29 -06:00
890359f8c7 Fixed Rate limit issue in cursored functions. Closes #354 2021-01-26 17:09:38 -06:00
91955e80d2 Fixed Picture OCR. Fixes #356 2021-01-26 16:23:48 -06:00
6c1c66226d Merge branch 'googletrans-integration' into next-gen 2021-01-26 16:11:49 -06:00
1a76913aac Removed debug info 2021-01-26 16:09:55 -06:00
f85af2cbd2 Merge pull request #361 from manuelcortez/googletrans-integration
Change translation service
2021-01-26 16:06:38 -06:00
351f700927 Replaced translation services. Fixes #355 2021-01-26 16:01:26 -06:00
Jose Manuel Delicado
abdde4c1f0 Updated windows-dependencies submodule 2021-01-23 19:31:58 +01:00
Jose Manuel Delicado
4899285eca Updated readme 2021-01-23 19:30:30 +01:00
riku
fb3e6b537c Updated Japanese translation 2021-01-23 09:05:35 +09:00
riku
22d1cc9ce9 Merge branch 'next-gen' into update_japanese_translation 2021-01-23 09:03:01 +09:00
304d91e8b7 Started replacing yandex.translate with googletrans 2021-01-22 17:55:15 -06:00
cf650052e4 Merge pull request #351 from riku22/add_requirements
Add pyenchant to requirements
2021-01-22 17:34:20 -06:00
riku
10055788e4 Removed twython 2021-01-23 07:26:42 +09:00
riku
a67f3f037e Merge branch 'next-gen' into add_requirements 2021-01-23 07:26:14 +09:00
Jose Manuel Delicado
4d736c00fc main.py: Check for other running instances before anything else 2021-01-22 18:50:09 +01:00
Jose Manuel Delicado
29b8a645db Updated changelog 2021-01-22 18:22:07 +01:00
Jose Manuel Delicado
2459a499ce Merge branch 'next-gen' into proxy 2021-01-22 18:01:29 +01:00
28f4e3a534 Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2021-01-22 10:56:07 -06:00
e2931e96eb Remove references to Twython. #351 2021-01-22 10:55:08 -06:00
Jose Manuel Delicado
fbcba39e69 Small fixes. Support for socks4a proxies 2021-01-22 17:54:47 +01:00
Jose Manuel Delicado
b59d262dca Apply proxy settings before TWBlue main components start loading 2021-01-22 17:40:48 +01:00
Jose Manuel Delicado
7f4a13231f config.py: update the list of supported proxy types 2021-01-22 17:09:39 +01:00
Jose Manuel Delicado
4ab5af2ae9 Store the proxy type as an integer when saving the configuration 2021-01-22 17:06:32 +01:00
Jose Manuel Delicado
4c4eaf4012 controller/settings: assign the proxy type in the combo box based on the retrieved integer from config 2021-01-22 17:01:44 +01:00
Jose Manuel Delicado
b03198a39f Proxy type is now an integer instead of a string 2021-01-22 16:55:38 +01:00
Jose Manuel Delicado
06dc26e962 Proxy port is now an integer in the default configuration 2021-01-22 16:54:20 +01:00
Jose Manuel Delicado
bcc8f5968e Configuration dialog: use SpinCTRL instead of TextCTRL to specify the proxy port 2021-01-22 16:53:07 +01:00
Jose Manuel Delicado
4c144d2f7e Requirements: always install the latest WXPython version 2021-01-22 16:20:41 +01:00
747290e16a Require extended tweets for quoted tweets. Fixes #352 2021-01-22 09:15:47 -06:00
Jose Manuel Delicado
64b6d4df74 Requirements: do not install an idna version greather than 3.0 to avoid breaking requests package dependencies. Added wheel as a requirement to increase installation speed 2021-01-22 12:46:37 +01:00
riku
22d48cb5d9 Update Japanese translation 2021-01-22 12:52:19 +09:00
riku
fea01c44ca Add twython and pyenchant to requirements 2021-01-22 11:31:40 +09:00
b80a342f92 Count tweet characters and URLS as specified by Twitter. Fixes #199. Fixes #315 2021-01-21 17:12:18 -06:00
60a13d974c Added twitter-text-parser as a dependency 2021-01-21 17:10:56 -06:00
4c9000839d Fixed some errors when starting people buffers 2021-01-21 08:27:50 -06:00
b3c24c6734 Merge pull request #347 from manuelcortez/tweepy
Migration to Tweepy. Closes #237 , closes #240
2021-01-20 17:50:19 -06:00
a2a8cc5b79 Updated changelog 2021-01-20 17:44:29 -06:00
da7a208c1f Implemented people buffers and searches 2021-01-20 17:06:59 -06:00
979a3d3e99 Changed a function to order people buffers after being retrieved from Twitter 2021-01-20 17:06:15 -06:00
1f11ea7aa0 fixed an issue in compose for followers 2021-01-20 17:05:39 -06:00
8bdc933bce Migrated direct messages. Cursor support still require testing 2021-01-20 15:56:20 -06:00
999cbba464 Implemented function to get previous item for tweets and searches 2021-01-20 11:02:21 -06:00
7457521398 Restored media uploading (needs testing). Fixes #240 2021-01-20 10:35:11 -06:00
366b61134e Fixed trending topics dialog 2021-01-20 10:00:19 -06:00
52154ac271 Migrated support to Trending Topics 2021-01-20 09:59:38 -06:00
cdc285e362 Implemented support to list buffer creation 2021-01-20 09:21:50 -06:00
4643301764 Implemented list creation, edition and removal 2021-01-19 17:47:54 -06:00
f546543e9b Implemented tweet searches and conversation fupport 2021-01-14 09:18:57 -06:00
85fe94ec0c Implemented user actions (needs testing) 2021-01-13 10:38:26 -06:00
db13c96baa Fixed some incorrect error handling on utils 2021-01-13 10:33:49 -06:00
33b6000b41 Implemented user profile viewer 2021-01-13 10:32:03 -06:00
a261365682 Fixed some stuff in main controller. Likes work again 2021-01-13 10:31:37 -06:00
4064582583 Remove old reference to Twython error handling 2021-01-13 08:47:13 -06:00
fc0da0bdbb Migrated the main controller to use tweepy 2021-01-13 08:41:32 -06:00
76c678d4ba Fixed replies, retweets and iand tweet deletion 2021-01-05 11:10:55 -06:00
3db8b7b021 Fixed some rendering for dm's and people 2021-01-05 11:04:06 -06:00
fa49637d0e Replaced twython old exception handling by tweepy 2021-01-05 09:34:52 -06:00
65ec231349 Fixed an indentation issue 2021-01-05 09:20:36 -06:00
93705bf534 Fixed user timeline creation 2021-01-04 15:52:25 -06:00
29a905199d Basic implementation of tweetDisplayerDialog. Needs testing with lists of tweets and image description entities 2021-01-04 12:55:29 -06:00
8569d9b0a0 Readded code to load timelines with tweepy 2021-01-04 12:30:38 -06:00
1e1f2b089f Fix call to api.supported_languages() 2021-01-04 12:23:04 -06:00
78cc6e6784 Changed library calls on some basic buffer creation and manipulation 2021-01-04 11:16:56 -06:00
a37f339fea Fixed some issues when determining the presence of long tweets based on entities present in objects 2021-01-04 11:15:27 -06:00
d0cc12ef5c Added basic tweet rendering 2021-01-04 11:14:55 -06:00
bcd51d6259 Migrated most of twitter util functions from sessions/twitter/utils.py 2021-01-04 11:13:20 -06:00
7ceb806af2 Added more changes in order to deal with tweet objects 2020-12-22 17:29:33 -06:00
José Manuel
8f35fc0ec0 Merge pull request #342 from maniyax/next-gen
Edit Russian localization
2020-10-29 10:40:11 +01:00
maniyax
3f79fab8e5 update contributors.txt 2020-10-23 14:33:33 +03:00
maniyax
53dee8cb81 edit ru-locale 2020-10-23 14:31:47 +03:00
56103466fa Replaced get_account_settings, get_supported_languages, show_user, get_mute_ids 2020-07-20 13:26:22 -05:00
13f5df3a48 Use Tweepy functions for authentication and login 2020-07-20 13:11:37 -05:00
a89cc35d40 Started work: Added Tweepy as a dependency 2020-07-20 12:56:29 -05:00
276cd4b4dd Fixed a typo in last commit 2020-06-09 11:45:00 -05:00
c509433b2c Added an helper function for expanding t.co URLS for tweets 2020-06-09 11:34:02 -05:00
7292b36137 Removed old code from utils 2020-06-09 10:45:09 -05:00
Oreonan
0111c8aae1 some changes for french translation 2020-06-04 21:20:18 +02:00
eefe5e6200 Updated snapshot information 2020-06-04 12:14:57 -05:00
492f1d8aa5 Expand URL'S in profile bios 2020-06-04 10:25:00 -05:00
ac20ced5fa Expand URL in user profiles. Closes #275 2020-06-03 12:32:01 -05:00
866cd53328 Fixed typo in changelog 2020-06-03 11:24:20 -05:00
f523e3e81e Updated changelog 2020-06-03 11:22:07 -05:00
bdd7d617c3 Update title on user changes when sending a DM. Fixes #276 2020-06-03 11:19:23 -05:00
a0bc5f6cb3 Ignore folder env for virtual environments. Closes #321 2020-05-30 08:42:30 -05:00
3f238635f2 Fixed a typo 2020-05-29 21:25:13 -05:00
c300476ad2 Updated changelog to add a reference to an issue 2020-05-29 21:22:00 -05:00
6a2e00c467 Fixed calls to paths.config_path in Autocompletion module 2020-05-29 12:37:25 -05:00
4fa983a314 Show error when loading an account with invalid or expired tokens. Fixes #328 2020-05-29 11:17:18 -05:00
ed5b66d033 Fixed audio playback from URL 2020-05-29 10:28:10 -05:00
bec45c37c1 Updated Readme instructions to Python 3 and cx_freeze 2020-05-29 10:13:28 -05:00
ad42c09fef Updated changelog 2020-05-29 09:51:34 -05:00
084fa1894c Restart TWBlue gracefully 2020-05-29 09:49:57 -05:00
211 changed files with 18073 additions and 33139 deletions

1
.gitignore vendored
View File

@@ -20,3 +20,4 @@ release-snapshot/
src/com_cache/ src/com_cache/
doc/strings.py doc/strings.py
doc/changelog.py doc/changelog.py
env/

123
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,123 @@
variables:
GIT_SUBMODULE_STRATEGY: recursive
PYTHON: "C:\\python38\\python.exe"
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
stages:
- build
- make_installer
- upload
twblue32:
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.8.10 -y -ForceX86
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
stage: build
interruptible: true
script:
# Create html documentation firstly.
- cd doc
- '&$env:PYTHON documentation_importer.py'
- cd ..\src
- '&$env:PYTHON ..\doc\generator.py'
- '&$env:PYTHON write_version_data.py'
- '&$env:PYTHON setup.py build'
- cd ..
- mkdir artifacts
- cd scripts
- '&$env:PYTHON make_archive.py'
- cd ..
- mv src/dist artifacts/TWBlue
- move src/twblue.zip artifacts/twblue_x86.zip
# Move the generated script nsis file to artifacts, so we won't need python when generating the installer.
- move scripts/twblue.nsi artifacts/twblue.nsi
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
twblue64:
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.8.10 -y
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
stage: build
interruptible: true
script:
# Create html documentation firstly.
- cd doc
- '&$env:PYTHON documentation_importer.py'
- cd ..\src
- '&$env:PYTHON ..\doc\generator.py'
- '&$env:PYTHON write_version_data.py'
- '&$env:PYTHON setup.py build'
- cd ..
- mkdir artifacts
- cd scripts
- '&$env:PYTHON make_archive.py'
- cd ..
- mv src/dist artifacts/TWBlue64
- move src/twblue.zip artifacts/twblue_x64.zip
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
generate_versions:
stage: make_installer
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 nsis -y -ForceX86
script:
- move artifacts/TWBlue scripts/
- move artifacts/TWBlue64 scripts/
- move artifacts/twblue.nsi scripts/installer.nsi
- cd scripts
- '&$env:NSIS installer.nsi'
- move twblue_setup.exe ../artifacts
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
upload:
stage: upload
tags:
- linux
image: python
interruptible: true
script:
- cd artifacts
- python ../scripts/upload.py
only:
- tags
- schedules

View File

@@ -24,45 +24,37 @@ This document describes how to run tw blue from source and how to build a binary
### Required dependencies. ### 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: 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:
```
git submodule init git submodule init
git submodule update git submodule update
```
All the dependencies provided in this folder are prebuilt. If you want to build them from source, you will need Microsoft visual Studio 2008.
#### Dependencies packaged in windows installers #### Dependencies packaged in windows installers
* [Python,](http://python.org) version 2.7.16 * [Python,](https://python.org) version 3.8.7
If you want to build both x86 and x64 binaries, you can install python x86 to C:\python27 and python x64 to C:\python27x64, for example. 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.
* [PyEnchant,](http://pythonhosted.org/pyenchant/) version 1.6.6.
x64 version has been built by TWBlue developers, so you only will find it in windows-dependencies folder
The windows installers are available only in the windows-dependencies folder.
To build a binary version:
* [Py2exe](http://www.sourceforge.net/projects/py2exe/) for Python 2.7, version 0.6.9
#### Dependencies that must be installed using pip #### 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: 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:\python27x64\scripts `cd C:\python37x64\scripts`
You can also add the scripts folder to your path environment variable or choose the corresponding option when installing Python. 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: pip and setuptools are included in the Python installer since version 2.7.9.
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 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 `pip install -r requirements.txt`
Note that if you perform the command from the path where Pip is located, you need to specify the path to your Tw Blue root folder where the requirements file is located, for example: Note that if you perform the command from the path where Pip is located, you need to specify the path to your Tw Blue root folder where the requirements file is located, for example:
pip install -r D:\repos\TwBlue\requirements.txt `pip install -r D:\repos\TwBlue\requirements.txt`
Pip will automatically get the additional libraries that the listed packages need to work properly. Pip will automatically get the additional libraries that the listed packages need to work properly.
If you need to update your dependencies, perform the following command: If you need to update your dependencies, perform the following command:
pip install --upgrade -r requirements.txt `pip install --upgrade -r requirements.txt`
#### Other dependencies #### Other dependencies
@@ -71,7 +63,7 @@ These dependencies are located in the windows-dependencies directory. You don't
* Bootstrap 1.2.1: included in dependencies directory. * Bootstrap 1.2.1: included in dependencies directory.
This dependency has been built using pure basic 4.61. Its source can be found at http://hg.q-continuum.net/updater This dependency has been built using pure basic 4.61. Its source can be found at http://hg.q-continuum.net/updater
* [oggenc2.exe,](http://www.rarewares.org/ogg-oggenc.php) version 2.87 * [oggenc2.exe,](http://www.rarewares.org/ogg-oggenc.php) version 2.87
* Microsoft Visual c++ 2008 redistributable dlls. * Microsoft Visual c++ 2019 redistributable dlls.
* VLC plugins and DLL libraries. * VLC plugins and DLL libraries.
#### Dependencies required to build the installer #### Dependencies required to build the installer
@@ -94,7 +86,7 @@ In order to add the support for spell checking in more languages than english yo
Now that you have installed all these packages, you can run TW Blue from source using a command prompt. Navigate to the repo's `src` directory, and type the following command: Now that you have installed all these packages, you can run TW Blue from source using a command prompt. Navigate to the repo's `src` directory, and type the following command:
python main.py `python main.py`
If necessary, change the first part of the command to reflect the location of your python executable. You can run TW Blue using python x86 and x64. If necessary, change the first part of the command to reflect the location of your python executable. You can run TW Blue using python x86 and x64.
@@ -102,8 +94,8 @@ Now that you have installed all these packages, you can run TW Blue from source
To generate the documentation in html format, navigate to the doc folder inside this repo. After that, run these commands: To generate the documentation in html format, navigate to the doc folder inside this repo. After that, run these commands:
python document_importer.py `python document_importer.py`
python generator.py `python generator.py`
The documentation will be generated, placing each language in a separate folder in the doc directory. Move these folders (for example `de`, `en`, `es`, `fr`, `it`, ...) to `src/documentation`, creating the directory if necessary. The documentation will be generated, placing each language in a separate folder in the doc directory. Move these folders (for example `de`, `en`, `es`, `fr`, `it`, ...) to `src/documentation`, creating the directory if necessary.
Also, copy the `license.txt` file located in the root of the repo to the documentation folder. Also, copy the `license.txt` file located in the root of the repo to the documentation folder.
@@ -114,7 +106,7 @@ A binary version doesn't need python and the other dependencies to run, it's the
To build it, run the following command from the src folder: To build it, run the following command from the src folder:
python setup.py py2exe `python setup.py build`
You will find the binaries in the dist directory. You will find the binaries in the dist directory.
@@ -122,9 +114,9 @@ To build it, run the following command from the src folder:
If you want to install TWBlue on your computer, you must create the installer first. Follow these steps: If you want to install TWBlue on your computer, you must create the installer first. Follow these steps:
* Navigate to the src directory, and create a binary version for x86: C:\python27\python setup.py py2exe * Navigate to the src directory, and create a binary version for x86: C:\python37\python setup.py build
* Move the dist directory to the scripts folder in this repo, and rename it to twblue * Move the dist directory to the scripts folder in this repo, and rename it to twblue
* Repeat these steps with Python for x64: C:\python27x64\python setup.py py2exe * Repeat these steps with Python for x64: C:\python37x64\python setup.py build
* Move the new dist directory to the scripts folder, and rename it to twblue64 * Move the new dist directory to the scripts folder, and rename it to twblue64
* Go to the scripts folder, right click on the twblue.nsi file, and choose compyle unicode NSIS script * Go to the scripts folder, right click on the twblue.nsi file, and choose compyle unicode NSIS script
* This may take a while. After the process, you will find the installer in the scripts folder * This may take a while. After the process, you will find the installer in the scripts folder
@@ -137,9 +129,9 @@ Run the gen_pot.bat file, located in the tools directory. Your python installati
If you want to have TWBlue on your PortableApps.com platform, follow these steps: 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:\python27\python setup.py py2exe * 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 * 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:\python27x64\python setup.py py2exe * 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 * 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 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. * 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.

View File

@@ -1,84 +0,0 @@
pull_requests:
# Avoid building after pull requests. Shall we disable this option?
do_not_increment_build_number: true
# Only build whenever we add tags to the repo.
skip_non_tags: true
environment:
matrix:
# List of python versions we want to work with.
- PYTHON: "C:\\Python37"
PYTHON_VERSION: "3.7.x" # currently 2.7.9
PYTHON_ARCH: "32"
# perhaps we may enable this one in future?
# - PYTHON: "C:\\Python37-x64"
# PYTHON_VERSION: "3.7.x" # currently 2.7.9
# PYTHON_ARCH: "64"
# This is important so we will retrieve everything in submodules as opposed to default method.
clone_script:
- cmd: >-
git clone -q --branch=%APPVEYOR_REPO_BRANCH% https://github.com/%APPVEYOR_REPO_NAME%.git %APPVEYOR_BUILD_FOLDER%
&& cd %APPVEYOR_BUILD_FOLDER%
&& git checkout -qf %APPVEYOR_REPO_COMMIT%
&& git submodule update --init --recursive
install:
# If there is a newer build queued for the same PR, cancel this one.
# The AppVeyor 'rollout builds' option is supposed to serve the same
# purpose but it is problematic because it tends to cancel builds pushed
# directly to master instead of just PR builds (or the converse).
# credits: JuliaLang developers.
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
throw "There are newer queued builds for this pull request, failing early." }
# - ECHO "Filesystem root:"
# - ps: "ls \"C:/\""
# Check that we have the expected version and architecture for Python
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- "python --version"
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
# Upgrade to the latest version of pip to avoid it displaying warnings
# about it being out of date.
- "python -m pip install --upgrade pip setuptools"
# Install the build dependencies of the project. If some dependencies contain
# compiled extensions and are not provided as pre-built wheel packages,
# pip will build them from source using the MSVC compiler matching the
# target Python version and architecture
- "%CMD_IN_ENV% pip install -r requirements.txt"
- "%CMD_IN_ENV% pip install pyenchant"
build_script:
# Build documentation at first, so setup.py won't fail when copying everything.
- "cd doc"
# Import documentation before building, so strings.py will be created.
- "%CMD_IN_ENV% python documentation_importer.py"
# build doc from src folder so it will generate result files right there.
- "cd ..\\src"
- "%CMD_IN_ENV% python ..\\doc\\generator.py"
# Build distributable files.
- "%CMD_IN_ENV% python setup.py build"
- "cd dist"
# Zip it all.
- cmd: 7z a ..\..\snapshot.zip *
artifacts:
- path: snapshot.zip
deploy:
- provider: FTP
host: twblue.es
protocol: ftp
beta: true
username: twblue.es
password:
secure: lQZqpYRnHf4LLVOg0C42NQ==
folder: 'web/pubs'

View File

@@ -39,4 +39,5 @@ florian Ionașcu
Christian Leo Mameli Christian Leo Mameli
Natalia Hedlund (Наталья Хедлунд) Natalia Hedlund (Наталья Хедлунд)
Valeria (Валерия) Valeria (Валерия)
Corentin Bacqué-Cazenave Oreonan
Artem Plaksin (maniyax)

View File

@@ -2,7 +2,43 @@
## changes in this version ## changes in this version
* 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]) * 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 contains audio or videos will be detected as audio items, and you can playback those with the regular command to play audios. ([#384,](https://github.com/manuelcortez/TWBlue/pull/384))
* 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))
## Changes in version 0.95 ## Changes in version 0.95

View File

@@ -16,9 +16,9 @@ def prepare_documentation_in_file(fileSource, fileDest):
if "\n" == i: if "\n" == i:
newvar = "\"\"," newvar = "\"\","
elif "\n" == i[-1]: elif "\n" == i[-1]:
newvar = "_(u\"\"\"%s\"\"\"),\n" % (i[:-1]) newvar = "\"\"\"%s\"\"\",\n" % (i[:-1])
else: else:
newvar = "_(u\"\"\"%s\"\"\"),\n" % (i) newvar = "\"\"\"%s\"\"\",\n" % (i)
f2.write(newvar) f2.write(newvar)
f1.close() f1.close()
f2.write("]") f2.write("]")

View File

@@ -8,28 +8,27 @@ import shutil
from codecs import open as _open from codecs import open as _open
from importlib import reload from importlib import reload
def change_language(name, language): def get_translation_function(name, language):
global _ if language == "en":
os.environ["lang"] = language return gettext.NullTranslations()
_ = gettext.install(name, os.path.join(paths.app_path(), "locales")) translation_function = gettext.translation(name, os.path.join(paths.app_path(), "locales"), languages=[language])
return translation_function
# the list of supported language codes of TW Blue # the list of supported language codes of TW Blue
languages = ["en", "es", "fr", "de", "it", "gl", "ja", "ru", "ro", "eu", "ca", "da"] languages = ["en", "es", "fr", "de", "it", "gl", "ja", "ru", "ro", "eu", "ca", "da", "sr"]
def generate_document(language, document_type="documentation"): def generate_document(language, document_type="documentation"):
if document_type == "documentation": if document_type == "documentation":
translation_file = "twblue-documentation" translation_file = "twblue-documentation"
change_language(translation_file, language) translation_function = get_translation_function(translation_file, language)
reload(strings) markdown_file = markdown.markdown("\n".join([translation_function.gettext(s[:-1]) if s != "\n" else s for s in strings.documentation[1:]]), extensions=["markdown.extensions.toc"])
markdown_file = markdown.markdown("\n".join(strings.documentation[1:]), extensions=["markdown.extensions.toc"]) title = translation_function.gettext(strings.documentation[0][:-1])
title = strings.documentation[0]
filename = "manual.html" filename = "manual.html"
elif document_type == "changelog": elif document_type == "changelog":
translation_file = "twblue-changelog" translation_file = "twblue-changelog"
change_language(translation_file, language) translation_function = get_translation_function(translation_file, language)
reload(changelog) markdown_file = markdown.markdown("\n".join([translation_function.gettext(s[:-1]) if s != "\n" else s for s in changelog.documentation[1:]]), extensions=["markdown.extensions.toc"])
markdown_file = markdown.markdown("\n".join(changelog.documentation[1:]), extensions=["markdown.extensions.toc"]) title = translation_function.gettext(changelog.documentation[0][:-1])
title = changelog.documentation[0]
filename = "changelog.html" filename = "changelog.html"
first_html_block = """<!doctype html> first_html_block = """<!doctype html>
<html lang="%s"> <html lang="%s">
@@ -56,11 +55,13 @@ def create_documentation():
shutil.copy(os.path.join("..", "license.txt"), os.path.join("documentation", "license.txt")) shutil.copy(os.path.join("..", "license.txt"), os.path.join("documentation", "license.txt"))
for i in languages: for i in languages:
print("Creating documentation for: %s" % (i,)) print("Creating documentation for: %s" % (i,))
try:
generate_document(i) generate_document(i)
generate_document(i, "changelog") generate_document(i, "changelog")
except:
continue
print("Done") print("Done")
change_language("twblue-documentation", "en")
import strings import strings
import changelog import changelog
create_documentation() create_documentation()

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

View File

@@ -5,19 +5,6 @@ return "key\0";
char *get_api_secret(){ char *get_api_secret(){
return "secret_key\0"; return "secret_key\0";
} }
char *get_dropbox_api_key(){
return "key\0";
}
char *get_dropbox_api_secret(){
return "secret_key\0";
}
char *get_twishort_api_key(){ char *get_twishort_api_key(){
return "key\0"; return "key\0";
} }
char *get_bts_user(){
return "user\0";
}
char *get_bts_password(){
return "pass\0";
}

View File

@@ -3,10 +3,6 @@
char *get_api_key(); char *get_api_key();
char *get_api_secret(); char *get_api_secret();
char *get_dropbox_api_key();
char *get_dropbox_api_secret();
char *get_twishort_api_key(); char *get_twishort_api_key();
char *get_bts_user();
char *get_bts_password();
#endif #endif

View File

@@ -1,4 +1,5 @@
wxpython==4.0.3 wxpython
wheel
six six
configobj configobj
markdown markdown
@@ -8,25 +9,46 @@ oauthlib
requests-oauthlib requests-oauthlib
requests-toolbelt requests-toolbelt
pypubsub pypubsub
pygeocoder geopy
arrow arrow
python-dateutil python-dateutil
futures futures
winpaths winpaths
PySocks PySocks
win_inet_pton win_inet_pton
yandex.translate # Install the latest RC of this lib
idna # see https://github.com/ssut/py-googletrans/issues/234
googletrans==4.0.0-rc1
idna<3,>=2.5
chardet chardet
urllib3 urllib3
youtube-dl youtube-dl
python-vlc python-vlc
pypiwin32 pypiwin32
pywin32
certifi certifi
backports.functools_lru_cache backports.functools_lru_cache
cx_freeze cx_freeze
git+https://github.com/manuelcortez/twython tweepy
git+https://github.com/manuelcortez/libloader twitter-text-parser
git+https://github.com/manuelcortez/platform_utils pyenchant
git+https://github.com/manuelcortez/accessible_output2 sqlitedict
git+https://github.com/jmdaweb/sound_lib cx-Logging
h11
h2
hpack
hstspreload
httpcore
httpx
hyperframe
rfc3986
sniffio
attrs
importlib-metadata
numpy
pillow
charset-normalizer
git+https://github.com/accessibleapps/libloader
git+https://github.com/accessibleapps/platform_utils
git+https://github.com/accessibleapps/accessible_output2
git+https://github.com/accessibleapps/sound_lib

12
scripts/make_archive.py Normal file
View File

@@ -0,0 +1,12 @@
import shutil
import os
import sys
def create_archive():
os.chdir("..\\src")
print("Creating zip archive...")
folder = "dist"
shutil.make_archive("twblue", "zip", folder)
os.chdir("..\\scripts")
create_archive()

View File

@@ -14,11 +14,11 @@ SetCompress auto
SetCompressor /solid lzma SetCompressor /solid lzma
SetDatablockOptimize on SetDatablockOptimize on
VIAddVersionKey ProductName "TWBlue" VIAddVersionKey ProductName "TWBlue"
VIAddVersionKey LegalCopyright "Copyright 2018 Manuel Cortéz." VIAddVersionKey LegalCopyright "Copyright 2014-2021 Manuel Cortéz."
VIAddVersionKey ProductVersion "0.95" VIAddVersionKey ProductVersion "0.95.0"
VIAddVersionKey FileVersion "0.95" VIAddVersionKey FileVersion "0.95.0"
VIProductVersion "0.95.0.0" VIProductVersion "0.95.0"
VIFileVersion "0.95.0.0" VIFileVersion "0.95.0"
!insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_WELCOME
!define MUI_LICENSEPAGE_RADIOBUTTONS !define MUI_LICENSEPAGE_RADIOBUTTONS
!insertmacro MUI_PAGE_LICENSE "license.txt" !insertmacro MUI_PAGE_LICENSE "license.txt"
@@ -27,12 +27,12 @@ var StartMenuFolder
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder !insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
!insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_LINK "Visit TWBlue website" !define MUI_FINISHPAGE_LINK "Visit TWBlue website"
!define MUI_FINISHPAGE_LINK_LOCATION "http://twblue.es" !define MUI_FINISHPAGE_LINK_LOCATION "https://twblue.es"
!define MUI_FINISHPAGE_RUN "$INSTDIR\TWBlue.exe" !define MUI_FINISHPAGE_RUN "$INSTDIR\TWBlue.exe"
!insertmacro MUI_PAGE_FINISH !insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "French" !insertmacro MUI_LANGUAGE "French"
!insertmacro MUI_LANGUAGE "Spanish" !insertmacro MUI_LANGUAGE "Spanish"
!insertmacro MUI_LANGUAGE "Italian" !insertmacro MUI_LANGUAGE "Italian"
@@ -73,9 +73,9 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "U
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.95" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.95"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "http://twblue.es" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "https://twblue.es"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 95 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1
SectionEnd SectionEnd

48
scripts/upload.py Normal file
View File

@@ -0,0 +1,48 @@
#! /usr/bin/env python
import sys
import os
import glob
import ftplib
transferred=0
def convert_bytes(n):
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
if n >= P:
return '%.2fPb' % (float(n) / T)
elif n >= T:
return '%.2fTb' % (float(n) / T)
elif n >= G:
return '%.2fGb' % (float(n) / G)
elif n >= M:
return '%.2fMb' % (float(n) / M)
elif n >= K:
return '%.2fKb' % (float(n) / K)
else:
return '%d' % n
def callback(progress):
global transferred
transferred = transferred+len(progress)
print("Uploaded {}".format(convert_bytes(transferred),))
ftp_server = os.environ.get("FTP_SERVER") or sys.argv[1]
ftp_username = os.environ.get("FTP_USERNAME") or sys.argv[2]
ftp_password = os.environ.get("FTP_PASSWORD") or sys.argv[3]
print("Uploading files to the TWBlue server...")
print("Connecting to %s" % (ftp_server,))
connection = ftplib.FTP(ftp_server)
print("Connected to FTP server {}".format(ftp_server,))
connection.login(user=ftp_username, passwd=ftp_password)
print("Logged in successfully")
connection.cwd("web/pubs")
files = glob.glob("*.zip")+glob.glob("*.exe")
print("These files will be uploaded into the version folder: {}".format(files,))
for file in files:
transferred = 0
print("Uploading {}".format(file,))
with open(file, "rb") as f:
connection.storbinary('STOR %s' % file, f, callback=callback, blocksize=1024*1024)
print("Upload completed. exiting...")
connection.quit()

View File

@@ -12,6 +12,7 @@ reverse_timelines = boolean(default=False)
announce_stream_status = boolean(default=True) announce_stream_status = boolean(default=True)
retweet_mode = string(default="ask") retweet_mode = string(default="ask")
persist_size = integer(default=0) persist_size = integer(default=0)
load_cache_in_memory=boolean(default=True)
show_screen_names = boolean(default=False) show_screen_names = boolean(default=False)
buffer_order = list(default=list('home','mentions', 'dm', 'sent_dm', 'sent_tweets','favorites','followers','friends','blocks','muted','events')) buffer_order = list(default=list('home','mentions', 'dm', 'sent_dm', 'sent_tweets','favorites','followers','friends','blocks','muted','events'))
@@ -20,7 +21,7 @@ volume = float(default=1.0)
input_device = string(default="Default") input_device = string(default="Default")
output_device = string(default="Default") output_device = string(default="Default")
session_mute = boolean(default=False) session_mute = boolean(default=False)
current_soundpack = string(default="default") current_soundpack = string(default="FreakyBlue")
indicate_audio = boolean(default=True) indicate_audio = boolean(default=True)
indicate_geo = boolean(default=True) indicate_geo = boolean(default=True)
indicate_img = boolean(default=True) indicate_img = boolean(default=True)
@@ -48,3 +49,5 @@ braille_reporting = boolean(default=True)
speech_reporting = boolean(default=True) speech_reporting = boolean(default=True)
[filters] [filters]
[user-aliases]

View File

@@ -24,8 +24,8 @@ mention_all = boolean(default=False)
no_streaming = boolean(default=False) no_streaming = boolean(default=False)
[proxy] [proxy]
type = string(default="Direct connection") type = integer(default=0)
server = string(default="") server = string(default="")
port = string(default="") port = integer(default=8080)
user = string(default="") user = string(default="")
password = string(default="") password = string(default="")

View File

@@ -3,20 +3,14 @@ import datetime
name = 'TWBlue' name = 'TWBlue'
short_name='twblue' short_name='twblue'
snapshot = True update_url = 'https://twblue.es/updates/updates.php'
if snapshot == False: mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/updates.json'
version = "0.95"
update_url = 'https://twblue.es/updates/stable.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json'
else:
version = "1"
update_url = 'https://twblue.es/updates/snapshot.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/snapshots.json'
authors = ["Manuel Cortéz", "José Manuel Delicado"] authors = ["Manuel Cortéz", "José Manuel Delicado"]
authorEmail = "manuel@manuelcortez.net" authorEmail = "manuel@manuelcortez.net"
copyright = "Copyright (C) 2013-2018, Manuel cortéz." copyright = "Copyright (C) 2013-2021, Manuel cortéz."
description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features." description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features."
translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Rémy Ruiz (French)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"] translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Oreonan (Français)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"]
url = u"https://twblue.es" url = u"https://twblue.es"
report_bugs_url = "https://github.com/manuelcortez/twblue/issues" report_bugs_url = "https://github.com/manuelcortez/twblue/issues"
supported_languages = [] supported_languages = []
version = "11"

View File

@@ -1,9 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from audio_services import matches_url from audio_services import matches_url
import youtube_utils
import requests import requests
from . import youtube_utils
@matches_url('https://audioboom.com') @matches_url('https://audioboom.com')
def convert_audioboom(url): def convert_audioboom(url):

View File

@@ -9,7 +9,7 @@ log = logging.getLogger("config")
MAINFILE = "twblue.conf" MAINFILE = "twblue.conf"
MAINSPEC = "app-configuration.defaults" MAINSPEC = "app-configuration.defaults"
proxyTypes=[u"http", u"https", u"socks4", u"socks5"] proxyTypes = ["system", "http", "socks4", "socks4a", "socks5", "socks5h"]
app = None app = None
keymap=None keymap=None
changed_keymap = False changed_keymap = False

View File

@@ -1,8 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" this package contains logic related to buffers. A buffer is a virtual representation of a group of items retrieved through the Social network API'S. from . import base as base
Ideally, new social networks added to TWBlue will have its own "buffers", and these buffers should be defined within this package, following the Twitter example. from . import twitter as twitter
Currently, the package contains the following modules:
* baseBuffers: Define a set of functions and structure to be expected in all buffers. New buffers should inherit its classes from one of the classes present here.
* twitterBuffers: All other code, specific to Twitter.
"""
from __future__ import unicode_literals

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from .account import AccountBuffer
from .base import Buffer
from .empty import EmptyBuffer

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
""" Common logic to all buffers in TWBlue."""
import logging
import config
import widgetUtils
from pubsub import pub
from wxUI import buffers
from . import base
log = logging.getLogger("controller.buffers.base.account")
class AccountBuffer(base.Buffer):
def __init__(self, parent, name, account, account_id):
super(AccountBuffer, self).__init__(parent, None, name)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.accountPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.session = None
self.needs_init = False
self.account = account
self.buffer.account = account
self.name = name
self.account_id = account_id
def setup_account(self):
widgetUtils.connect_event(self.buffer, widgetUtils.CHECKBOX, self.autostart, menuitem=self.buffer.autostart_account)
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(False)
else:
self.buffer.change_autostart(True)
if not hasattr(self, "logged"):
self.buffer.change_login(login=False)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.logout)
else:
self.buffer.change_login(login=True)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.login)
def login(self, *args, **kwargs):
del self.logged
self.setup_account()
pub.sendMessage("login", session_id=self.account_id)
def logout(self, *args, **kwargs):
self.logged = False
self.setup_account()
pub.sendMessage("logout", session_id=self.account_id)
def autostart(self, *args, **kwargs):
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(True)
config.app["sessions"]["ignored_sessions"].remove(self.account_id)
else:
self.buffer.change_autostart(False)
config.app["sessions"]["ignored_sessions"].append(self.account_id)
config.app.write()

View File

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

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
import logging
from wxUI import buffers
from . import base
log = logging.getLogger("controller.buffers.base.empty")
class EmptyBuffer(base.Buffer):
def __init__(self, parent, name, account):
super(EmptyBuffer, self).__init__(parent=parent)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.emptyPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.account = account
self.buffer.account = account
self.name = name
self.session = None
self.needs_init = True

View File

@@ -1,203 +0,0 @@
# -*- coding: utf-8 -*-
""" Common logic to all buffers in TWBlue."""
from __future__ import unicode_literals
from builtins import object
import logging
import wx
import output
import config
import sound
import widgetUtils
from pubsub import pub
from wxUI import buffers
log = logging.getLogger("controller.buffers.baseBuffers")
def _items_exist(function):
""" A decorator to execute a function only if the selected buffer contains at least one item."""
def function_(self, *args, **kwargs):
if self.buffer.list.get_count() > 0:
function(self, *args, **kwargs)
return function_
class buffer(object):
""" A basic buffer object. This should be the base class for all other derived buffers."""
def __init__(self, parent=None, function=None, session=None, *args, **kwargs):
"""Inits the main controller for this buffer:
@ parent wx.Treebook object: Container where we will put this buffer.
@ function str or None: function to be called periodically and update items on this buffer.
@ session sessionmanager.session object or None: Session handler for settings, database and data access.
"""
super(buffer, self).__init__()
self.function = function
# Compose_function will be used to render an object on this buffer. Normally, signature is as follows:
# compose_function(item, db, relative_times, show_screen_names=False, session=None)
# Read more about compose functions in twitter/compose.py.
self.compose_function = None
self.args = args
self.kwargs = kwargs
# This will be used as a reference to the wx.Panel object wich stores the buffer GUI.
self.buffer = None
# This should countains the account associated to this buffer.
self.account = ""
# This controls whether the start_stream function should be called when starting the program.
self.needs_init = True
# if this is set to False, the buffer will be ignored on the invisible interface.
self.invisible = False
# Control variable, used to track time of execution for calls to start_stream.
self.execution_time = 0
def clear_list(self):
pass
def get_event(self, ev):
""" Catch key presses in the WX interface and generate the corresponding event names."""
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio"
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url"
elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list"
elif ev.GetKeyCode() == wx.WXK_DELETE: event = "destroy_status"
else:
event = None
ev.Skip()
if event != None:
try:
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 destroy_status(self):
pass
def post_status(self, *args, **kwargs):
pass
def save_positions(self):
try:
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
except AttributeError:
pass
class accountPanel(buffer):
def __init__(self, parent, name, account, account_id):
super(accountPanel, self).__init__(parent, None, name)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.accountPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.session = None
self.needs_init = False
self.account = account
self.buffer.account = account
self.name = name
self.account_id = account_id
def setup_account(self):
widgetUtils.connect_event(self.buffer, widgetUtils.CHECKBOX, self.autostart, menuitem=self.buffer.autostart_account)
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(False)
else:
self.buffer.change_autostart(True)
if not hasattr(self, "logged"):
self.buffer.change_login(login=False)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.logout)
else:
self.buffer.change_login(login=True)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.login)
def login(self, *args, **kwargs):
del self.logged
self.setup_account()
pub.sendMessage("login", session_id=self.account_id)
def logout(self, *args, **kwargs):
self.logged = False
self.setup_account()
pub.sendMessage("logout", session_id=self.account_id)
def autostart(self, *args, **kwargs):
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(True)
config.app["sessions"]["ignored_sessions"].remove(self.account_id)
else:
self.buffer.change_autostart(False)
config.app["sessions"]["ignored_sessions"].append(self.account_id)
config.app.write()
class emptyPanel(buffer):
def __init__(self, parent, name, account):
super(emptyPanel, self).__init__(parent=parent)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.emptyPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.account = account
self.buffer.account = account
self.name = name
self.session = None
self.needs_init = True

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from .base import BaseBuffer
from .directMessages import DirectMessagesBuffer, SentDirectMessagesBuffer
from .list import ListBuffer
from .people import PeopleBuffer
from .trends import TrendsBuffer
from .search import SearchBuffer, SearchPeopleBuffer, ConversationBuffer

View File

@@ -0,0 +1,655 @@
# -*- 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 widgetUtils
import arrow
import webbrowser
import output
import config
import sound
import languageHandler
import logging
from audio_services import youtube_utils
from controller.buffers.base import base
from sessions.twitter import compose, utils, reduce
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from tweepy.cursor import Cursor
from pubsub import pub
from sessions.twitter.long_tweets import twishort, tweets
log = logging.getLogger("controller.buffers")
def _tweets_exist(function):
""" A decorator to execute a function only if the selected buffer contains at least one item."""
def function_(self, *args, **kwargs):
if self.buffer.list.get_count() > 0:
function(self, *args, **kwargs)
return function_
class BaseBuffer(base.Buffer):
def __init__(self, parent, function, name, sessionObject, account, sound=None, bufferType=None, compose_func="compose_tweet", *args, **kwargs):
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)
else:
self.buffer = buffers.basePanel(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 "-favorite" in self.name:
self.finished_timeline = False
# Add a compatibility layer for username based timelines from config.
# ToDo: Remove this in some new versions of the client, when user ID timelines become mandatory.
try:
int(self.kwargs["user_id"])
except ValueError:
self.is_screen_name = True
self.kwargs["screen_name"] = self.kwargs["user_id"]
self.kwargs.pop("user_id")
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=_(u"Home"), mentions=_(u"Mentions"), direct_messages=_(u"Direct messages"), sent_direct_messages=_(u"Sent direct messages"), sent_tweets=_(u"Sent tweets"), favourites=_(u"Likes"), followers=_(u"Followers"), friends=_(u"Friends"), blocked=_(u"Blocked users"), muted=_(u"Muted users"))
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 "-favorite" in self.name:
return _(u"{username}'s likes").format(username=self.username,)
elif "-followers" in self.name:
return _(u"{username}'s followers").format(username=self.username,)
elif "-friends" in self.name:
return _(u"{username}'s friends").format(username=self.username,)
log.error("Error getting name for buffer %s" % (self.name,))
return _(u"Unknown buffer")
def post_status(self, *args, **kwargs):
item = None
title = _(u"Tweet")
caption = _(u"Write the tweet here")
tweet = messages.tweet(self.session, title, caption, "")
if tweet.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = tweet.message.long_tweet.GetValue()
config.app.write()
text = tweet.message.get_text()
if len(text) > 280 and tweet.message.get("long_tweet") == True:
if not hasattr(tweet, "attachments"):
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text)
else:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1)
if not hasattr(tweet, "attachments") or len(tweet.attachments) == 0:
item = self.session.api_call(call_name="update_status", status=text, _sound="tweet_send.ogg", tweet_mode="extended")
else:
call_threaded(self.post_with_media, text=text, attachments=tweet.attachments)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
if hasattr(tweet.message, "destroy"): tweet.message.destroy()
self.session.settings.write()
def post_with_media(self, text, attachments):
media_ids = []
for i in attachments:
img = self.session.twitter.media_upload(i["file"])
self.session.twitter.create_media_metadata(media_id=img.media_id, alt_text=i["description"])
media_ids.append(img.media_id)
item = self.session.twitter.update_status(status=text, media_ids=media_ids)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
def get_formatted_message(self):
if self.type == "dm" or self.name == "direct_messages":
return self.compose_function(self.get_right_tweet(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)[1]
return self.get_message()
def get_message(self):
tweet = self.get_right_tweet()
return " ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session))
def get_full_tweet(self):
tweet = self.get_right_tweet()
tweetsList = []
tweet_id = tweet.id
message = None
if hasattr(tweet, "message"):
message = tweet.message
try:
tweet = self.session.twitter.get_status(id=tweet_id, include_ext_alt_text=True, tweet_mode="extended")
tweet.full_text = utils.expand_urls(tweet.full_text, tweet.entities)
except TweepyException as e:
utils.twitter_error(e)
return
if message != None:
tweet.message = message
l = tweets.is_long(tweet)
while l != False:
tweetsList.append(tweet)
try:
tweet = self.session.twitter.get_status(id=l, include_ext_alt_text=True, tweet_mode="extended")
tweet.full_text = utils.expand_urls(tweet.full_text, tweet.entities)
except TweepyException as e:
utils.twitter_error(e)
return
l = tweets.is_long(tweet)
if l == False:
tweetsList.append(tweet)
return (tweet, tweetsList)
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
# starts stream every 3 minutes.
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))
if self.name != "direct_messages":
val = self.session.call_paged(self.function, *self.args, **self.kwargs)
else:
# 50 results are allowed per API call, so let's assume max value can be 50.
# reference: https://developer.twitter.com/en/docs/twitter-api/v1/direct-messages/sending-and-receiving/api-reference/list-events
if self.session.settings["general"]["max_tweets_per_call"] > 50:
count = 50
else:
count = self.session.settings["general"]["max_tweets_per_call"]
# try to retrieve the cursor for the current buffer.
try:
val = getattr(self.session.twitter, self.function)(return_cursors=True, count=count, *self.args, **self.kwargs)
if type(val) == tuple:
val, cursor = val
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in val]
val = results
val.reverse()
log.debug("Retrieved %d items from the cursored search on function %s." %(len(val), self.function))
user_ids = [item.message_create["sender_id"] for item in val]
self.session.save_users(user_ids)
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
number_of_items = self.session.order_buffer(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
if "-timeline" in self.name:
self.username = val[0].user.screen_name
elif "-favorite" in self.name:
self.username = self.session.api_call("get_user", **self.kwargs).screen_name
self.finished_timeline = True
if number_of_items > 0 and self.name != "sent_tweets" 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:
tweet = self.session.db[self.name][-1]
else:
tweet = self.session.db[self.name][0]
output.speak(_(u"New tweet in {0}").format(self.get_buffer_name()))
output.speak(" ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)))
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_(u"{0} new tweets in {1}.").format(number_of_items, self.get_buffer_name()))
def get_more_items(self):
elements = []
if self.session.settings["general"]["reverse_timelines"] == False:
last_id = self.session.db[self.name][0].id
else:
last_id = self.session.db[self.name][-1].id
try:
items = getattr(self.session.twitter, self.function)(max_id=last_id, count=self.session.settings["general"]["max_tweets_per_call"], *self.args, **self.kwargs)
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
if items == None:
return
items_db = self.session.db[self.name]
self.session.add_users_from_results(items)
for i in items:
if utils.is_allowed(i, self.session.settings, self.name) == True and utils.find_item(i, self.session.db[self.name]) == None:
i = reduce.reduce_tweet(i)
i = self.session.check_quoted_status(i)
i = self.session.check_long_tweet(i)
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:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
else:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
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.name[:-9] in self.session.settings["other_buffers"]["timelines"]:
self.session.settings["other_buffers"]["timelines"].remove(self.name[:-9])
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 "favorite" in self.name:
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-9] in self.session.settings["other_buffers"]["favourites_timelines"]:
self.session.settings["other_buffers"]["favourites_timelines"].remove(self.name[:-9])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
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 remove_tweet(self, id):
if type(self.session.db[self.name]) == dict: return
items = self.session.db[self.name]
for i in range(0, len(items)):
if items[i].id == id:
items.pop(i)
self.remove_item(i)
self.session.db[self.name] = items
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,))
if self.buffer.list.get_count() == 0:
for i in list_to_use:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
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:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
else:
items = list_to_use[0:number_of_items]
items.reverse()
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
def add_new_item(self, item):
tweet = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
if self.session.settings["general"]["reverse_timelines"] == False:
self.buffer.list.insert_item(False, *tweet)
else:
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...")
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.tweet)
# if self.type == "baseBuffer":
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)
def show_menu(self, ev, pos=0, *args, **kwargs):
if self.buffer.list.get_count() == 0: return
if self.name == "sent_tweets" or self.name == "direct_messages":
menu = menus.sentPanelMenu()
elif self.name == "direct_messages":
menu = menus.dmPanelMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.send_message, menuitem=menu.reply)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
else:
menu = menus.basePanelMenu()
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.retweet)
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, ev.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_tweet(self):
if hasattr(self.session.db[self.name][self.buffer.list.get_selected()], "retweeted_status"):
tweet = self.session.db[self.name][self.buffer.list.get_selected()].retweeted_status
else:
tweet = self.session.db[self.name][self.buffer.list.get_selected()]
return tweet
def get_right_tweet(self):
tweet = self.session.db[self.name][self.buffer.list.get_selected()]
return tweet
@_tweets_exist
def reply(self, *args, **kwargs):
tweet = self.get_right_tweet()
user = self.session.get_user(tweet.user)
screen_name = user.screen_name
id = tweet.id
twishort_enabled = hasattr(tweet, "twishort")
users = utils.get_all_mentioned(tweet, self.session.db, field="screen_name")
ids = utils.get_all_mentioned(tweet, self.session.db, field="id")
# Build the window title
if len(users) < 1:
title=_("Reply to {arg0}").format(arg0=screen_name)
else:
title=_("Reply")
message = messages.reply(self.session, title, _(u"Reply to %s") % (screen_name,), "", users=users, ids=ids)
if message.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue()
if len(users) > 0:
config.app["app-settings"]["mention_all"] = message.message.mentionAll.GetValue()
config.app.write()
params = {"_sound": "reply_send.ogg", "in_reply_to_status_id": id, "tweet_mode": "extended"}
text = message.message.get_text()
if twishort_enabled == False:
excluded_ids = message.get_ids()
params["exclude_reply_user_ids"] =excluded_ids
params["auto_populate_reply_metadata"] =True
else:
mentioned_people = message.get_people()
text = "@"+screen_name+" "+mentioned_people+u" "+text
if len(text) > 280 and message.message.get("long_tweet") == True:
if message.image == None:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text)
else:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1)
params["status"] = text
if message.image == None:
params["call_name"] = "update_status"
else:
params["call_name"] = "update_status_with_media"
params["media"] = message.file
item = self.session.api_call(**params)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
if hasattr(message.message, "destroy"): message.message.destroy()
self.session.settings.write()
@_tweets_exist
def send_message(self, *args, **kwargs):
tweet = self.get_right_tweet()
if self.type == "dm":
screen_name = self.session.get_user(tweet.message_create["sender_id"]).screen_name
users = [screen_name]
elif self.type == "people":
screen_name = tweet.screen_name
users = [screen_name]
else:
screen_name = self.session.get_user(tweet.user).screen_name
users = utils.get_all_users(tweet, self.session)
dm = messages.dm(self.session, _(u"Direct message to %s") % (screen_name,), _(u"New direct message"), users)
if dm.message.get_response() == widgetUtils.OK:
screen_name = dm.message.get("cb")
user = self.session.get_user_by_screen_name(screen_name)
recipient_id = user
text = dm.message.get_text()
val = self.session.api_call(call_name="send_direct_message", recipient_id=recipient_id, text=text)
if val != None:
sent_dms = self.session.db["sent_direct_messages"]
if self.session.settings["general"]["reverse_timelines"] == False:
sent_dms.append(val)
else:
sent_dms.insert(0, val)
self.session.db["sent_direct_messages"] = sent_dms
pub.sendMessage("sent-dm", data=val, user=self.session.db["user_name"])
if hasattr(dm.message, "destroy"): dm.message.destroy()
@_tweets_exist
def share_item(self, *args, **kwargs):
tweet = self.get_right_tweet()
id = tweet.id
if self.session.settings["general"]["retweet_mode"] == "ask":
answer = commonMessageDialogs.retweet_question(self.buffer)
if answer == widgetUtils.YES:
self._retweet_with_comment(tweet, id)
elif answer == widgetUtils.NO:
self._direct_retweet(id)
elif self.session.settings["general"]["retweet_mode"] == "direct":
self._direct_retweet(id)
else:
self._retweet_with_comment(tweet, id)
def _retweet_with_comment(self, tweet, id, comment=''):
# If quoting a retweet, let's quote the original tweet instead the retweet.
if hasattr(tweet, "retweeted_status"):
tweet = tweet.retweeted_status
if hasattr(tweet, "full_text"):
comments = tweet.full_text
else:
comments = tweet.text
retweet = messages.tweet(self.session, _(u"Quote"), _(u"Add your comment to the tweet"), u"“@%s: %s" % (self.session.get_user(tweet.user).screen_name, comments), max=256, messageType="retweet")
if comment != '':
retweet.message.set_text(comment)
if retweet.message.get_response() == widgetUtils.OK:
text = retweet.message.get_text()
text = text+" https://twitter.com/{0}/status/{1}".format(self.session.get_user(tweet.user).screen_name, id)
if retweet.image == None:
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
item = self.session.api_call(call_name="update_status", _sound="retweet_send.ogg", status=text, in_reply_to_status_id=id, tweet_mode="extended")
# if item != None:
# new_item = self.session.twitter.get_status(id=item.id, include_ext_alt_text=True, tweet_mode="extended")
# pub.sendMessage("sent-tweet", data=new_item, user=self.session.db["user_name"])
else:
call_threaded(self.session.api_call, call_name="update_status", _sound="retweet_send.ogg", status=text, media=retweet.image)
if hasattr(retweet.message, "destroy"): retweet.message.destroy()
def _direct_retweet(self, id):
item = self.session.api_call(call_name="retweet", _sound="retweet_send.ogg", id=id)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# Retweets are returned as non-extended tweets, so let's get the object as extended
# just before sending the event message. See https://github.com/manuelcortez/TWBlue/issues/253
# item = self.session.twitter.get_status(id=item.id, include_ext_alt_text=True, tweet_mode="extended")
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
def onFocus(self, *args, **kwargs):
tweet = self.get_tweet()
if platform.system() == "Windows" and 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())
self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 2, ts)
if self.session.settings['sound']['indicate_audio'] and utils.is_audio(tweet):
self.session.sound.play("audio.ogg")
if self.session.settings['sound']['indicate_geo'] and utils.is_geocoded(tweet):
self.session.sound.play("geo.ogg")
if self.session.settings['sound']['indicate_img'] and utils.is_media(tweet):
self.session.sound.play("image.ogg")
def audio(self, url='', *args, **kwargs):
if sound.URLPlayer.player.is_playing():
return sound.URLPlayer.stop_audio()
tweet = self.get_tweet()
if tweet == None: return
urls = utils.find_urls(tweet, twitter_media=True)
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.")
# @_tweets_exist
def url(self, url='', announce=True, *args, **kwargs):
if url == '':
tweet = self.get_tweet()
urls = utils.find_urls(tweet)
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()
@_tweets_exist
def destroy_status(self, *args, **kwargs):
index = self.buffer.list.get_selected()
if self.type == "events" or self.type == "people" or self.type == "empty" or self.type == "account": return
answer = commonMessageDialogs.delete_tweet_dialog(None)
if answer == widgetUtils.YES:
items = self.session.db[self.name]
try:
if self.name == "direct_messages" or self.name == "sent_direct_messages":
self.session.twitter.delete_direct_message(id=self.get_right_tweet().id)
items.pop(index)
else:
self.session.twitter.destroy_status(id=self.get_right_tweet().id)
items.pop(index)
self.buffer.list.remove_item(index)
except TweepyException:
self.session.sound.play("error.ogg")
self.session.db[self.name] = items
@_tweets_exist
def user_details(self):
tweet = self.get_right_tweet()
if self.type == "dm":
users = [self.session.get_user(tweet.message_create["sender_id"]).screen_name]
elif self.type == "people":
users = [tweet.screen_name]
else:
users = utils.get_all_users(tweet, self.session)
dlg = dialogs.utils.selectUserDialog(title=_(u"User details"), users=users)
if dlg.get_response() == widgetUtils.OK:
user.profileController(session=self.session, user=dlg.get_user())
if hasattr(dlg, "destroy"): dlg.destroy()
def get_quoted_tweet(self, tweet):
quoted_tweet = self.session.twitter.get_status(id=tweet.id)
quoted_tweet.text = utils.find_urls_in_text(quoted_tweet.text, quoted_tweet.entities)
l = tweets.is_long(quoted_tweet)
id = tweets.get_id(l)
original_tweet = self.session.twitter.get_status(id=id)
original_tweet.text = utils.find_urls_in_text(original_tweet.text, original_tweet.entities)
return compose.compose_quoted_tweet(quoted_tweet, original_tweet, self.session.db, self.session.settings["general"]["relative_times"])
def get_item_url(self):
tweet = self.get_tweet()
url = "https://twitter.com/{screen_name}/status/{tweet_id}".format(screen_name=self.session.get_user(tweet.user).screen_name, tweet_id=tweet.id)
return url
def open_in_browser(self, *args, **kwargs):
url = self.get_item_url()
output.speak(_(u"Opening item in web browser..."))
webbrowser.open(url)

View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
import platform
import widgetUtils
import arrow
import webbrowser
import output
import config
import languageHandler
import logging
from controller import messages
from sessions.twitter import compose, utils
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from pubsub import pub
from . import base
log = logging.getLogger("controller.buffers.twitter.dmBuffer")
class DirectMessagesBuffer(base.BaseBuffer):
def get_more_items(self):
# 50 results are allowed per API call, so let's assume max value can be 50.
# reference: https://developer.twitter.com/en/docs/twitter-api/v1/direct-messages/sending-and-receiving/api-reference/list-events
if self.session.settings["general"]["max_tweets_per_call"] > 50:
count = 50
else:
count = self.session.settings["general"]["max_tweets_per_call"]
total = 0
# try to retrieve the cursor for the current buffer.
cursor = self.session.db["cursors"].get(self.name)
try:
items = getattr(self.session.twitter, self.function)(return_cursors=True, cursor=cursor, count=count, *self.args, **self.kwargs)
if type(items) == tuple:
items, cursor = items
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in items]
items = results
log.debug("Retrieved %d items for cursored search in function %s" % (len(items), self.function))
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
if items == None:
return
sent = []
received = []
sent_dms = self.session.db["sent_direct_messages"]
received_dms = self.session.db["direct_messages"]
for i in items:
if int(i.message_create["sender_id"]) == self.session.db["user_id"]:
if self.session.settings["general"]["reverse_timelines"] == False:
sent_dms.insert(0, i)
sent.append(i)
else:
sent_dms.append(i)
sent.insert(0, i)
else:
if self.session.settings["general"]["reverse_timelines"] == False:
received_dms.insert(0, i)
received.append(i)
else:
received_dms.append(i)
received.insert(0, i)
total = total+1
self.session.db["direct_messages"] = received_dms
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"])
selected = self.buffer.list.get_selected()
if self.session.settings["general"]["reverse_timelines"] == True:
for i in received:
if int(i.message_create["sender_id"]) == self.session.db["user_id"]:
continue
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
self.buffer.list.select_item(selected)
else:
for i in received:
if int(i.message_create["sender_id"]) == self.session.db["user_id"]:
continue
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
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(self.session, _(u"Mention"), _(u"Mention to %s") % (screen_name,), "@%s " % (screen_name,), [screen_name,])
if message.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue()
config.app.write()
if message.image == None:
item = self.session.api_call(call_name="update_status", _sound="reply_send.ogg", status=message.message.get_text(), tweet_mode="extended")
if item != None:
pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
else:
call_threaded(self.session.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", status=message.message.get_text(), media=message.file)
if hasattr(message.message, "destroy"): message.message.destroy()
def onFocus(self, *args, **kwargs):
tweet = self.get_tweet()
if platform.system() == "Windows" and self.session.settings["general"]["relative_times"] == True:
# fix this:
original_date = arrow.get(int(tweet.created_timestamp))
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(tweet):
self.session.sound.play("audio.ogg")
if self.session.settings['sound']['indicate_img'] and utils.is_media(tweet):
self.session.sound.play("image.ogg")
def clear_list(self):
dlg = commonMessageDialogs.clear_list()
if dlg == widgetUtils.YES:
self.session.db[self.name] = []
self.buffer.list.clear()
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:
tweet = self.session.db[self.name][-1]
else:
tweet = self.session.db[self.name][0]
output.speak(_(u"New direct message"))
output.speak(" ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)))
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_(u"{0} new direct messages.").format(number_of_items,))
def open_in_browser(self, *args, **kwargs):
output.speak(_(u"This action is not supported in the buffer yet."))
class SentDirectMessagesBuffer(DirectMessagesBuffer):
def __init__(self, *args, **kwargs):
super(SentDirectMessagesBuffer, self).__init__(*args, **kwargs)
if ("sent_direct_messages" in self.session.db) == False:
self.session.db["sent_direct_messages"] = []
def get_more_items(self):
output.speak(_(u"Getting more items cannot be done in this buffer. Use the direct messages buffer instead."))
def start_stream(self, *args, **kwargs):
pass
def put_more_items(self, items):
if self.session.settings["general"]["reverse_timelines"] == True:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
else:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)

View File

@@ -0,0 +1,44 @@
# -*- 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 . import base
log = logging.getLogger("controller.buffers.twitter.listBuffer")
class ListBuffer(base.BaseBuffer):
def __init__(self, parent, function, name, sessionObject, account, sound=None, bufferType=None, list_id=None, *args, **kwargs):
super(ListBuffer, self).__init__(parent, function, name, sessionObject, account, sound=None, bufferType=None, *args, **kwargs)
self.users = []
self.list_id = list_id
self.kwargs["list_id"] = list_id
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
self.get_user_ids()
super(ListBuffer, self).start_stream(mandatory, play_sound, avoid_autoreading)
def get_user_ids(self):
for i in Cursor(self.session.twitter.get_list_members, list_id=self.list_id, include_entities=False, skip_status=True, count=5000).items():
if i.id not in self.users:
self.users.append(i.id)
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-5] in self.session.settings["other_buffers"]["lists"]:
self.session.settings["other_buffers"]["lists"].remove(self.name[:-5])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
return True
elif dlg == widgetUtils.NO:
return False

View File

@@ -0,0 +1,258 @@
# -*- 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
import config
import logging
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from pubsub import pub
from sessions.twitter import compose
from . import base
log = logging.getLogger("controller.buffers.twitter.peopleBuffer")
def _tweets_exist(function):
""" A decorator to execute a function only if the selected buffer contains at least one item."""
def function_(self, *args, **kwargs):
if self.buffer.list.get_count() > 0:
function(self, *args, **kwargs)
return function_
class PeopleBuffer(base.BaseBuffer):
def __init__(self, parent, function, name, sessionObject, account, bufferType=None, *args, **kwargs):
super(PeopleBuffer, self).__init__(parent, function, name, sessionObject, account, bufferType="peoplePanel", *args, **kwargs)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.compose_function = compose.compose_followers_list
log.debug("Compose_function: %s" % (self.compose_function,))
self.get_tweet = self.get_right_tweet
self.url = self.interact
if "-followers" in self.name or "-friends" in self.name:
self.finished_timeline = False
# Add a compatibility layer for username based timelines from config.
# ToDo: Remove this in some new versions of the client, when user ID timelines become mandatory.
try:
int(self.kwargs["user_id"])
except ValueError:
self.is_screen_name = True
self.kwargs["screen_name"] = self.kwargs["user_id"]
self.kwargs.pop("user_id")
def remove_buffer(self, force=True):
if "-followers" in self.name:
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-10] in self.session.settings["other_buffers"]["followers_timelines"]:
self.session.settings["other_buffers"]["followers_timelines"].remove(self.name[:-10])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
return True
elif dlg == widgetUtils.NO:
return False
elif "-friends" in self.name:
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-8] in self.session.settings["other_buffers"]["friends_timelines"]:
self.session.settings["other_buffers"]["friends_timelines"].remove(self.name[:-8])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
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 onFocus(self, ev):
pass
def get_message(self):
return " ".join(self.compose_function(self.get_tweet(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session))
def delete_item(self): pass
@_tweets_exist
def reply(self, *args, **kwargs):
tweet = self.get_right_tweet()
screen_name = tweet.screen_name
message = messages.reply(self.session, _(u"Mention"), _(u"Mention to %s") % (screen_name,), "@%s " % (screen_name,), [screen_name,])
if message.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue()
config.app.write()
if message.image == None:
item = self.session.api_call(call_name="update_status", _sound="reply_send.ogg", status=message.message.get_text(), tweet_mode="extended")
if item != None:
pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
else:
call_threaded(self.session.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", status=message.message.get_text(), media=message.file)
if hasattr(message.message, "destroy"): message.message.destroy()
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
# starts stream every 3 minutes.
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 %s buffer, %s account" % (self.name, self.account,))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
try:
val = getattr(self.session.twitter, self.function)(return_cursors=True, count=self.session.settings["general"]["max_tweets_per_call"], *self.args, **self.kwargs)
if type(val) == tuple:
val, cursor = val
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in val]
val = results
val.reverse()
log.debug("Retrieved %d items from cursored search in function %s" % (len(val), self.function))
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
number_of_items = self.session.order_people(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
self.username = self.session.api_call("get_user", **self.kwargs).screen_name
self.finished_timeline = True
if number_of_items > 0 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):
try:
cursor = self.session.db["cursors"].get(self.name)
items = getattr(self.session.twitter, self.function)(return_cursors=True, users=True, cursor=cursor, count=self.session.settings["general"]["max_tweets_per_call"], *self.args, **self.kwargs)
if type(items) == tuple:
items, cursor = items
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in items]
items = results
log.debug("Retrieved %d items from cursored search in function %s" % (len(items), self.function))
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
if items == None:
return
items_db = self.session.db[self.name]
for i in items:
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
selected = self.buffer.list.get_selected()
if self.session.settings["general"]["reverse_timelines"] == True:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session)
self.buffer.list.insert_item(True, *tweet)
self.buffer.list.select_item(selected)
else:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session)
self.buffer.list.insert_item(True, *tweet)
output.speak(_(u"%s items retrieved") % (len(items)), True)
def put_items_on_list(self, number_of_items):
log.debug("The list contains %d items" % (self.buffer.list.get_count(),))
# log.debug("Putting %d items on the list..." % (number_of_items,))
if self.buffer.list.get_count() == 0:
for i in self.session.db[self.name]:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session)
self.buffer.list.insert_item(False, *tweet)
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
# self.buffer.set_list_position()
elif self.buffer.list.get_count() > 0:
if self.session.settings["general"]["reverse_timelines"] == False:
for i in self.session.db[self.name][len(self.session.db[self.name])-number_of_items:]:
tweet = self.compose_function(i, self.session.db)
self.buffer.list.insert_item(False, *tweet)
else:
items = self.session.db[self.name][0:number_of_items]
items.reverse()
for i in items:
tweet = self.compose_function(i, self.session.db)
self.buffer.list.insert_item(True, *tweet)
log.debug("now the list contains %d items" % (self.buffer.list.get_count(),))
def get_right_tweet(self):
tweet = self.session.db[self.name][self.buffer.list.get_selected()]
return tweet
def add_new_item(self, item):
tweet = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session)
if self.session.settings["general"]["reverse_timelines"] == False:
self.buffer.list.insert_item(False, *tweet)
else:
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))
def clear_list(self):
dlg = commonMessageDialogs.clear_list()
if dlg == widgetUtils.YES:
self.session.db[self.name] = []
self.session.db["cursors"][self.name] = -1
self.buffer.list.clear()
def interact(self):
user.profileController(self.session, user=self.get_right_tweet().screen_name)
def show_menu(self, ev, pos=0, *args, **kwargs):
menu = menus.peoplePanelMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.send_message, menuitem=menu.reply)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.details, menuitem=menu.details)
# widgetUtils.connect_event(menu, widgetUtils.MENU, self.lists, menuitem=menu.lists)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
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, ev.GetPosition())
def details(self, *args, **kwargs):
pub.sendMessage("execute-action", action="user_details")
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:
tweet = self.session.db[self.name][-1]
else:
tweet = self.session.db[self.name][0]
output.speak(" ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)))
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_(u"{0} new followers.").format(number_of_items))
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

View File

@@ -0,0 +1,123 @@
# -*- 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 . import base, people
log = logging.getLogger("controller.buffers.twitter.searchBuffer")
class SearchBuffer(base.BaseBuffer):
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]:
self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11])
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
class SearchPeopleBuffer(people.PeopleBuffer):
""" This is identical to a normal peopleBufferController, except that uses the page parameter instead of a cursor."""
def __init__(self, parent, function, name, sessionObject, account, bufferType="peoplePanel", *args, **kwargs):
super(SearchPeopleBuffer, self).__init__(parent, function, name, sessionObject, account, bufferType="peoplePanel", *args, **kwargs)
if ("page" in self.kwargs) == False:
self.page = 1
else:
self.page = self.kwargs.pop("page")
def get_more_items(self, *args, **kwargs):
# Add 1 to the page parameter, put it in kwargs and calls to get_more_items in the parent buffer.
self.page = self.page +1
self.kwargs["page"] = self.page
super(SearchPeopleBuffer, self).get_more_items(*args, **kwargs)
# remove the parameter again to make sure start_stream won't fetch items for this page indefinitely.
self.kwargs.pop("page")
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]:
self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11])
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
class ConversationBuffer(SearchBuffer):
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:
self.execution_time = current_time
results = self.get_replies(self.tweet)
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.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 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
def get_replies(self, tweet):
""" Try to retrieve the whole conversation for the passed object by using a mix between calls to API V1.1 and V2 """
results = []
# If the tweet that starts the conversation is a reply to something else, let's try to get the parent tweet first.
if hasattr(self, "in_reply_to_status_id") and self.tweet.in_reply_to_status_id != None:
try:
tweet2 = self.session.twitter_v2.get_tweet(id=self.tweet.in_reply_to_status_id, user_auth=True, tweet_fields=["conversation_id"])
results.append(tweet2)
except TweepyException as e:
log.exception("There was an error attempting to retrieve a parent tweet for the conversation for {}".format(self.name))
# Now, try to fetch the tweet initiating the conversation in V2 so we can get conversation_id
try:
tweet = self.session.twitter_v2.get_tweet(id=self.tweet.id, user_auth=True, tweet_fields=["conversation_id"])
results.append(tweet.data)
term = "conversation_id:{}".format(tweet.data.conversation_id)
tweets = self.session.twitter_v2.search_recent_tweets(term, user_auth=True, max_results=98)
if tweets.data != None:
results.extend(tweets.data)
except TweepyException as e:
log.exception("There was an error when attempting to retrieve the whole conversation for buffer {}".format(self.buffer.name))
new_results = []
ids = [tweet.id for tweet in results]
try:
results = self.session.twitter.lookup_statuses(ids, include_ext_alt_text=True, tweet_mode="extended")
results.sort(key=lambda x: x.id)
except TweepyException as e:
log.exception("There was an error attempting to retrieve tweets for Twitter API V1.1, in conversation buffer {}".format(self.name))
return []
return results

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
import time
import platform
if platform.system() == "Windows":
import wx
from wxUI import buffers, commonMessageDialogs, menus
from controller import user
elif platform.system() == "Linux":
from gi.repository import Gtk
from gtkUI import buffers, commonMessageDialogs
from controller import messages
import widgetUtils
import output
import logging
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from pubsub import pub
from controller.buffers import base
log = logging.getLogger("controller.buffers.twitter.trends")
class TrendsBuffer(base.Buffer):
def __init__(self, parent, name, sessionObject, account, trendsFor, *args, **kwargs):
super(TrendsBuffer, self).__init__(parent=parent, sessionObject=sessionObject)
self.trendsFor = trendsFor
self.session = sessionObject
self.account = account
self.invisible = True
self.buffer = buffers.trendsPanel(parent, name)
self.buffer.account = account
self.type = self.buffer.type
self.bind_events()
self.sound = "trends_updated.ogg"
self.trends = []
self.name = name
self.buffer.name = name
self.compose_function = self.compose_function_
self.get_formatted_message = self.get_message
self.reply = self.search_topic
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
# starts stream every 3 minutes.
current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory == True:
self.execution_time = current_time
try:
data = self.session.twitter.get_place_trends(id=self.trendsFor)
except TweepyException as err:
log.exception("Error %s" % (str(err)))
if not hasattr(self, "name_"):
self.name_ = data[0]["locations"][0]["name"]
self.trends = data[0]["trends"]
self.put_items_on_the_list()
if 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)
def put_items_on_the_list(self):
selected_item = self.buffer.list.get_selected()
self.buffer.list.clear()
for i in self.trends:
tweet = self.compose_function(i)
self.buffer.list.insert_item(False, *tweet)
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
def compose_function_(self, trend):
return [trend["name"]]
def bind_events(self):
log.debug("Binding events...")
self.buffer.list.list.Bind(wx.EVT_CHAR_HOOK, self.get_event)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.tweet_about_this_trend, self.buffer.tweetTrendBtn)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.tweet)
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)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.search_topic, self.buffer.search_topic)
def get_message(self):
return self.compose_function(self.trends[self.buffer.list.get_selected()])[0]
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-3] in self.session.settings["other_buffers"]["trending_topic_buffers"]:
self.session.settings["other_buffers"]["trending_topic_buffers"].remove(self.name[:-3])
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
def url(self, *args, **kwargs):
self.tweet_about_this_trend()
def search_topic(self, *args, **kwargs):
topic = self.trends[self.buffer.list.get_selected()]["name"]
pub.sendMessage("search", term=topic)
def show_menu(self, ev, pos=0, *args, **kwargs):
menu = menus.trendsPanelMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.search_topic, menuitem=menu.search_topic)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.tweet_about_this_trend, menuitem=menu.tweetThisTrend)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
if pos != 0:
self.buffer.PopupMenu(menu, pos)
else:
self.buffer.PopupMenu(menu, ev.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 tweet_about_this_trend(self, *args, **kwargs):
if self.buffer.list.get_count() == 0: return
title = _(u"Tweet")
caption = _(u"Write the tweet here")
tweet = messages.tweet(self.session, title, caption, self.get_message()+ " ")
tweet.message.set_cursor_at_end()
if tweet.message.get_response() == widgetUtils.OK:
text = tweet.message.get_text()
if len(text) > 280 and tweet.message.get("long_tweet") == True:
if tweet.image == None:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text)
else:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1)
if tweet.image == None:
call_threaded(self.session.api_call, call_name="update_status", status=text)
else:
call_threaded(self.session.api_call, call_name="update_status_with_media", status=text, media=tweet.image)
if hasattr(tweet.message, "destroy"): tweet.message.destroy()
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 open_in_browser(self, *args, **kwargs):
output.speak(_(u"This action is not supported in the buffer, yet."))

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import object
import widgetUtils import widgetUtils
import output import output
import logging
from wxUI.dialogs import lists from wxUI.dialogs import lists
from twython import TwythonError from tweepy.errors import TweepyException
from sessions.twitter import compose, utils from sessions.twitter import compose, utils
from pubsub import pub from pubsub import pub
log = logging.getLogger("controller.listsController")
class listsController(object): class listsController(object):
def __init__(self, session, user=None): def __init__(self, session, user=None):
super(listsController, self).__init__() super(listsController, self).__init__()
@@ -31,7 +32,7 @@ class listsController(object):
return [compose.compose_list(item) for item in self.session.db["lists"]] return [compose.compose_list(item) for item in self.session.db["lists"]]
def get_user_lists(self, user): def get_user_lists(self, user):
self.lists = self.session.twitter.show_lists(reverse=True, screen_name=user) self.lists = self.session.twitter.lists_all(reverse=True, screen_name=user)
return [compose.compose_list(item) for item in self.lists] return [compose.compose_list(item) for item in self.lists]
def create_list(self, *args, **kwargs): def create_list(self, *args, **kwargs):
@@ -48,8 +49,9 @@ class listsController(object):
new_list = self.session.twitter.create_list(name=name, description=description, mode=mode) new_list = self.session.twitter.create_list(name=name, description=description, mode=mode)
self.session.db["lists"].append(new_list) self.session.db["lists"].append(new_list)
self.dialog.lista.insert_item(False, *compose.compose_list(new_list)) self.dialog.lista.insert_item(False, *compose.compose_list(new_list))
except TwythonError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.status_code, e.msg)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))
dialog.destroy() dialog.destroy()
def edit_list(self, *args, **kwargs): def edit_list(self, *args, **kwargs):
@@ -65,44 +67,48 @@ class listsController(object):
else: else:
mode = "private" mode = "private"
try: try:
self.session.twitter.update_list(list_id=list["id"], name=name, description=description, mode=mode) self.session.twitter.update_list(list_id=list.id, name=name, description=description, mode=mode)
self.session.get_lists() self.session.get_lists()
self.dialog.populate_list(self.get_all_lists(), True) self.dialog.populate_list(self.get_all_lists(), True)
except TwythonError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.error_code, e.msg)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))
dialog.destroy() dialog.destroy()
def remove_list(self, *args, **kwargs): def remove_list(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()]["id"] list = self.session.db["lists"][self.dialog.get_item()].id
if lists.remove_list() == widgetUtils.YES: if lists.remove_list() == widgetUtils.YES:
try: try:
self.session.twitter.delete_list(list_id=list) self.session.twitter.destroy_list(list_id=list)
self.session.db["lists"].pop(self.dialog.get_item()) self.session.db["lists"].pop(self.dialog.get_item())
self.dialog.lista.remove_item(self.dialog.get_item()) self.dialog.lista.remove_item(self.dialog.get_item())
except TwythonError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.error_code, e.msg)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))
def open_list_as_buffer(self, *args, **kwargs): def open_list_as_buffer(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()] 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("create-new-buffer", buffer="list", account=self.session.db["user_name"], create=list.name)
def subscribe(self, *args, **kwargs): def subscribe(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list_id = self.lists[self.dialog.get_item()]["id"] list_id = self.lists[self.dialog.get_item()].id
try: try:
list = self.session.twitter.subscribe_to_list(list_id=list_id) list = self.session.twitter.subscribe_list(list_id=list_id)
item = utils.find_item(list["id"], self.session.db["lists"]) item = utils.find_item(list.id, self.session.db["lists"])
self.session.db["lists"].append(list) self.session.db["lists"].append(list)
except TwythonError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.status_code, e.msg)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))
def unsubscribe(self, *args, **kwargs): def unsubscribe(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list_id = self.lists[self.dialog.get_item()]["id"] list_id = self.lists[self.dialog.get_item()].id
try: try:
list = self.session.twitter.unsubscribe_from_list(list_id=list_id) list = self.session.twitter.unsubscribe_list(list_id=list_id)
self.session.db["lists"].remove(list) self.session.db["lists"].remove(list)
except TwythonError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.status_code, e.msg)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))

View File

@@ -1,11 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import str
from builtins import range
from builtins import object
import platform import platform
system = platform.system() system = platform.system()
import application import application
import wx
import requests import requests
from audio_services import youtube_utils from audio_services import youtube_utils
import arrow import arrow
@@ -26,31 +23,30 @@ elif system == "Linux":
from gtkUI import (view, commonMessageDialogs) from gtkUI import (view, commonMessageDialogs)
from sessions.twitter import utils, compose from sessions.twitter import utils, compose
from sessionmanager import manager, sessionManager from sessionmanager import manager, sessionManager
from controller import buffers
from controller.buffers import baseBuffers, twitterBuffers
from . import messages from . import messages
from . import userAliasController
import sessions import sessions
from sessions.twitter import session as session_ from sessions.twitter import session as session_
from pubsub import pub from pubsub import pub
import sound import sound
import output import output
from twython import TwythonError, TwythonAuthError from tweepy.errors import TweepyException, Forbidden
from mysc.thread_utils import call_threaded from mysc.thread_utils import call_threaded
from mysc.repeating_timer import RepeatingTimer from mysc.repeating_timer import RepeatingTimer
from mysc import restart from mysc import restart
import config import config
import widgetUtils import widgetUtils
import pygeocoder
from pygeolib import GeocoderError
import logging import logging
import webbrowser import webbrowser
from geopy.geocoders import Nominatim
from mysc import localization from mysc import localization
import os import os
import languageHandler import languageHandler
log = logging.getLogger("mainController") log = logging.getLogger("mainController")
geocoder = pygeocoder.Geocoder() geocoder = Nominatim(user_agent="TWBlue")
class Controller(object): class Controller(object):
@@ -130,12 +126,14 @@ class Controller(object):
pub.subscribe(self.update_sent_dms, "sent-dms-updated") pub.subscribe(self.update_sent_dms, "sent-dms-updated")
pub.subscribe(self.more_dms, "more-sent-dms") pub.subscribe(self.more_dms, "more-sent-dms")
pub.subscribe(self.manage_sent_tweets, "sent-tweet") pub.subscribe(self.manage_sent_tweets, "sent-tweet")
pub.subscribe(self.manage_new_tweet, "newTweet")
pub.subscribe(self.manage_friend, "friend") pub.subscribe(self.manage_friend, "friend")
pub.subscribe(self.manage_unfollowing, "unfollowing") pub.subscribe(self.manage_unfollowing, "unfollowing")
pub.subscribe(self.manage_favourite, "favourite") pub.subscribe(self.manage_favourite, "favourite")
pub.subscribe(self.manage_unfavourite, "unfavourite") pub.subscribe(self.manage_unfavourite, "unfavourite")
pub.subscribe(self.manage_blocked_user, "blocked-user") pub.subscribe(self.manage_blocked_user, "blocked-user")
pub.subscribe(self.manage_unblocked_user, "unblocked-user") pub.subscribe(self.manage_unblocked_user, "unblocked-user")
pub.subscribe(self.create_buffer, "createBuffer")
if system == "Windows": if system == "Windows":
pub.subscribe(self.invisible_shorcuts_changed, "invisible-shorcuts-changed") pub.subscribe(self.invisible_shorcuts_changed, "invisible-shorcuts-changed")
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.show_hide, menuitem=self.view.show_hide) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.show_hide, menuitem=self.view.show_hide)
@@ -189,9 +187,11 @@ class Controller(object):
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.report_error, self.view.reportError) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.report_error, self.view.reportError)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_documentation, self.view.doc) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_documentation, self.view.doc)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_changelog, self.view.changelog) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.view_changelog, self.view.changelog)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_alias, self.view.addAlias)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_to_list, self.view.addToList) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.add_to_list, self.view.addToList)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_from_list, self.view.removeFromList) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_from_list, self.view.removeFromList)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.update_buffer, self.view.update_buffer) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.update_buffer, self.view.update_buffer)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.manage_aliases, self.view.manageAliases)
def set_systray_icon(self): def set_systray_icon(self):
self.systrayIcon = sysTrayIcon.SysTrayIcon() self.systrayIcon = sysTrayIcon.SysTrayIcon()
@@ -257,9 +257,9 @@ class Controller(object):
# Connection checker executed each minute. # Connection checker executed each minute.
self.checker_function = RepeatingTimer(60, self.check_connection) self.checker_function = RepeatingTimer(60, self.check_connection)
self.checker_function.start() # self.checker_function.start()
self.save_db = RepeatingTimer(300, self.save_data_in_db) # self.save_db = RepeatingTimer(300, self.save_data_in_db)
self.save_db.start() # self.save_db.start()
log.debug("Setting updates to buffers every %d seconds..." % (60*config.app["app-settings"]["update_period"],)) log.debug("Setting updates to buffers every %d seconds..." % (60*config.app["app-settings"]["update_period"],))
self.update_buffers_function = RepeatingTimer(60*config.app["app-settings"]["update_period"], self.update_buffers) self.update_buffers_function = RepeatingTimer(60*config.app["app-settings"]["update_period"], self.update_buffers)
self.update_buffers_function.start() self.update_buffers_function.start()
@@ -270,15 +270,19 @@ class Controller(object):
if sessions.sessions[i].is_logged == False: continue if sessions.sessions[i].is_logged == False: continue
self.start_buffers(sessions.sessions[i]) self.start_buffers(sessions.sessions[i])
self.set_buffer_positions(sessions.sessions[i]) self.set_buffer_positions(sessions.sessions[i])
sessions.sessions[i].start_streaming()
if config.app["app-settings"]["play_ready_sound"] == True: if config.app["app-settings"]["play_ready_sound"] == True:
sessions.sessions[list(sessions.sessions.keys())[0]].sound.play("ready.ogg") sessions.sessions[list(sessions.sessions.keys())[0]].sound.play("ready.ogg")
if config.app["app-settings"]["speak_ready_msg"] == True: if config.app["app-settings"]["speak_ready_msg"] == True:
output.speak(_(u"Ready")) output.speak(_(u"Ready"))
self.started = True self.started = True
self.streams_checker_function = RepeatingTimer(60, self.check_streams)
self.streams_checker_function.start()
def create_ignored_session_buffer(self, session): def create_ignored_session_buffer(self, session):
self.accounts.append(session.settings["twitter"]["user_name"]) self.accounts.append(session.settings["twitter"]["user_name"])
account = baseBuffers.accountPanel(self.view.nb, session.settings["twitter"]["user_name"], session.settings["twitter"]["user_name"], session.session_id) account = buffers.base.AccountBuffer(self.view.nb, session.settings["twitter"]["user_name"], session.settings["twitter"]["user_name"], session.session_id)
account.logged = False account.logged = False
account.setup_account() account.setup_account()
self.buffers.append(account) self.buffers.append(account)
@@ -292,106 +296,89 @@ class Controller(object):
self.create_buffers(session, False) self.create_buffers(session, False)
self.start_buffers(session) self.start_buffers(session)
def create_buffer(self, buffer_type="baseBuffer", session_type="twitter", buffer_title="", parent_tab=None, start=False, kwargs={}):
log.debug("Creating buffer of type {0} with parent_tab of {2} arguments {1}".format(buffer_type, kwargs, parent_tab))
if not hasattr(buffers, session_type):
raise AttributeError("Session type %s does not exist yet." % (session_type))
available_buffers = getattr(buffers, session_type)
if not hasattr(available_buffers, buffer_type):
raise AttributeError("Specified buffer type does not exist: %s" % (buffer_type,))
buffer = getattr(available_buffers, buffer_type)(**kwargs)
if start:
if kwargs.get("function") == "user_timeline":
try:
buffer.start_stream(play_sound=False)
except ValueError:
commonMessageDialogs.unauthorized()
return
else:
call_threaded(buffer.start_stream)
self.buffers.append(buffer)
if parent_tab == None:
log.debug("Appending buffer {}...".format(buffer,))
self.view.add_buffer(buffer.buffer, buffer_title)
else:
self.view.insert_buffer(buffer.buffer, buffer_title, parent_tab)
log.debug("Inserting buffer {0} into control {1}".format(buffer, parent_tab))
def create_buffers(self, session, createAccounts=True): def create_buffers(self, session, createAccounts=True):
""" Generates buffer objects for an user account. """ Generates buffer objects for an user account.
session SessionObject: a sessionmanager.session.Session Object""" session SessionObject: a sessionmanager.session.Session Object"""
session.get_user_info() session.get_user_info()
if createAccounts == True: if createAccounts == True:
self.accounts.append(session.db["user_name"]) self.accounts.append(session.db["user_name"])
account = baseBuffers.accountPanel(self.view.nb, session.db["user_name"], session.db["user_name"], session.session_id) account = buffers.base.AccountBuffer(self.view.nb, session.db["user_name"], session.db["user_name"], session.session_id)
account.setup_account() account.setup_account()
self.buffers.append(account) self.buffers.append(account)
self.view.add_buffer(account.buffer , name=session.db["user_name"]) self.view.add_buffer(account.buffer , name=session.db["user_name"])
root_position =self.view.search(session.db["user_name"], session.db["user_name"])
for i in session.settings['general']['buffer_order']: for i in session.settings['general']['buffer_order']:
if i == 'home': if i == 'home':
home = twitterBuffers.baseBufferController(self.view.nb, "get_home_timeline", "home_timeline", session, session.db["user_name"], sound="tweet_received.ogg", tweet_mode="extended") pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Home"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="home_timeline", name="home_timeline", sessionObject=session, account=session.db["user_name"], sound="tweet_received.ogg", tweet_mode="extended"))
self.buffers.append(home)
self.view.insert_buffer(home.buffer, name=_(u"Home"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'mentions': elif i == 'mentions':
mentions = twitterBuffers.baseBufferController(self.view.nb, "get_mentions_timeline", "mentions", session, session.db["user_name"], sound="mention_received.ogg", tweet_mode="extended") pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Mentions"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="mentions_timeline", name="mentions", sessionObject=session, account=session.db["user_name"], sound="mention_received.ogg", tweet_mode="extended"))
self.buffers.append(mentions)
self.view.insert_buffer(mentions.buffer, name=_(u"Mentions"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'dm': elif i == 'dm':
dm = twitterBuffers.directMessagesController(self.view.nb, "get_direct_messages", "direct_messages", session, session.db["user_name"], bufferType="dmPanel", compose_func="compose_direct_message", sound="dm_received.ogg", full_text=True, items="events") pub.sendMessage("createBuffer", buffer_type="DirectMessagesBuffer", session_type=session.type, buffer_title=_("Direct messages"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="get_direct_messages", name="direct_messages", sessionObject=session, account=session.db["user_name"], bufferType="dmPanel", compose_func="compose_direct_message", sound="dm_received.ogg"))
self.buffers.append(dm)
self.view.insert_buffer(dm.buffer, name=_(u"Direct messages"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'sent_dm': elif i == 'sent_dm':
sent_dm = twitterBuffers.sentDirectMessagesController(self.view.nb, "", "sent_direct_messages", session, session.db["user_name"], bufferType="dmPanel", compose_func="compose_direct_message") pub.sendMessage("createBuffer", buffer_type="SentDirectMessagesBuffer", session_type=session.type, buffer_title=_("Sent direct messages"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function=None, name="sent_direct_messages", sessionObject=session, account=session.db["user_name"], bufferType="dmPanel", compose_func="compose_direct_message"))
self.buffers.append(sent_dm)
self.view.insert_buffer(sent_dm.buffer, name=_(u"Sent direct messages"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'sent_tweets': elif i == 'sent_tweets':
sent_tweets = twitterBuffers.baseBufferController(self.view.nb, "get_user_timeline", "sent_tweets", session, session.db["user_name"], screen_name=session.db["user_name"], tweet_mode="extended") pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Sent tweets"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="user_timeline", name="sent_tweets", sessionObject=session, account=session.db["user_name"], screen_name=session.db["user_name"], tweet_mode="extended"))
self.buffers.append(sent_tweets)
self.view.insert_buffer(sent_tweets.buffer, name=_(u"Sent tweets"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'favorites': elif i == 'favorites':
favourites = twitterBuffers.baseBufferController(self.view.nb, "get_favorites", "favourites", session, session.db["user_name"], sound="favourite.ogg", tweet_mode="extended") pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Likes"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="get_favorites", name="favourites", sessionObject=session, account=session.db["user_name"], sound="favourite.ogg", tweet_mode="extended"))
self.buffers.append(favourites)
self.view.insert_buffer(favourites.buffer, name=_(u"Likes"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'followers': elif i == 'followers':
followers = twitterBuffers.peopleBufferController(self.view.nb, "get_followers_list", "followers", session, session.db["user_name"], sound="update_followers.ogg", screen_name=session.db["user_name"]) pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Followers"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="get_followers", name="followers", sessionObject=session, account=session.db["user_name"], sound="update_followers.ogg", screen_name=session.db["user_name"]))
self.buffers.append(followers)
self.view.insert_buffer(followers.buffer, name=_(u"Followers"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'friends': elif i == 'friends':
friends = twitterBuffers.peopleBufferController(self.view.nb, "get_friends_list", "friends", session, session.db["user_name"], screen_name=session.db["user_name"]) pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Following"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="get_friends", name="friends", sessionObject=session, account=session.db["user_name"], screen_name=session.db["user_name"]))
self.buffers.append(friends)
self.view.insert_buffer(friends.buffer, name=_(u"Friends"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'blocks': elif i == 'blocks':
blocks = twitterBuffers.peopleBufferController(self.view.nb, "list_blocks", "blocked", session, session.db["user_name"]) pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Blocked users"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="get_blocks", name="blocked", sessionObject=session, account=session.db["user_name"]))
self.buffers.append(blocks)
self.view.insert_buffer(blocks.buffer, name=_(u"Blocked users"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
elif i == 'muted': elif i == 'muted':
muted = twitterBuffers.peopleBufferController(self.view.nb, "list_mutes", "muted", session, session.db["user_name"]) pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Muted users"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, function="get_mutes", name="muted", sessionObject=session, account=session.db["user_name"]))
self.buffers.append(muted) pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, name="timelines", account=session.db["user_name"]))
self.view.insert_buffer(muted.buffer, name=_(u"Muted users"), pos=self.view.search(session.db["user_name"], session.db["user_name"])) timelines_position =self.view.search("timelines", session.db["user_name"])
timelines = baseBuffers.emptyPanel(self.view.nb, "timelines", session.db["user_name"])
self.buffers.append(timelines)
self.view.insert_buffer(timelines.buffer , name=_(u"Timelines"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
for i in session.settings["other_buffers"]["timelines"]: for i in session.settings["other_buffers"]["timelines"]:
tl = twitterBuffers.baseBufferController(self.view.nb, "get_user_timeline", "%s-timeline" % (i,), session, session.db["user_name"], sound="tweet_timeline.ogg", bufferType=None, user_id=i, tweet_mode="extended") 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=self.view.nb, function="user_timeline", name="%s-timeline" % (i,), sessionObject=session, account=session.db["user_name"], sound="tweet_timeline.ogg", bufferType=None, user_id=i, tweet_mode="extended"))
self.buffers.append(tl) pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Likes timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, name="favs_timelines", account=session.db["user_name"]))
self.view.insert_buffer(tl.buffer, name=_(u"Timeline for {}").format(i,), pos=self.view.search("timelines", session.db["user_name"])) favs_timelines_position =self.view.search("favs_timelines", session.db["user_name"])
favs_timelines = baseBuffers.emptyPanel(self.view.nb, "favs_timelines", session.db["user_name"])
self.buffers.append(favs_timelines)
self.view.insert_buffer(favs_timelines.buffer , name=_(u"Likes timelines"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
for i in session.settings["other_buffers"]["favourites_timelines"]: for i in session.settings["other_buffers"]["favourites_timelines"]:
tl = twitterBuffers.baseBufferController(self.view.nb, "get_favorites", "%s-favorite" % (i,), session, session.db["user_name"], bufferType=None, sound="favourites_timeline_updated.ogg", user_id=i, tweet_mode="extended") 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=self.view.nb, function="get_favorites", name="%s-favorite" % (i,), sessionObject=session, account=session.db["user_name"], bufferType=None, sound="favourites_timeline_updated.ogg", user_id=i, tweet_mode="extended"))
self.buffers.append(tl) pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Followers timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, name="followers_timelines", account=session.db["user_name"]))
self.view.insert_buffer(tl.buffer, name=_(u"Likes for {}").format(i,), pos=self.view.search("favs_timelines", session.db["user_name"])) followers_timelines_position =self.view.search("followers_timelines", session.db["user_name"])
followers_timelines = baseBuffers.emptyPanel(self.view.nb, "followers_timelines", session.db["user_name"])
self.buffers.append(followers_timelines)
self.view.insert_buffer(followers_timelines.buffer , name=_(u"Followers' Timelines"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
for i in session.settings["other_buffers"]["followers_timelines"]: for i in session.settings["other_buffers"]["followers_timelines"]:
tl = twitterBuffers.peopleBufferController(self.view.nb, "get_followers_list", "%s-followers" % (i,), session, session.db["user_name"], sound="new_event.ogg", user_id=i) 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=self.view.nb, function="get_followers", name="%s-followers" % (i,), sessionObject=session, account=session.db["user_name"], sound="new_event.ogg", user_id=i))
self.buffers.append(tl) pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Following timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, name="friends_timelines", account=session.db["user_name"]))
self.view.insert_buffer(tl.buffer, name=_(u"Followers for {}").format(i,), pos=self.view.search("followers_timelines", session.db["user_name"])) friends_timelines_position =self.view.search("friends_timelines", session.db["user_name"])
friends_timelines = baseBuffers.emptyPanel(self.view.nb, "friends_timelines", session.db["user_name"])
self.buffers.append(friends_timelines)
self.view.insert_buffer(friends_timelines.buffer , name=_(u"Friends' Timelines"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
for i in session.settings["other_buffers"]["friends_timelines"]: for i in session.settings["other_buffers"]["friends_timelines"]:
tl = twitterBuffers.peopleBufferController(self.view.nb, "get_friends_list", "%s-friends" % (i,), session, session.db["user_name"], sound="new_event.ogg", user_id=i) 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=self.view.nb, function="get_friends", name="%s-friends" % (i,), sessionObject=session, account=session.db["user_name"], sound="new_event.ogg", user_id=i))
self.buffers.append(tl) pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Lists"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, name="lists", account=session.db["user_name"]))
self.view.insert_buffer(tl.buffer, name=_(u"Friends for {}").format(i,), pos=self.view.search("friends_timelines", session.db["user_name"])) lists_position =self.view.search("lists", session.db["user_name"])
lists = baseBuffers.emptyPanel(self.view.nb, "lists", session.db["user_name"])
self.buffers.append(lists)
self.view.insert_buffer(lists.buffer , name=_(u"Lists"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
for i in session.settings["other_buffers"]["lists"]: for i in session.settings["other_buffers"]["lists"]:
tl = twitterBuffers.listBufferController(self.view.nb, "get_list_statuses", "%s-list" % (i,), session, session.db["user_name"], bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), tweet_mode="extended") 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=self.view.nb, function="list_timeline", name="%s-list" % (i,), sessionObject=session, account=session.db["user_name"], bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), tweet_mode="extended"))
session.lists.append(tl) pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Searches"), parent_tab=root_position, start=False, kwargs=dict(parent=self.view.nb, name="searches", account=session.db["user_name"]))
self.buffers.append(tl) searches_position =self.view.search("searches", session.db["user_name"])
self.view.insert_buffer(tl.buffer, name=_(u"List for {}").format(i), pos=self.view.search("lists", session.db["user_name"]))
searches = baseBuffers.emptyPanel(self.view.nb, "searches", session.db["user_name"])
self.buffers.append(searches)
self.view.insert_buffer(searches.buffer , name=_(u"Searches"), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
for i in session.settings["other_buffers"]["tweet_searches"]: for i in session.settings["other_buffers"]["tweet_searches"]:
tl = twitterBuffers.searchBufferController(self.view.nb, "search", "%s-searchterm" % (i,), session, session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", q=i, tweet_mode="extended") 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=self.view.nb, function="search_tweets", name="%s-searchterm" % (i,), sessionObject=session, account=session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", q=i, tweet_mode="extended"))
self.buffers.append(tl)
self.view.insert_buffer(tl.buffer, name=_(u"Search for {}").format(i), pos=self.view.search("searches", session.db["user_name"]))
for i in session.settings["other_buffers"]["trending_topic_buffers"]: for i in session.settings["other_buffers"]["trending_topic_buffers"]:
buffer = twitterBuffers.trendsBufferController(self.view.nb, "%s_tt" % (i,), session, session.db["user_name"], i, sound="trends_updated.ogg") 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=self.view.nb, name="%s_tt" % (i,), sessionObject=session, account=session.db["user_name"], trendsFor=i, sound="trends_updated.ogg"))
buffer.start_stream(play_sound=False)
buffer.searchfunction = self.search
self.buffers.append(buffer)
self.view.insert_buffer(buffer.buffer, name=_(u"Trending topics for %s") % (buffer.name_), pos=self.view.search(session.db["user_name"], session.db["user_name"]))
def set_buffer_positions(self, session): def set_buffer_positions(self, session):
"Sets positions for buffers if values exist in the database." "Sets positions for buffers if values exist in the database."
@@ -430,21 +417,18 @@ class Controller(object):
if dlg.get_response() == widgetUtils.OK and dlg.get("term") != "": if dlg.get_response() == widgetUtils.OK and dlg.get("term") != "":
term = dlg.get("term") term = dlg.get("term")
buffer = self.get_best_buffer() buffer = self.get_best_buffer()
searches_position =self.view.search("searches", buffer.session.db["user_name"])
if dlg.get("tweets") == True: if dlg.get("tweets") == True:
if term not in buffer.session.settings["other_buffers"]["tweet_searches"]: if term not in buffer.session.settings["other_buffers"]["tweet_searches"]:
buffer.session.settings["other_buffers"]["tweet_searches"].append(term) buffer.session.settings["other_buffers"]["tweet_searches"].append(term)
buffer.session.settings.write() buffer.session.settings.write()
args = {"lang": dlg.get_language(), "result_type": dlg.get_result_type()} args = {"lang": dlg.get_language(), "result_type": dlg.get_result_type()}
search = twitterBuffers.searchBufferController(self.view.nb, "search", "%s-searchterm" % (term,), buffer.session, buffer.session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", q=term, tweet_mode="extended", **args) pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=buffer.session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=self.view.nb, function="search_tweets", name="%s-searchterm" % (term,), sessionObject=buffer.session, account=buffer.session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", q=term, tweet_mode="extended", **args))
else: else:
log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,)) log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,))
return return
elif dlg.get("users") == True: elif dlg.get("users") == True:
search = twitterBuffers.searchPeopleBufferController(self.view.nb, "search_users", "%s-searchUser" % (term,), buffer.session, buffer.session.db["user_name"], bufferType=None, sound="search_updated.ogg", q=term) pub.sendMessage("createBuffer", buffer_type="SearchPeopleBuffer", session_type=buffer.session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=self.view.nb, function="search_users", name="%s-searchUser" % (term,), sessionObject=buffer.session, account=buffer.session.db["user_name"], bufferType=None, sound="search_updated.ogg", q=term))
search.start_stream(mandatory=True)
pos=self.view.search("searches", buffer.session.db["user_name"])
self.insert_buffer(search, pos)
self.view.insert_buffer(search.buffer, name=_(u"Search for {}").format(term), pos=pos)
dlg.Destroy() dlg.Destroy()
def find(self, *args, **kwargs): def find(self, *args, **kwargs):
@@ -530,11 +514,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users) dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user() user = dlg.get_user()
@@ -547,11 +531,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users) dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user() user = dlg.get_user()
@@ -561,25 +545,26 @@ class Controller(object):
dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]]) dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]])
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
try: try:
list = buff.session.twitter.add_list_member(list_id=buff.session.db["lists"][dlg.get_item()]["id"], screen_name=user) list = buff.session.twitter.add_list_member(list_id=buff.session.db["lists"][dlg.get_item()].id, screen_name=user)
older_list = utils.find_item(buff.session.db["lists"][dlg.get_item()]["id"], buff.session.db["lists"]) older_list = utils.find_item(buff.session.db["lists"][dlg.get_item()].id, buff.session.db["lists"])
listBuffer = self.search_buffer("%s-list" % (buff.session.db["lists"][dlg.get_item()]["name"].lower()), buff.session.db["user_name"]) listBuffer = self.search_buffer("%s-list" % (buff.session.db["lists"][dlg.get_item()].name.lower()), buff.session.db["user_name"])
if listBuffer != None: listBuffer.get_user_ids() if listBuffer != None: listBuffer.get_user_ids()
buff.session.db["lists"].pop(older_list) buff.session.db["lists"].pop(older_list)
buff.session.db["lists"].append(list) buff.session.db["lists"].append(list)
except TwythonError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.error_code, e.msg)) log.exception("error %s" % (str(e)))
output.speak("error %s" % (str(e)))
def remove_from_list(self, *args, **kwargs): def remove_from_list(self, *args, **kwargs):
buff = self.get_best_buffer() buff = self.get_best_buffer()
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users) dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user() user = dlg.get_user()
@@ -589,14 +574,15 @@ class Controller(object):
dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]]) dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]])
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
try: try:
list = buff.session.twitter.delete_list_member(list_id=buff.session.db["lists"][dlg.get_item()]["id"], screen_name=user) list = buff.session.twitter.remove_list_member(list_id=buff.session.db["lists"][dlg.get_item()].id, screen_name=user)
older_list = utils.find_item(buff.session.db["lists"][dlg.get_item()]["id"], buff.session.db["lists"]) older_list = utils.find_item(buff.session.db["lists"][dlg.get_item()].id, buff.session.db["lists"])
listBuffer = self.search_buffer("%s-list" % (buff.session.db["lists"][dlg.get_item()]["name"].lower()), buff.session.db["user_name"]) listBuffer = self.search_buffer("%s-list" % (buff.session.db["lists"][dlg.get_item()].name.lower()), buff.session.db["user_name"])
if listBuffer != None: listBuffer.get_user_ids() if listBuffer != None: listBuffer.get_user_ids()
buff.session.db["lists"].pop(older_list) buff.session.db["lists"].pop(older_list)
buff.session.db["lists"].append(list) buff.session.db["lists"].append(list)
except TwythonError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.error_code, e.msg)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))
def list_manager(self, *args, **kwargs): def list_manager(self, *args, **kwargs):
s = self.get_best_buffer().session s = self.get_best_buffer().session
@@ -621,6 +607,7 @@ class Controller(object):
if d.needs_restart == True: if d.needs_restart == True:
commonMessageDialogs.needs_restart() commonMessageDialogs.needs_restart()
buff.session.settings.write() buff.session.settings.write()
buff.session.save_persistent_data()
restart.restart_program() restart.restart_program()
def report_error(self, *args, **kwargs): def report_error(self, *args, **kwargs):
@@ -653,15 +640,20 @@ class Controller(object):
log.debug("Saving global configuration...") log.debug("Saving global configuration...")
for item in sessions.sessions: for item in sessions.sessions:
if sessions.sessions[item].logged == False: continue if sessions.sessions[item].logged == False: continue
log.debug("Disconnecting streaming endpoint for session" + sessions.sessions[item].session_id)
sessions.sessions[item].stop_streaming()
log.debug("Disconnecting streams for %s session" % (sessions.sessions[item].session_id,)) log.debug("Disconnecting streams for %s session" % (sessions.sessions[item].session_id,))
sessions.sessions[item].sound.cleaner.cancel() sessions.sessions[item].sound.cleaner.cancel()
log.debug("Shelving database for " + sessions.sessions[item].session_id) log.debug("Saving database for " + sessions.sessions[item].session_id)
sessions.sessions[item].shelve() sessions.sessions[item].save_persistent_data()
if system == "Windows": if system == "Windows":
self.systrayIcon.RemoveIcon() self.systrayIcon.RemoveIcon()
pidpath = os.path.join(os.getenv("temp"), "{}.pid".format(application.name)) pidpath = os.path.join(os.getenv("temp"), "{}.pid".format(application.name))
if os.path.exists(pidpath): if os.path.exists(pidpath):
os.remove(pidpath) os.remove(pidpath)
if hasattr(self, "streams_checker_function"):
log.debug("Stopping stream checker...")
self.streams_checker_function.cancel()
widgetUtils.exit_application() widgetUtils.exit_application()
def follow(self, *args, **kwargs): def follow(self, *args, **kwargs):
@@ -669,11 +661,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users) u = userActionsController.userActionsController(buff, users)
def unfollow(self, *args, **kwargs): def unfollow(self, *args, **kwargs):
@@ -681,11 +673,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "unfollow") u = userActionsController.userActionsController(buff, users, "unfollow")
def mute(self, *args, **kwargs): def mute(self, *args, **kwargs):
@@ -693,11 +685,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "mute") u = userActionsController.userActionsController(buff, users, "mute")
def unmute(self, *args, **kwargs): def unmute(self, *args, **kwargs):
@@ -705,11 +697,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "unmute") u = userActionsController.userActionsController(buff, users, "unmute")
def block(self, *args, **kwargs): def block(self, *args, **kwargs):
@@ -717,11 +709,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "block") u = userActionsController.userActionsController(buff, users, "block")
def unblock(self, *args, **kwargs): def unblock(self, *args, **kwargs):
@@ -729,11 +721,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "unblock") u = userActionsController.userActionsController(buff, users, "unblock")
def report(self, *args, **kwargs): def report(self, *args, **kwargs):
@@ -741,13 +733,38 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "report") u = userActionsController.userActionsController(buff, users, "report")
def add_alias(self, *args, **kwargs):
buff = self.get_best_buffer()
if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet()
if buff.type == "people":
users = [tweet.screen_name]
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.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 = buff.session.get_user_by_screen_name(user)
buff.session.settings["user-aliases"][str(user_id)] = alias
buff.session.settings.write()
output.speak(_("Alias has been set correctly for {}.").format(user))
pub.sendMessage("alias-added")
def manage_aliases(self, *args, **kwargs):
buff = self.get_best_buffer()
alias_controller = userAliasController.userAliasController(buff.session.settings)
def post_tweet(self, event=None): def post_tweet(self, event=None):
buffer = self.get_best_buffer() buffer = self.get_best_buffer()
buffer.post_status() buffer.post_status()
@@ -775,7 +792,7 @@ class Controller(object):
if buffer.type == "dm" or buffer.type == "people" or buffer.type == "events": if buffer.type == "dm" or buffer.type == "people" or buffer.type == "events":
return return
else: else:
id = buffer.get_tweet()["id"] id = buffer.get_tweet().id
call_threaded(buffer.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id) call_threaded(buffer.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id)
def remove_from_favourites(self, *args, **kwargs): def remove_from_favourites(self, *args, **kwargs):
@@ -783,7 +800,7 @@ class Controller(object):
if buffer.type == "dm" or buffer.type == "people" or buffer.type == "events": if buffer.type == "dm" or buffer.type == "people" or buffer.type == "events":
return return
else: else:
id = buffer.get_tweet()["id"] id = buffer.get_tweet().id
call_threaded(buffer.session.api_call, call_name="destroy_favorite", id=id) call_threaded(buffer.session.api_call, call_name="destroy_favorite", id=id)
def toggle_like(self, *args, **kwargs): def toggle_like(self, *args, **kwargs):
@@ -791,9 +808,9 @@ class Controller(object):
if buffer.type == "dm" or buffer.type == "people" or buffer.type == "events": if buffer.type == "dm" or buffer.type == "people" or buffer.type == "events":
return return
else: else:
id = buffer.get_tweet()["id"] id = buffer.get_tweet().id
tweet = buffer.session.twitter.show_status(id=id, include_ext_alt_text=True, tweet_mode="extended") tweet = buffer.session.twitter.get_status(id=id, include_ext_alt_text=True, tweet_mode="extended")
if tweet["favorited"] == False: if tweet.favorited == False:
call_threaded(buffer.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id) call_threaded(buffer.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id)
else: else:
call_threaded(buffer.session.api_call, call_name="destroy_favorite", id=id) call_threaded(buffer.session.api_call, call_name="destroy_favorite", id=id)
@@ -804,16 +821,19 @@ class Controller(object):
return return
elif buffer.type == "baseBuffer" or buffer.type == "favourites_timeline" or buffer.type == "list" or buffer.type == "search": elif buffer.type == "baseBuffer" or buffer.type == "favourites_timeline" or buffer.type == "list" or buffer.type == "search":
tweet, tweetsList = buffer.get_full_tweet() tweet, tweetsList = buffer.get_full_tweet()
msg = messages.viewTweet(tweet, tweetsList, utc_offset=buffer.session.db["utc_offset"]) msg = messages.viewTweet(tweet, tweetsList, utc_offset=buffer.session.db["utc_offset"], item_url=buffer.get_item_url())
elif buffer.type == "dm": elif buffer.type == "dm":
non_tweet = buffer.get_formatted_message() non_tweet = buffer.get_formatted_message()
item = buffer.get_right_tweet() item = buffer.get_right_tweet()
original_date = arrow.get(int(item["created_timestamp"][:-3])) original_date = arrow.get(int(item.created_timestamp))
date = original_date.shift(seconds=buffer.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage()) date = original_date.shift(seconds=buffer.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
msg = messages.viewTweet(non_tweet, [], False, date=date) msg = messages.viewTweet(non_tweet, [], False, date=date)
else: else:
item_url = ""
if hasattr(buffer, "get_item_url"):
item_url = buffer.get_item_url()
non_tweet = buffer.get_formatted_message() non_tweet = buffer.get_formatted_message()
msg = messages.viewTweet(non_tweet, [], False) msg = messages.viewTweet(non_tweet, [], False, item_url=item_url)
def open_in_browser(self, *args, **kwargs): def open_in_browser(self, *args, **kwargs):
buffer = self.get_current_buffer() buffer = self.get_current_buffer()
@@ -828,11 +848,11 @@ class Controller(object):
if not hasattr(buff, "get_right_tweet"): return if not hasattr(buff, "get_right_tweet"): return
tweet = buff.get_right_tweet() tweet = buff.get_right_tweet()
if buff.type == "people": if buff.type == "people":
users = [tweet["screen_name"]] users = [tweet.screen_name]
elif buff.type == "dm": elif buff.type == "dm":
users = [buff.session.get_user(tweet["message_create"]["sender_id"])["screen_name"]] users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else: else:
users = utils.get_all_users(tweet, buff.session.db) users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.userSelection.selectUserDialog(users=users, default=default) dlg = dialogs.userSelection.selectUserDialog(users=users, default=default)
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
usr = utils.if_user_exists(buff.session.twitter, dlg.get_user()) usr = utils.if_user_exists(buff.session.twitter, dlg.get_user())
@@ -840,85 +860,85 @@ class Controller(object):
if usr == dlg.get_user(): if usr == dlg.get_user():
commonMessageDialogs.suspended_user() commonMessageDialogs.suspended_user()
return return
if usr["protected"] == True: if usr.protected == True:
if usr["following"] == False: if usr.following == False:
commonMessageDialogs.no_following() commonMessageDialogs.no_following()
return return
tl_type = dlg.get_action() tl_type = dlg.get_action()
if tl_type == "tweets": if tl_type == "tweets":
if usr["statuses_count"] == 0: if usr.statuses_count == 0:
commonMessageDialogs.no_tweets() commonMessageDialogs.no_tweets()
return return
if usr["id_str"] in buff.session.settings["other_buffers"]["timelines"]: if usr.id_str in buff.session.settings["other_buffers"]["timelines"]:
commonMessageDialogs.timeline_exist() commonMessageDialogs.timeline_exist()
return return
tl = twitterBuffers.baseBufferController(self.view.nb, "get_user_timeline", "%s-timeline" % (usr["id_str"],), buff.session, buff.session.db["user_name"], bufferType=None, sound="tweet_timeline.ogg", user_id=usr["id_str"], tweet_mode="extended") tl = buffers.twitter.BaseBuffer(self.view.nb, "user_timeline", "%s-timeline" % (usr.id_str,), buff.session, buff.session.db["user_name"], bufferType=None, sound="tweet_timeline.ogg", user_id=usr.id_str, tweet_mode="extended")
try: try:
tl.start_stream(play_sound=False) tl.start_stream(play_sound=False)
except TwythonAuthError: except ValueError:
commonMessageDialogs.unauthorized() commonMessageDialogs.unauthorized()
return return
pos=self.view.search("timelines", buff.session.db["user_name"]) pos=self.view.search("timelines", buff.session.db["user_name"])
self.insert_buffer(tl, pos+1) self.insert_buffer(tl, pos+1)
self.view.insert_buffer(tl.buffer, name=_(u"Timeline for {}").format(dlg.get_user()), pos=pos) self.view.insert_buffer(tl.buffer, name=_(u"Timeline for {}").format(dlg.get_user()), pos=pos)
buff.session.settings["other_buffers"]["timelines"].append(usr["id_str"]) buff.session.settings["other_buffers"]["timelines"].append(usr.id_str)
pub.sendMessage("buffer-title-changed", buffer=tl) pub.sendMessage("buffer-title-changed", buffer=tl)
buff.session.sound.play("create_timeline.ogg") buff.session.sound.play("create_timeline.ogg")
elif tl_type == "favourites": elif tl_type == "favourites":
if usr["favourites_count"] == 0: if usr.favourites_count == 0:
commonMessageDialogs.no_favs() commonMessageDialogs.no_favs()
return return
if usr["id_str"] in buff.session.settings["other_buffers"]["favourites_timelines"]: if usr.id_str in buff.session.settings["other_buffers"]["favourites_timelines"]:
commonMessageDialogs.timeline_exist() commonMessageDialogs.timeline_exist()
return return
tl = twitterBuffers.baseBufferController(self.view.nb, "get_favorites", "%s-favorite" % (usr["id_str"],), buff.session, buff.session.db["user_name"], bufferType=None, sound="favourites_timeline_updated.ogg", user_id=usr["id_str"], tweet_mode="extended") tl = buffers.twitter.BaseBuffer(self.view.nb, "get_favorites", "%s-favorite" % (usr.id_str,), buff.session, buff.session.db["user_name"], bufferType=None, sound="favourites_timeline_updated.ogg", user_id=usr.id_str, tweet_mode="extended")
try: try:
tl.start_stream(play_sound=False) tl.start_stream(play_sound=False)
except TwythonAuthError: except ValueError:
commonMessageDialogs.unauthorized() commonMessageDialogs.unauthorized()
return return
pos=self.view.search("favs_timelines", buff.session.db["user_name"]) pos=self.view.search("favs_timelines", buff.session.db["user_name"])
self.insert_buffer(tl, pos+1) self.insert_buffer(tl, pos+1)
self.view.insert_buffer(buffer=tl.buffer, name=_(u"Likes for {}").format(dlg.get_user()), pos=pos) self.view.insert_buffer(buffer=tl.buffer, name=_(u"Likes for {}").format(dlg.get_user()), pos=pos)
buff.session.settings["other_buffers"]["favourites_timelines"].append(usr["id_str"]) buff.session.settings["other_buffers"]["favourites_timelines"].append(usr.id_str)
pub.sendMessage("buffer-title-changed", buffer=buff) pub.sendMessage("buffer-title-changed", buffer=buff)
buff.session.sound.play("create_timeline.ogg") buff.session.sound.play("create_timeline.ogg")
elif tl_type == "followers": elif tl_type == "followers":
if usr["followers_count"] == 0: if usr.followers_count == 0:
commonMessageDialogs.no_followers() commonMessageDialogs.no_followers()
return return
if usr["id_str"] in buff.session.settings["other_buffers"]["followers_timelines"]: if usr.id_str in buff.session.settings["other_buffers"]["followers_timelines"]:
commonMessageDialogs.timeline_exist() commonMessageDialogs.timeline_exist()
return return
tl = twitterBuffers.peopleBufferController(self.view.nb, "get_followers_list", "%s-followers" % (usr["id_str"],), buff.session, buff.session.db["user_name"], sound="new_event.ogg", user_id=usr["id_str"]) tl = buffers.twitter.PeopleBuffer(self.view.nb, "get_followers", "%s-followers" % (usr.id_str,), buff.session, buff.session.db["user_name"], sound="new_event.ogg", user_id=usr.id_str)
try: try:
tl.start_stream(play_sound=False) tl.start_stream(play_sound=False)
except TwythonAuthError: except ValueError:
commonMessageDialogs.unauthorized() commonMessageDialogs.unauthorized()
return return
pos=self.view.search("followers_timelines", buff.session.db["user_name"]) pos=self.view.search("followers_timelines", buff.session.db["user_name"])
self.insert_buffer(tl, pos+1) self.insert_buffer(tl, pos+1)
self.view.insert_buffer(buffer=tl.buffer, name=_(u"Followers for {}").format(dlg.get_user()), pos=pos) self.view.insert_buffer(buffer=tl.buffer, name=_(u"Followers for {}").format(dlg.get_user()), pos=pos)
buff.session.settings["other_buffers"]["followers_timelines"].append(usr["id_str"]) buff.session.settings["other_buffers"]["followers_timelines"].append(usr.id_str)
buff.session.sound.play("create_timeline.ogg") buff.session.sound.play("create_timeline.ogg")
pub.sendMessage("buffer-title-changed", buffer=i) pub.sendMessage("buffer-title-changed", buffer=i)
elif tl_type == "friends": elif tl_type == "friends":
if usr["friends_count"] == 0: if usr.friends_count == 0:
commonMessageDialogs.no_friends() commonMessageDialogs.no_friends()
return return
if usr["id_str"] in buff.session.settings["other_buffers"]["friends_timelines"]: if usr.id_str in buff.session.settings["other_buffers"]["friends_timelines"]:
commonMessageDialogs.timeline_exist() commonMessageDialogs.timeline_exist()
return return
tl = twitterBuffers.peopleBufferController(self.view.nb, "get_friends_list", "%s-friends" % (usr["id_str"],), buff.session, buff.session.db["user_name"], sound="new_event.ogg", user_id=usr["id_str"]) tl = buffers.twitter.PeopleBuffer(self.view.nb, "get_friends", "%s-friends" % (usr.id_str,), buff.session, buff.session.db["user_name"], sound="new_event.ogg", user_id=usr.id_str)
try: try:
tl.start_stream(play_sound=False) tl.start_stream(play_sound=False)
except TwythonAuthError: except ValueError:
commonMessageDialogs.unauthorized() commonMessageDialogs.unauthorized()
return return
pos=self.view.search("friends_timelines", buff.session.db["user_name"]) pos=self.view.search("friends_timelines", buff.session.db["user_name"])
self.insert_buffer(tl, pos+1) self.insert_buffer(tl, pos+1)
self.view.insert_buffer(buffer=tl.buffer, name=_(u"Friends for {}").format(dlg.get_user()), pos=pos) self.view.insert_buffer(buffer=tl.buffer, name=_(u"Friends for {}").format(dlg.get_user()), pos=pos)
buff.session.settings["other_buffers"]["friends_timelines"].append(usr["id_str"]) buff.session.settings["other_buffers"]["friends_timelines"].append(usr.id_str)
buff.session.sound.play("create_timeline.ogg") buff.session.sound.play("create_timeline.ogg")
pub.sendMessage("buffer-title-changed", buffer=i) pub.sendMessage("buffer-title-changed", buffer=i)
else: else:
@@ -927,9 +947,9 @@ class Controller(object):
def open_conversation(self, *args, **kwargs): def open_conversation(self, *args, **kwargs):
buffer = self.get_current_buffer() buffer = self.get_current_buffer()
id = buffer.get_right_tweet()["id_str"] id = buffer.get_right_tweet().id
user = buffer.get_right_tweet()["user"]["screen_name"] user = buffer.session.get_user(buffer.get_right_tweet().user).screen_name
search = twitterBuffers.conversationBufferController(self.view.nb, "search", "%s-searchterm" % (id,), buffer.session, buffer.session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", since_id=id, q="@{0}".format(user,)) search = buffers.twitter.ConversationBuffer(self.view.nb, "search_tweets", "%s-searchterm" % (id,), buffer.session, buffer.session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", since_id=id, q="@{0}".format(user,))
search.tweet = buffer.get_right_tweet() search.tweet = buffer.get_right_tweet()
search.start_stream(start=True) search.start_stream(start=True)
pos=self.view.search("searches", buffer.session.db["user_name"]) pos=self.view.search("searches", buffer.session.db["user_name"])
@@ -956,7 +976,7 @@ class Controller(object):
if trends.dialog.get_response() == widgetUtils.OK: if trends.dialog.get_response() == widgetUtils.OK:
woeid = trends.get_woeid() woeid = trends.get_woeid()
if woeid in buff.session.settings["other_buffers"]["trending_topic_buffers"]: return if woeid in buff.session.settings["other_buffers"]["trending_topic_buffers"]: return
buffer = twitterBuffers.trendsBufferController(self.view.nb, "%s_tt" % (woeid,), buff.session, buff.account, woeid, sound="trends_updated.ogg") buffer = buffers.twitter.TrendsBuffer(self.view.nb, "%s_tt" % (woeid,), buff.session, buff.account, woeid, sound="trends_updated.ogg")
buffer.searchfunction = self.search buffer.searchfunction = self.search
pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"]) pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"])
self.view.insert_buffer(buffer.buffer, name=_(u"Trending topics for %s") % (trends.get_string()), pos=pos) self.view.insert_buffer(buffer.buffer, name=_(u"Trending topics for %s") % (trends.get_string()), pos=pos)
@@ -968,29 +988,27 @@ class Controller(object):
def reverse_geocode(self, event=None): def reverse_geocode(self, event=None):
try: try:
tweet = self.get_current_buffer().get_tweet() tweet = self.get_current_buffer().get_tweet()
if tweet["coordinates"] != None: if tweet.coordinates != None:
x = tweet["coordinates"]["coordinates"][0] x = tweet.coordinates["coordinates"][0]
y = tweet["coordinates"]["coordinates"][1] y = tweet.coordinates["coordinates"][1]
address = geocoder.reverse_geocode(y, x, language = languageHandler.curLang) address = geocoder.reverse("{}, {}".format(y, x), language = languageHandler.curLang)
if event == None: output.speak(address[0].__str__()) if event == None: output.speak(address.address)
else: self.view.show_address(address[0].__str__()) else: self.view.show_address(address.address)
else: else:
output.speak(_(u"There are no coordinates in this tweet")) output.speak(_(u"There are no coordinates in this tweet"))
except GeocoderError:
output.speak(_(u"There are no results for the coordinates in this tweet"))
except ValueError: except ValueError:
output.speak(_(u"Error decoding coordinates. Try again later.")) output.speak(_(u"Error decoding coordinates. Try again later."))
except KeyError: # except KeyError:
pass # pass
except AttributeError: except AttributeError:
pass output.speak(_("Unable to find address in OpenStreetMap."))
def view_reverse_geocode(self, event=None): def view_reverse_geocode(self, event=None):
try: try:
tweet = self.get_current_buffer().get_right_tweet() tweet = self.get_current_buffer().get_right_tweet()
if tweet["coordinates"] != None: if tweet.coordinates != None:
x = tweet["coordinates"]["coordinates"][0] x = tweet.coordinates["coordinates"][0]
y = tweet["coordinates"]["coordinates"][1] y = tweet.coordinates["coordinates"][1]
address = geocoder.reverse_geocode(y, x, language = languageHandler.curLang) address = geocoder.reverse_geocode(y, x, language = languageHandler.curLang)
dlg = commonMessageDialogs.view_geodata(address[0].__str__()) dlg = commonMessageDialogs.view_geodata(address[0].__str__())
else: else:
@@ -1238,14 +1256,17 @@ class Controller(object):
keymap = {} keymap = {}
for i in config.keymap["keymap"]: for i in config.keymap["keymap"]:
if hasattr(self, i): if hasattr(self, i):
if config.keymap["keymap"][i] != "":
keymap[config.keymap["keymap"][i]] = getattr(self, i) keymap[config.keymap["keymap"][i]] = getattr(self, i)
return keymap return keymap
def register_invisible_keyboard_shorcuts(self, keymap): def register_invisible_keyboard_shorcuts(self, keymap):
if config.changed_keymap: if config.changed_keymap:
commonMessageDialogs.changed_keymap() commonMessageDialogs.changed_keymap()
# Make sure we pass a keymap without undefined keystrokes.
new_keymap = {key: keymap[key] for key in keymap.keys() if keymap[key] != ""}
self.keyboard_handler = WXKeyboardHandler(self.view) self.keyboard_handler = WXKeyboardHandler(self.view)
self.keyboard_handler.register_keys(keymap) self.keyboard_handler.register_keys(new_keymap)
def unregister_invisible_keyboard_shorcuts(self, keymap): def unregister_invisible_keyboard_shorcuts(self, keymap):
try: try:
@@ -1272,16 +1293,16 @@ class Controller(object):
def manage_sent_tweets(self, data, user): def manage_sent_tweets(self, data, user):
buffer = self.search_buffer("sent_tweets", user) buffer = self.search_buffer("sent_tweets", user)
if buffer == None: return if buffer == None: return
# if "sent_tweets" not in buffer.session.settings["other_buffers"]["muted_buffers"]:
# self.notify(buffer.session, play_sound=play_sound)
data = buffer.session.check_quoted_status(data) data = buffer.session.check_quoted_status(data)
data = buffer.session.check_long_tweet(data) data = buffer.session.check_long_tweet(data)
if data == False: # Long tweet deleted from twishort. if data == False: # Long tweet deleted from twishort.
return return
items = buffer.session.db[buffer.name]
if buffer.session.settings["general"]["reverse_timelines"] == False: if buffer.session.settings["general"]["reverse_timelines"] == False:
buffer.session.db[buffer.name].append(data) items.append(data)
else: else:
buffer.session.db[buffer.name].insert(0, data) items.insert(0, data)
buffer.session.db[buffer.name] = items
buffer.add_new_item(data) buffer.add_new_item(data)
def manage_friend(self, data, user): def manage_friend(self, data, user):
@@ -1330,7 +1351,10 @@ class Controller(object):
i.start_stream() i.start_stream()
else: else:
i.start_stream(play_sound=False) i.start_stream(play_sound=False)
except TwythonAuthError: except TweepyException as err:
log.exception("Error %s starting buffer %s on account %s, with args %r and kwargs %r." % (str(err), i.name, i.account, i.args, i.kwargs))
# Determine if this error was caused by a block applied to the current user (IE permission errors).
if type(err) == Forbidden:
buff = self.view.search(i.name, i.account) buff = self.view.search(i.name, i.account)
i.remove_buffer(force=True) i.remove_buffer(force=True)
commonMessageDialogs.blocked_timeline() commonMessageDialogs.blocked_timeline()
@@ -1354,48 +1378,44 @@ class Controller(object):
try: try:
if sessions.sessions[i].is_logged == False: continue if sessions.sessions[i].is_logged == False: continue
sessions.sessions[i].check_connection() sessions.sessions[i].check_connection()
except TwythonError: # We shouldn't allow this function to die. except TweepyException: # We shouldn't allow this function to die.
pass pass
def create_new_buffer(self, buffer, account, create): def create_new_buffer(self, buffer, account, create):
buff = self.search_buffer("home_timeline", account) buff = self.search_buffer("home_timeline", account)
if create == True: if create == True:
if buffer == "favourites": if buffer == "favourites":
favourites = twitterBuffers.baseBufferController(self.view.nb, "get_favorites", "favourites", buff.session, buff.session.db["user_name"], tweet_mode="extended") favourites = buffers.twitter.BaseBuffer(self.view.nb, "get_favorites", "favourites", buff.session, buff.session.db["user_name"], tweet_mode="extended")
self.buffers.append(favourites) self.buffers.append(favourites)
self.view.insert_buffer(favourites.buffer, name=_(u"Likes"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"])) self.view.insert_buffer(favourites.buffer, name=_(u"Likes"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"]))
favourites.start_stream(play_sound=False) favourites.start_stream(play_sound=False)
if buffer == "followers": if buffer == "followers":
followers = twitterBuffers.peopleBufferController(self.view.nb, "get_followers_list", "followers", buff.session, buff.session.db["user_name"], screen_name=buff.session.db["user_name"]) followers = buffers.twitter.PeopleBuffer(self.view.nb, "get_followers", "followers", buff.session, buff.session.db["user_name"], screen_name=buff.session.db["user_name"])
self.buffers.append(followers) self.buffers.append(followers)
self.view.insert_buffer(followers.buffer, name=_(u"Followers"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"])) self.view.insert_buffer(followers.buffer, name=_(u"Followers"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"]))
followers.start_stream(play_sound=False) followers.start_stream(play_sound=False)
elif buffer == "friends": elif buffer == "friends":
friends = twitterBuffers.peopleBufferController(self.view.nb, "get_friends_list", "friends", buff.session, buff.session.db["user_name"], screen_name=buff.session.db["user_name"]) friends = buffers.twitter.PeopleBuffer(self.view.nb, "get_friends", "friends", buff.session, buff.session.db["user_name"], screen_name=buff.session.db["user_name"])
self.buffers.append(friends) self.buffers.append(friends)
self.view.insert_buffer(friends.buffer, name=_(u"Friends"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"])) self.view.insert_buffer(friends.buffer, name=_(u"Friends"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"]))
friends.start_stream(play_sound=False) friends.start_stream(play_sound=False)
elif buffer == "blocked": elif buffer == "blocked":
blocks = twitterBuffers.peopleBufferController(self.view.nb, "list_blocks", "blocked", buff.session, buff.session.db["user_name"]) blocks = buffers.twitter.PeopleBuffer(self.view.nb, "get_blocks", "blocked", buff.session, buff.session.db["user_name"])
self.buffers.append(blocks) self.buffers.append(blocks)
self.view.insert_buffer(blocks.buffer, name=_(u"Blocked users"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"])) self.view.insert_buffer(blocks.buffer, name=_(u"Blocked users"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"]))
blocks.start_stream(play_sound=False) blocks.start_stream(play_sound=False)
elif buffer == "muted": elif buffer == "muted":
muted = twitterBuffers.peopleBufferController(self.view.nb, "get_muted_users_list", "muted", buff.session, buff.session.db["user_name"]) muted = buffers.twitter.PeopleBuffer(self.view.nb, "get_mutes", "muted", buff.session, buff.session.db["user_name"])
self.buffers.append(muted) self.buffers.append(muted)
self.view.insert_buffer(muted.buffer, name=_(u"Muted users"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"])) self.view.insert_buffer(muted.buffer, name=_(u"Muted users"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"]))
muted.start_stream(play_sound=False) muted.start_stream(play_sound=False)
elif buffer == "events":
events = twitterBuffers.eventsBufferController(self.view.nb, "events", buff.session, buff.session.db["user_name"], bufferType="dmPanel", screen_name=buff.session.db["user_name"])
self.buffers.append(events)
self.view.insert_buffer(events.buffer, name=_(u"Events"), pos=self.view.search(buff.session.db["user_name"], buff.session.db["user_name"]))
elif create == False: elif create == False:
self.destroy_buffer(buffer, buff.session.db["user_name"]) self.destroy_buffer(buffer, buff.session.db["user_name"])
elif buffer == "list": elif buffer == "list":
if create in buff.session.settings["other_buffers"]["lists"]: if create in buff.session.settings["other_buffers"]["lists"]:
output.speak(_(u"This list is already opened"), True) output.speak(_(u"This list is already opened"), True)
return return
tl = twitterBuffers.listBufferController(self.view.nb, "get_list_statuses", create+"-list", buff.session, buff.session.db["user_name"], bufferType=None, list_id=utils.find_list(create, buff.session.db["lists"]), tweet_mode="extended") tl = buffers.twitter.ListBuffer(self.view.nb, "list_timeline", create+"-list", buff.session, buff.session.db["user_name"], bufferType=None, list_id=utils.find_list(create, buff.session.db["lists"]), tweet_mode="extended")
buff.session.lists.append(tl) buff.session.lists.append(tl)
pos=self.view.search("lists", buff.session.db["user_name"]) pos=self.view.search("lists", buff.session.db["user_name"])
self.insert_buffer(tl, pos) self.insert_buffer(tl, pos)
@@ -1536,7 +1556,10 @@ class Controller(object):
if i.session != None and i.session.is_logged == True: if i.session != None and i.session.is_logged == True:
try: try:
i.start_stream(mandatory=True) i.start_stream(mandatory=True)
except TwythonAuthError: except TweepyException as err:
log.exception("Error %s starting buffer %s on account %s, with args %r and kwargs %r." % (str(err), i.name, i.account, i.args, i.kwargs))
# Determine if this error was caused by a block applied to the current user (IE permission errors).
if type(err) == Forbidden:
buff = self.view.search(i.name, i.account) buff = self.view.search(i.name, i.account)
i.remove_buffer(force=True) i.remove_buffer(force=True)
commonMessageDialogs.blocked_timeline() commonMessageDialogs.blocked_timeline()
@@ -1576,12 +1599,12 @@ class Controller(object):
return return
tweet = buffer.get_tweet() tweet = buffer.get_tweet()
media_list = [] media_list = []
if ("entities" in tweet) and ("media" in tweet["entities"]): 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] [media_list.append(i) for i in tweet.entities["media"] if i not in media_list]
elif "retweeted_status" in tweet and "media" in tweet["retweeted_status"]["entities"]: 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] [media_list.append(i) for i in tweet.retweeted_status.entities["media"] if i not in media_list]
elif "quoted_status" in tweet and "media" in tweet["quoted_status"]["entities"]: 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] [media_list.append(i) for i in tweet.quoted_status.entities["media"] if i not in media_list]
if len(media_list) > 1: if len(media_list) > 1:
image_list = [_(u"Picture {0}").format(i,) for i in range(0, len(media_list))] image_list = [_(u"Picture {0}").format(i,) for i in range(0, len(media_list))]
dialog = dialogs.urlList.urlList(title=_(u"Select the picture")) dialog = dialogs.urlList.urlList(title=_(u"Select the picture"))
@@ -1597,7 +1620,7 @@ class Controller(object):
if buffer.session.settings["mysc"]["ocr_language"] != "": if buffer.session.settings["mysc"]["ocr_language"] != "":
ocr_lang = buffer.session.settings["mysc"]["ocr_language"] ocr_lang = buffer.session.settings["mysc"]["ocr_language"]
else: else:
ocr_lang = ocr.OCRSpace.short_langs.index(tweet["lang"]) ocr_lang = ocr.OCRSpace.short_langs.index(tweet.lang)
ocr_lang = ocr.OCRSpace.OcrLangs[ocr_lang] ocr_lang = ocr.OCRSpace.OcrLangs[ocr_lang]
api = ocr.OCRSpace.OCRSpaceAPI() api = ocr.OCRSpace.OCRSpaceAPI()
try: try:
@@ -1619,4 +1642,28 @@ class Controller(object):
def save_data_in_db(self): def save_data_in_db(self):
for i in sessions.sessions: for i in sessions.sessions:
sessions.sessions[i].shelve() sessions.sessions[i].save_persistent_data()
def manage_new_tweet(self, data, user, _buffers):
sound_to_play = None
for buff in _buffers:
buffer = self.search_buffer(buff, user)
if buffer == None or buffer.session.db["user_name"] != user: return
buffer.add_new_item(data)
if buff == "home_timeline": sound_to_play = "tweet_received.ogg"
elif buff == "mentions": sound_to_play = "mention_received.ogg"
elif buff == "sent_tweets": sound_to_play = "tweet_send.ogg"
elif "timeline" in buff: sound_to_play = "tweet_timeline.ogg"
else: sound_to_play = None
if sound_to_play != None and buff not in buffer.session.settings["other_buffers"]["muted_buffers"]:
self.notify(buffer.session, sound_to_play)
def check_streams(self):
if self.started == False:
return
for i in sessions.sessions:
try:
if sessions.sessions[i].is_logged == False: continue
sessions.sessions[i].check_streams()
except TweepyException: # We shouldn't allow this function to die.
pass

View File

@@ -1,12 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import str
from builtins import range
from builtins import object
import re import re
import platform import platform
from . import attach
import arrow import arrow
import languageHandler import languageHandler
system = platform.system() system = platform.system()
@@ -16,15 +10,16 @@ import url_shortener
import sound import sound
import config import config
from pubsub import pub from pubsub import pub
from twitter_text import parse_tweet
if system == "Windows": if system == "Windows":
from wxUI.dialogs import message, urlList from wxUI.dialogs import message, urlList
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
from extra import translator, SpellChecker, autocompletionUsers from extra import translator, SpellChecker, autocompletionUsers
from extra.AudioUploader import audioUploader from extra.AudioUploader import audioUploader
elif system == "Linux": elif system == "Linux":
from gtkUI.dialogs import message from gtkUI.dialogs import message
from sessions.twitter import utils from sessions.twitter import utils
from . import attach
class basicTweet(object): class basicTweet(object):
""" This class handles the tweet main features. Other classes should derive from this class.""" """ This class handles the tweet main features. Other classes should derive from this class."""
@@ -50,8 +45,11 @@ class basicTweet(object):
dlg = translator.gui.translateDialog() dlg = translator.gui.translateDialog()
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
text_to_translate = self.message.get_text() text_to_translate = self.message.get_text()
dest = [x[0] for x in translator.translator.available_languages()][dlg.get("dest_lang")] language_dict = translator.translator.available_languages()
msg = translator.translator.translate(text=text_to_translate, target=dest) for k in language_dict:
if language_dict[k] == dlg.dest_lang.GetStringSelection():
dst = k
msg = translator.translator.translate(text=text_to_translate, target=dst)
self.message.set_text(msg) self.message.set_text(msg)
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()
@@ -104,9 +102,11 @@ class basicTweet(object):
else: else:
self.message.disable_button("shortenButton") self.message.disable_button("shortenButton")
self.message.disable_button("unshortenButton") self.message.disable_button("unshortenButton")
if self.message.get("long_tweet") == False: if self.message.get("long_tweet") == False and hasattr(self, "max"):
self.message.set_title(_(u"%s - %s of %d characters") % (self.title, len(self.message.get_text()), self.max)) text = self.message.get_text()
if len(self.message.get_text()) > self.max: results = parse_tweet(text)
self.message.set_title(_(u"%s - %s of %d characters") % (self.title, results.weightedLength, self.max))
if results.weightedLength > self.max:
self.session.sound.play("max_length.ogg") self.session.sound.play("max_length.ogg")
else: else:
self.message.set_title(_(u"%s - %s characters") % (self.title, len(self.message.get_text()))) self.message.set_title(_(u"%s - %s characters") % (self.title, len(self.message.get_text())))
@@ -194,13 +194,18 @@ class dm(basicTweet):
super(dm, self).__init__(session, title, caption, text, messageType="dm", max=10000) super(dm, self).__init__(session, title, caption, text, messageType="dm", max=10000)
widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
self.text_processor() self.text_processor()
widgetUtils.connect_event(self.message.cb, widgetUtils.ENTERED_TEXT, self.user_changed)
def user_changed(self, *args, **kwargs):
self.title = _("Direct message to %s") % (self.message.get_user())
self.text_processor()
def autocomplete_users(self, *args, **kwargs): def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu("dm") c.show_menu("dm")
class viewTweet(basicTweet): class viewTweet(basicTweet):
def __init__(self, tweet, tweetList, is_tweet=True, utc_offset=0, date=""): def __init__(self, tweet, tweetList, is_tweet=True, utc_offset=0, date="", item_url=""):
""" This represents a tweet displayer. However it could be used for showing something wich is not a tweet, like a direct message or an event. """ This represents a tweet displayer. However it could be used for showing something wich is not a tweet, like a direct message or an event.
param tweet: A dictionary that represents a full tweet or a string for non-tweets. param tweet: A dictionary that represents a full tweet or a string for non-tweets.
param tweetList: If is_tweet is set to True, this could be a list of quoted tweets. param tweetList: If is_tweet is set to True, this could be a list of quoted tweets.
@@ -211,62 +216,67 @@ class viewTweet(basicTweet):
text = "" text = ""
for i in range(0, len(tweetList)): for i in range(0, len(tweetList)):
# tweets with message keys are longer tweets, the message value is the full messaje taken from twishort. # tweets with message keys are longer tweets, the message value is the full messaje taken from twishort.
if "message" in tweetList[i] and tweetList[i]["is_quote_status"] == False: if hasattr(tweetList[i], "message") and tweetList[i].is_quote_status == False:
value = "message" value = "message"
else: else:
value = "full_text" value = "full_text"
if "retweeted_status" in tweetList[i] and tweetList[i]["is_quote_status"] == False: if hasattr(tweetList[i], "retweeted_status") and tweetList[i].is_quote_status == False:
if ("message" in tweetList[i]) == False: if not hasattr(tweetList[i], "message"):
text = text + "rt @%s: %s\n" % (tweetList[i]["retweeted_status"]["user"]["screen_name"], tweetList[i]["retweeted_status"]["full_text"]) text = text + "rt @%s: %s\n" % (tweetList[i].retweeted_status.user.screen_name, tweetList[i].retweeted_status.full_text)
else: else:
text = text + "rt @%s: %s\n" % (tweetList[i]["retweeted_status"]["user"]["screen_name"], tweetList[i][value]) text = text + "rt @%s: %s\n" % (tweetList[i].retweeted_status.user.screen_name, getattr(tweetList[i], value))
else: else:
text = text + " @%s: %s\n" % (tweetList[i]["user"]["screen_name"], tweetList[i][value]) text = text + " @%s: %s\n" % (tweetList[i].user.screen_name, getattr(tweetList[i], value))
# tweets with extended_entities could include image descriptions. # tweets with extended_entities could include image descriptions.
if "extended_entities" in tweetList[i] and "media" in tweetList[i]["extended_entities"]: if hasattr(tweetList[i], "extended_entities") and "media" in tweetList[i].extended_entities:
for z in tweetList[i]["extended_entities"]["media"]: for z in tweetList[i].extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
if "retweeted_status" in tweetList[i] and "extended_entities" in tweetList[i]["retweeted_status"] and "media" in tweetList[i]["retweeted_status"]["extended_entities"]: if hasattr(tweetList[i], "retweeted_status") and hasattr(tweetList[i].retweeted_status, "extended_entities") and "media" in tweetList[i].retweeted_status["extended_entities"]:
for z in tweetList[i]["retweeted_status"]["extended_entities"]["media"]: for z in tweetList[i].retweeted_status.extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
# set rt and likes counters. # set rt and likes counters.
rt_count = str(tweet["retweet_count"]) rt_count = str(tweet.retweet_count)
favs_count = str(tweet["favorite_count"]) favs_count = str(tweet.favorite_count)
# Gets the client from where this tweet was made. # Gets the client from where this tweet was made.
source = re.sub(r"(?s)<.*?>", "", tweet["source"]) source = tweet.source
original_date = arrow.get(tweet["created_at"], "ddd MMM DD H:m:s Z YYYY", locale="en") original_date = arrow.get(tweet.created_at, locale="en")
date = original_date.shift(seconds=utc_offset).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage()) date = original_date.shift(seconds=utc_offset).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
if text == "": if text == "":
if "message" in tweet: if hasattr(tweet, "message"):
value = "message" value = "message"
else: else:
value = "full_text" value = "full_text"
if "retweeted_status" in tweet: if hasattr(tweet, "retweeted_status"):
if ("message" in tweet) == False: if not hasattr(tweet, "message"):
text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], tweet["retweeted_status"]["full_text"]) text = "rt @%s: %s" % (tweet.retweeted_status.user.screen_name, tweet.retweeted_status.full_text)
else: else:
text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], tweet[value]) text = "rt @%s: %s" % (tweet.retweeted_status.user.screen_name, getattr(tweet, value))
else: else:
text = tweet[value] text = getattr(tweet, value)
text = self.clear_text(text) text = self.clear_text(text)
if "extended_entities" in tweet and "media" in tweet["extended_entities"]: if hasattr(tweet, "extended_entities") and "media" in tweet.extended_entities:
for z in tweet["extended_entities"]["media"]: for z in tweet.extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
if "retweeted_status" in tweet and "extended_entities" in tweet["retweeted_status"] and "media" in tweet["retweeted_status"]["extended_entities"]: if hasattr(tweet, "retweeted_status") and hasattr(tweet.retweeted_status, "extended_entities") and "media" in tweet.retweeted_status.extended_entities:
for z in tweet["retweeted_status"]["extended_entities"]["media"]: for z in tweet.retweeted_status.extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
self.message = message.viewTweet(text, rt_count, favs_count, source, date) self.message = message.viewTweet(text, rt_count, favs_count, source, date)
self.message.set_title(len(text)) results = parse_tweet(text)
self.message.set_title(results.weightedLength)
[self.message.set_image_description(i) for i in image_description] [self.message.set_image_description(i) for i in image_description]
else: else:
self.title = _(u"View item") self.title = _(u"View item")
text = tweet text = tweet
self.message = message.viewNonTweet(text, date) self.message = message.viewNonTweet(text, date)
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
if item_url != "":
self.message.enable_button("share")
widgetUtils.connect_event(self.message.share, widgetUtils.BUTTON_PRESSED, self.share)
self.item_url = item_url
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
if self.contain_urls() == True: if self.contain_urls() == True:
self.message.enable_button("unshortenButton") self.message.enable_button("unshortenButton")
@@ -284,3 +294,8 @@ class viewTweet(basicTweet):
if "https://twitter.com/" in i: if "https://twitter.com/" in i:
text = text.replace(i, "\n") text = text.replace(i, "\n")
return text return text
def share(self, *args, **kwargs):
if hasattr(self, "item_url"):
output.copy(self.item_url)
output.speak(_("Link copied to clipboard."))

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import str
from builtins import object
import os import os
import webbrowser import webbrowser
import sound_lib import sound_lib
@@ -74,12 +71,12 @@ class globalSettingsController(object):
self.dialog.set_value("general", "update_period", config.app["app-settings"]["update_period"]) self.dialog.set_value("general", "update_period", config.app["app-settings"]["update_period"])
self.dialog.set_value("general", "check_for_updates", config.app["app-settings"]["check_for_updates"]) self.dialog.set_value("general", "check_for_updates", config.app["app-settings"]["check_for_updates"])
self.dialog.set_value("general", "remember_mention_and_longtweet", config.app["app-settings"]["remember_mention_and_longtweet"]) self.dialog.set_value("general", "remember_mention_and_longtweet", config.app["app-settings"]["remember_mention_and_longtweet"])
proxyTypes=config.proxyTypes proxyTypes = [_("System default"), _("HTTP"), _("SOCKS v4"), _("SOCKS v4 with DNS support"), _("SOCKS v5"), _("SOCKS v5 with DNS support")]
self.dialog.create_proxy([_(u"Direct connection")]+proxyTypes) self.dialog.create_proxy(proxyTypes)
if config.app["proxy"]["type"] not in proxyTypes: try:
self.dialog.proxy.type.SetSelection(config.app["proxy"]["type"])
except:
self.dialog.proxy.type.SetSelection(0) self.dialog.proxy.type.SetSelection(0)
else:
self.dialog.proxy.type.SetSelection(proxyTypes.index(config.app["proxy"]["type"])+1)
self.dialog.set_value("proxy", "server", config.app["proxy"]["server"]) self.dialog.set_value("proxy", "server", config.app["proxy"]["server"])
self.dialog.set_value("proxy", "port", config.app["proxy"]["port"]) self.dialog.set_value("proxy", "port", config.app["proxy"]["port"])
self.dialog.set_value("proxy", "user", config.app["proxy"]["user"]) self.dialog.set_value("proxy", "user", config.app["proxy"]["user"])
@@ -121,7 +118,7 @@ class globalSettingsController(object):
if config.app["proxy"]["type"]!=self.dialog.get_value("proxy", "type") or config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"): if config.app["proxy"]["type"]!=self.dialog.get_value("proxy", "type") or config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"):
if self.is_started == True: if self.is_started == True:
self.needs_restart = True self.needs_restart = True
config.app["proxy"]["type"]=self.dialog.get_value("proxy", "type") config.app["proxy"]["type"] = self.dialog.proxy.type.Selection
config.app["proxy"]["server"] = self.dialog.get_value("proxy", "server") config.app["proxy"]["server"] = self.dialog.get_value("proxy", "server")
config.app["proxy"]["port"] = self.dialog.get_value("proxy", "port") config.app["proxy"]["port"] = self.dialog.get_value("proxy", "port")
config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user") config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user")
@@ -151,6 +148,7 @@ class accountSettingsController(globalSettingsController):
else: else:
self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments")) 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", "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.create_reporting()
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_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"]) self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
@@ -193,6 +191,9 @@ class accountSettingsController(globalSettingsController):
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time") self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names") self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall") 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
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"): if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
if self.dialog.get_value("general", "persist_size") == '': if self.dialog.get_value("general", "persist_size") == '':
self.config["general"]["persist_size"] =-1 self.config["general"]["persist_size"] =-1

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import object
from wxUI.dialogs import trends from wxUI.dialogs import trends
import widgetUtils import widgetUtils
@@ -10,7 +8,7 @@ class trendingTopicsController(object):
self.countries = {} self.countries = {}
self.cities = {} self.cities = {}
self.dialog = trends.trendingTopicsDialog() self.dialog = trends.trendingTopicsDialog()
self.information = session.twitter.get_available_trends() self.information = session.twitter.available_trends()
self.split_information() self.split_information()
widgetUtils.connect_event(self.dialog.country, widgetUtils.RADIOBUTTON, self.get_places) widgetUtils.connect_event(self.dialog.country, widgetUtils.RADIOBUTTON, self.get_places)
widgetUtils.connect_event(self.dialog.city, widgetUtils.RADIOBUTTON, self.get_places) widgetUtils.connect_event(self.dialog.city, widgetUtils.RADIOBUTTON, self.get_places)

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import object
import wx import wx
import webbrowser import webbrowser
import widgetUtils import widgetUtils
@@ -8,7 +6,8 @@ import output
from wxUI.dialogs import update_profile, show_user from wxUI.dialogs import update_profile, show_user
import logging import logging
log = logging.getLogger("controller.user") log = logging.getLogger("controller.user")
from twython import TwythonError from tweepy.errors import TweepyException, Forbidden, NotFound
from sessions.twitter import utils
class profileController(object): class profileController(object):
def __init__(self, session, user=None): def __init__(self, session, user=None):
@@ -25,36 +24,36 @@ class profileController(object):
else: else:
try: try:
self.get_data(screen_name=self.user) self.get_data(screen_name=self.user)
except TwythonError as err: except TweepyException as err:
if err.error_code == 404: if type(err) == NotFound:
wx.MessageDialog(None, _(u"That user does not exist"), _(u"Error"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(None, _(u"That user does not exist"), _(u"Error"), wx.ICON_ERROR).ShowModal()
if err.error_code == 403: if type(err) == Forbidden:
wx.MessageDialog(None, _(u"User has been suspended"), _(u"Error"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(None, _(u"User has been suspended"), _(u"Error"), wx.ICON_ERROR).ShowModal()
log.error("error %d: %s" % (err.error_code, err.msg)) log.error("error %s" % (str(err)))
return return
self.dialog = show_user.showUserProfile() self.dialog = show_user.showUserProfile()
string = self.get_user_info() string = self.get_user_info()
self.dialog.set("text", string) self.dialog.set("text", string)
self.dialog.set_title(_(u"Information for %s") % (self.data["screen_name"])) self.dialog.set_title(_(u"Information for %s") % (self.data.screen_name))
if self.data["url"] != None: if self.data.url != None:
self.dialog.enable_url() self.dialog.enable_url()
widgetUtils.connect_event(self.dialog.url, widgetUtils.BUTTON_PRESSED, self.visit_url) widgetUtils.connect_event(self.dialog.url, widgetUtils.BUTTON_PRESSED, self.visit_url)
if self.dialog.get_response() == widgetUtils.OK and self.user == None: if self.dialog.get_response() == widgetUtils.OK and self.user == None:
self.do_update() self.do_update()
def get_data(self, screen_name): def get_data(self, screen_name):
self.data = self.session.twitter.show_user(screen_name=screen_name) self.data = self.session.twitter.get_user(screen_name=screen_name)
if screen_name != self.session.db["user_name"]: if screen_name != self.session.db["user_name"]:
self.friendship_status = self.session.twitter.show_friendship(source_screen_name=self.session.db["user_name"], target_screen_name=screen_name) self.friendship_status = self.session.twitter.get_friendship(source_screen_name=self.session.db["user_name"], target_screen_name=screen_name)
def fill_profile_fields(self): def fill_profile_fields(self):
self.dialog.set_name(self.data["name"]) self.dialog.set_name(self.data.name)
if self.data["url"] != None: if self.data.url != None:
self.dialog.set_url(self.data["url"]) self.dialog.set_url(self.data.url)
if len(self.data["location"]) > 0: if len(self.data.location) > 0:
self.dialog.set_location(self.data["location"]) self.dialog.set_location(self.data.location)
if len(self.data["description"]) > 0: if len(self.data.description) > 0:
self.dialog.set_description(self.data["description"]) self.dialog.set_description(self.data.description)
def get_image(self): def get_image(self):
file = self.dialog.upload_picture() file = self.dialog.upload_picture()
@@ -84,45 +83,46 @@ class profileController(object):
if self.file != None: if self.file != None:
try: try:
self.session.twitter.update_profile_image(image=self.file) self.session.twitter.update_profile_image(image=self.file)
except TwythonError as e: except TweepyException as e:
output.speak(u"Error %s. %s" % (e.error_code, e.msg)) output.speak(u"Error %s" % (str(e)))
try: try:
self.session.twitter.update_profile(name=name, description=description, location=location, url=url) self.session.twitter.update_profile(name=name, description=description, location=location, url=url)
except TwythonError as e: except TweepyException as e:
output.speak(u"Error %s. %s" % (e.error_code, e.msg)) output.speak(u"Error %s." % (str(e)))
def get_user_info(self): def get_user_info(self):
string = u"" string = u""
string = string + _(u"Username: @%s\n") % (self.data["screen_name"]) string = string + _(u"Username: @%s\n") % (self.data.screen_name)
string = string + _(u"Name: %s\n") % (self.data["name"]) string = string + _(u"Name: %s\n") % (self.data.name)
if self.data["location"] != "": if self.data.location != "":
string = string + _(u"Location: %s\n") % (self.data["location"]) string = string + _(u"Location: %s\n") % (self.data.location)
if self.data["url"] != None: if self.data.url != None:
string = string+ _(u"URL: %s\n") % (self.data["url"]) string = string+ _(u"URL: %s\n") % (self.data.entities["url"]["urls"][0]["expanded_url"])
if self.data["description"] != "": if self.data.description != "":
string = string+ _(u"Bio: %s\n") % (self.data["description"]) if self.data.entities.get("description") != None and self.data.entities["description"].get("urls"):
if self.data["protected"] == True: protected = _(u"Yes") self.data.description = utils.expand_urls(self.data.description, self.data.entities["description"])
string = string+ _(u"Bio: %s\n") % (self.data.description)
if self.data.protected == True: protected = _(u"Yes")
else: protected = _(u"No") else: protected = _(u"No")
string = string+ _(u"Protected: %s\n") % (protected) string = string+ _(u"Protected: %s\n") % (protected)
if hasattr(self, "friendship_status"): if hasattr(self, "friendship_status"):
relation = False relation = False
friendship = "Relationship: " friendship = "Relationship: "
if self.friendship_status["relationship"]["target"]["followed_by"]: if self.friendship_status[0].following:
friendship += _(u"You follow {0}. ").format(self.data["name"],) friendship += _(u"You follow {0}. ").format(self.data.name,)
relation = True relation = True
if self.friendship_status["relationship"]["target"]["following"]: if self.friendship_status[1].following:
friendship += _(u"{0} is following you.").format(self.data["name"],) friendship += _(u"{0} is following you.").format(self.data.name,)
relation = True relation = True
if relation == True: if relation == True:
string = string+friendship+"\n" string = string+friendship+"\n"
string = string+_(u"Followers: %s\n Friends: %s\n") % (self.data["followers_count"], self.data["friends_count"]) string = string+_(u"Followers: %s\n Friends: %s\n") % (self.data.followers_count, self.data.friends_count)
if self.data["verified"] == True: verified = _(u"Yes") if self.data.verified == True: verified = _(u"Yes")
else: verified = _(u"No") else: verified = _(u"No")
string = string+ _(u"Verified: %s\n") % (verified) string = string+ _(u"Verified: %s\n") % (verified)
string = string+ _(u"Tweets: %s\n") % (self.data["statuses_count"]) string = string+ _(u"Tweets: %s\n") % (self.data.statuses_count)
string = string+ _(u"Likes: %s") % (self.data["favourites_count"]) string = string+ _(u"Likes: %s") % (self.data.favourites_count)
return string return string
def visit_url(self, *args, **kwargs): def visit_url(self, *args, **kwargs):
webbrowser.open_new_tab(self.data["url"]) webbrowser.open_new_tab(self.data.url)

View File

@@ -1,12 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import object
import re
import widgetUtils import widgetUtils
import output import output
from wxUI.dialogs import userActions from wxUI.dialogs import userActions
from pubsub import pub from pubsub import pub
from twython import TwythonError from tweepy.errors import TweepyException
from extra import autocompletionUsers from extra import autocompletionUsers
class userActionsController(object): class userActionsController(object):
@@ -32,51 +29,51 @@ class userActionsController(object):
def follow(self, user): def follow(self, user):
try: try:
self.session.twitter.create_friendship(screen_name=user ) self.session.twitter.create_friendship(screen_name=user )
except TwythonError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True) output.speak("Error %s" % (str(err)), True)
def unfollow(self, user): def unfollow(self, user):
try: try:
id = self.session.twitter.destroy_friendship(screen_name=user ) id = self.session.twitter.destroy_friendship(screen_name=user )
except TwythonError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True) output.speak("Error %s" % (str(err)), True)
def mute(self, user): def mute(self, user):
try: try:
id = self.session.twitter.create_mute(screen_name=user ) id = self.session.twitter.create_mute(screen_name=user )
except TwythonError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True) output.speak("Error %s" % (str(err)), True)
def unmute(self, user): def unmute(self, user):
try: try:
id = self.session.twitter.destroy_mute(screen_name=user ) id = self.session.twitter.destroy_mute(screen_name=user )
except TwythonError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True) output.speak("Error %s" % (str(err)), True)
def report(self, user): def report(self, user):
try: try:
id = self.session.twitter.report_spam(screen_name=user ) id = self.session.twitter.report_spam(screen_name=user )
except TwythonError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True) output.speak("Error %s" % (str(err)), True)
def block(self, user): def block(self, user):
try: try:
id = self.session.twitter.create_block(screen_name=user ) id = self.session.twitter.create_block(screen_name=user )
except TwythonError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True) output.speak("Error %s" % (str(err)), True)
def unblock(self, user): def unblock(self, user):
try: try:
id = self.session.twitter.destroy_block(screen_name=user ) id = self.session.twitter.destroy_block(screen_name=user )
except TwythonError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True) output.speak("Error %s" % (str(err)), True)
def ignore_client(self, user): def ignore_client(self, user):
tweet = self.buffer.get_right_tweet() tweet = self.buffer.get_right_tweet()
if "sender" in tweet: if hasattr(tweet, "sender"):
output.speak(_(u"You can't ignore direct messages")) output.speak(_(u"You can't ignore direct messages"))
return return
client = re.sub(r"(?s)<.*?>", "", tweet["source"]) client = tweet.source
if client not in self.session.settings["twitter"]["ignored_clients"]: if client not in self.session.settings["twitter"]["ignored_clients"]:
self.session.settings["twitter"]["ignored_clients"].append(client) self.session.settings["twitter"]["ignored_clients"].append(client)
self.session.settings.write() self.session.settings.write()

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import widgetUtils
from pubsub import pub
from wxUI.dialogs import userAliasDialogs
from extra import autocompletionUsers
class userAliasController(object):
def __init__(self, settings):
super(userAliasController, self).__init__()
self.settings = settings
self.dialog = userAliasDialogs.userAliasEditorDialog()
self.update_aliases_manager()
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.on_add)
widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.on_edit)
widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.on_remove)
pub.subscribe(self.update_aliases_manager, "alias-added")
self.dialog.ShowModal()
def update_aliases_manager(self):
self.dialog.users.Clear()
aliases = [self.settings["user-aliases"].get(k) for k in self.settings["user-aliases"].keys()]
if len(aliases) > 0:
self.dialog.users.InsertItems(aliases, 0)
self.dialog.on_selection_changes()
def on_add(self, *args, **kwargs):
pub.sendMessage("execute-action", action="add_alias")
def on_edit(self, *args, **kwargs):
selection = self.dialog.get_selected_user()
if selection != "":
edited = self.dialog.edit_alias_dialog(_("Edit alias for {}").format(selection))
if edited == None or edited == "":
return
for user_key in self.settings["user-aliases"].keys():
if self.settings["user-aliases"][user_key] == selection:
self.settings["user-aliases"][user_key] = edited
self.settings.write()
self.update_aliases_manager()
break
def on_remove(self, *args, **kwargs):
selection = self.dialog.get_selected_user()
if selection == None or selection == "":
return
should_remove = self.dialog.remove_alias_dialog()
if should_remove:
for user_key in self.settings["user-aliases"].keys():
if self.settings["user-aliases"][user_key] == selection:
self.settings["user-aliases"].pop(user_key)
self.settings.write()
self.update_aliases_manager()
break

View File

@@ -13,8 +13,8 @@ actions = reverse_sort.reverse_sort([ ("audio", _(u"Audio tweet.")),
("favourite", _(u"Tweet liked.")), ("favourite", _(u"Tweet liked.")),
("favourites_timeline_updated", _(u"Likes buffer updated.")), ("favourites_timeline_updated", _(u"Likes buffer updated.")),
("geo", _(u"Geotweet.")), ("geo", _(u"Geotweet.")),
("image", _("Tweet contains one or more images")), ("image", _("Tweet contains one or more images")),
("limit", _(u"Boundary reached.")), ("limit", _(u"Boundary reached.")),
("list_tweet", _(u"List updated.")), ("list_tweet", _(u"List updated.")),
("max_length", _(u"Too many characters.")), ("max_length", _(u"Too many characters.")),
("mention_received", _(u"Mention received.")), ("mention_received", _(u"Mention received.")),

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import object
import output import output
from . import storage from . import storage
from . import wx_menu from . import wx_menu

View File

@@ -1,11 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
# -*- coding: utf-8 -*-
from builtins import object
from . import storage
import widgetUtils import widgetUtils
from . import wx_manage from tweepy.errors import TweepyException
from . import storage, wx_manage
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
class autocompletionManage(object): class autocompletionManage(object):
@@ -32,11 +28,12 @@ class autocompletionManage(object):
if usr == False: if usr == False:
return return
try: try:
data = self.session.twitter.twitter.show_user(screen_name=usr) data = self.session.twitter.get_user(screen_name=usr)
except: except TweepyException as e:
log.exception("Exception raised when attempting to add an user to the autocomplete database manually.")
self.dialog.show_invalid_user_error() self.dialog.show_invalid_user_error()
return return
self.database.set_user(data["screen_name"], data["name"], 0) self.database.set_user(data.screen_name, data.name, 0)
self.update_list() self.update_list()
def remove_user(self, ev): def remove_user(self, ev):

View File

@@ -1,13 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
# -*- coding: utf-8 -*-
from builtins import object
from . import storage
import widgetUtils import widgetUtils
import output
from . import wx_settings from . import wx_settings
from . import manage from . import manage
import output from . import storage
from mysc.thread_utils import call_threaded from mysc.thread_utils import call_threaded
class autocompletionSettings(object): class autocompletionSettings(object):
@@ -30,14 +26,14 @@ class autocompletionSettings(object):
database = storage.storage(self.buffer.session.session_id) database = storage.storage(self.buffer.session.session_id)
if self.dialog.get("followers_buffer") == True: if self.dialog.get("followers_buffer") == True:
buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"]) buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]["items"]: for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 1) database.set_user(i.screen_name, i.name, 1)
else: else:
database.remove_by_buffer(1) database.remove_by_buffer(1)
if self.dialog.get("friends_buffer") == True: if self.dialog.get("friends_buffer") == True:
buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"]) buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]["items"]: for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 2) database.set_user(i.screen_name, i.name, 2)
else: else:
database.remove_by_buffer(2) database.remove_by_buffer(2)
wx_settings.show_success_dialog() wx_settings.show_success_dialog()
@@ -52,12 +48,12 @@ def execute_at_startup(window, buffer, config):
if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True: if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True:
buffer = window.search_buffer("followers", config["twitter"]["user_name"]) buffer = window.search_buffer("followers", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]: for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 1) database.set_user(i.screen_name, i.name, 1)
else: else:
database.remove_by_buffer(1) database.remove_by_buffer(1)
if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True: if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True:
buffer = window.search_buffer("friends", config["twitter"]["user_name"]) buffer = window.search_buffer("friends", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]: for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 2) database.set_user(i.screen_name, i.name, 2)
else: else:
database.remove_by_buffer(2) database.remove_by_buffer(2)

View File

@@ -1,11 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals import os, sqlite3, paths
from builtins import object
import sqlite3, paths
class storage(object): class storage(object):
def __init__(self, session_id): def __init__(self, session_id):
self.connection = sqlite3.connect(paths.config_path("%s/autocompletionUsers.dat" % (session_id))) self.connection = sqlite3.connect(os.path.join(paths.config_path(), "%s/autocompletionUsers.dat" % (session_id)))
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor()
if self.table_exist("users") == False: if self.table_exist("users") == False:
self.create_table() self.create_table()
@@ -23,7 +21,7 @@ class storage(object):
return self.cursor.fetchall() return self.cursor.fetchall()
def get_users(self, term): def get_users(self, term):
self.cursor.execute("""SELECT * FROM users WHERE user LIKE ?""", ('{}%'.format(term),)) self.cursor.execute("""SELECT * FROM users WHERE UPPER(user) LIKE :term OR UPPER(name) LIKE :term""", {"term": "%{}%".format(term.upper())})
return self.cursor.fetchall() return self.cursor.fetchall()
def set_user(self, screen_name, user_name, from_a_buffer): def set_user(self, screen_name, user_name, from_a_buffer):

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import wx import wx
import widgetUtils import widgetUtils
from multiplatform_widgets import widgets from multiplatform_widgets import widgets
import application import application
class autocompletionManageDialog(widgetUtils.BaseDialog): class autocompletionManageDialog(widgetUtils.BaseDialog):
def __init__(self): def __init__(self):
super(autocompletionManageDialog, self).__init__(parent=None, id=-1, title=_(u"Manage Autocompletion database")) super(autocompletionManageDialog, self).__init__(parent=None, id=-1, title=_(u"Manage Autocompletion database"))

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import wx import wx
class menu(wx.Menu): class menu(wx.Menu):

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import wx import wx
import widgetUtils import widgetUtils
import application import application

View File

@@ -1,15 +1,23 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals import logging
from builtins import zip from googletrans import Translator, LANGUAGES
from yandex_translate import YandexTranslate
log = logging.getLogger("extras.translator")
# create a single translator instance
# see https://github.com/ssut/py-googletrans/issues/234
t = None
def translate(text="", target="en"): def translate(text="", target="en"):
t = YandexTranslate("trnsl.1.1.20161012T134532Z.d01b9c75fc39aa74.7d1be75a5166a80583eeb020e10f584168da6bf7") global t
vars = dict(text=text, lang=target) log.debug("Received translation request for language %s, text=%s" % (target, text))
return t.translate(**vars)["text"][0] if t == None:
t = Translator()
vars = dict(text=text, dest=target)
return t.translate(**vars).text
supported_langs = None supported_langs = None
d = None
languages = { languages = {
"af": _(u"Afrikaans"), "af": _(u"Afrikaans"),
"sq": _(u"Albanian"), "sq": _(u"Albanian"),
@@ -105,11 +113,4 @@ languages = {
} }
def available_languages(): def available_languages():
global supported_langs, d return dict(sorted(languages.items(), key=lambda x: x[1]))
if supported_langs == None and d == None:
t = YandexTranslate("trnsl.1.1.20161012T134532Z.d01b9c75fc39aa74.7d1be75a5166a80583eeb020e10f584168da6bf7")
supported_langs = t.langs
d = []
for i in supported_langs:
d.append(languages[i])
return sorted(zip(supported_langs, d))

View File

@@ -16,37 +16,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################ ############################################################
from __future__ import absolute_import
from __future__ import unicode_literals
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
from . import translator from . import translator
import wx import wx
from wxUI.dialogs import baseDialog from wxUI.dialogs import baseDialog
class translateDialog(baseDialog.BaseWXDialog): class translateDialog(baseDialog.BaseWXDialog):
def __init__(self): def __init__(self):
languages = []
language_dict = translator.available_languages()
for k in language_dict:
languages.append(language_dict[k])
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message")) super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
staticDest = wx.StaticText(panel, -1, _(u"Target language")) staticDest = wx.StaticText(panel, -1, _(u"Target language"))
self.dest_lang = wx.ComboBox(panel, -1, choices=[x[1] for x in translator.available_languages()], style = wx.CB_READONLY) self.dest_lang = wx.ComboBox(panel, -1, choices=languages, style = wx.CB_READONLY)
self.dest_lang.SetFocus() self.dest_lang.SetFocus()
self.dest_lang.SetSelection(0) self.dest_lang.SetSelection(0)
listSizer = wx.BoxSizer(wx.HORIZONTAL) listSizer = wx.BoxSizer(wx.HORIZONTAL)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from arrow import locales from arrow import locales
from arrow.locales import Locale from arrow.locales import Locale
@@ -8,88 +7,43 @@ def fix():
locales.get_locale = get_locale locales.get_locale = get_locale
def get_locale(name): def get_locale(name):
locale_cls = locales._locales.get(name.lower()) locale_cls = locales._locale_map.get(name.lower())
if locale_cls is None: if locale_cls is None:
name = name[:2] name = name[:2]
locale_cls = locales._locales.get(name.lower()) locale_cls = locales._locale_map.get(name.lower())
if locale_cls == None: if locale_cls == None:
return locales.EnglishLocale() return locales.EnglishLocale()
return locale_cls() return locale_cls()
class CatalaLocale(Locale): class GalicianLocale(object):
names = ['ca', 'ca_es', 'ca_ca']
past = 'Fa {0}'
future = '{0}' # I don't know what's the right phrase in catala for the future.
timeframes = {
'now': 'Ara mateix',
'seconds': 'segons',
'minute': '1 minut',
'minutes': '{0} minuts',
'hour': 'una hora',
'hours': '{0} hores',
'day': 'un dia',
'days': '{0} dies',
'month': 'un mes',
'months': '{0} messos',
'year': 'un any',
'years': '{0} anys',
}
month_names = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre']
month_abbreviations = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre']
day_names = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge']
day_abbreviations = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge']
class GalicianLocale(Locale):
names = ['gl', 'gl_es', 'gl_gl'] names = ['gl', 'gl_es', 'gl_gl']
past = 'Fai {0}' past = 'Hai {0}'
future = 'En {0}' future = 'En {0}'
and_word = "e"
timeframes = { timeframes = {
'now': 'Agora mesmo', 'now': 'Agora',
'seconds': 'segundos', "second": "un segundo",
'seconds': '{0} segundos',
'minute': 'un minuto', 'minute': 'un minuto',
'minutes': '{0} minutos', 'minutes': '{0} minutos',
'hour': 'una hora', 'hour': 'unha hora',
'hours': '{0} horas', 'hours': '{0} horas',
'day': 'un día', 'day': 'un día',
'days': '{0} días', 'days': '{0} días',
"week": "unha semana",
"weeks": "{0} semanas",
'month': 'un mes', 'month': 'un mes',
'months': '{0} meses', 'months': '{0} meses',
'year': 'un ano', 'year': 'un ano',
'years': '{0} anos', 'years': '{0} anos',
} }
month_names = ['', 'Xaneiro', 'Febreiro', 'Marzo', 'Abril', 'Maio', 'Xuño', 'Xullo', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Decembro'] meridians = {"am": "am", "pm": "pm", "AM": "AM", "PM": "PM"}
month_abbreviations = ['', 'Xan', 'Feb', 'Mar', 'Abr', 'Mai', 'Xun', 'Xul', 'Ago', 'Set', 'Out', 'Nov', 'Dec']
day_names = ['', 'Luns', 'Martes', 'Mércores', 'Xoves', 'Venres', 'Sábado', 'Domingo']
day_abbreviations = ['', 'Lun', 'Mar', 'Mer', 'xov', 'Ven' 'Sab', 'Dom']
class BasqueLocale(Locale): month_names = ['', 'xaneiro', 'febreiro', 'marzo', 'abril', 'maio', 'xuño', 'xullo', 'agosto', 'setembro', 'outubro', 'novembro', 'decembro']
names = ['eu', 'eu_es', 'eu_eu'] month_abbreviations = ['', 'xan', 'feb', 'mar', 'abr', 'mai', 'xun', 'xul', 'ago', 'set', 'out', 'nov', 'dec']
past = 'duela {0}' day_names = ['', 'luns', 'martes', 'mércores', 'xoves', 'venres', 'sábado', 'domingo']
future = '{0} igarota' day_abbreviations = ['', 'lun', 'mar', 'mer', 'xov', 'ven', 'sab', 'dom']
ordinal_day_re = r"((?P<value>[1-3]?[0-9](?=[ºª]))[ºª])"
timeframes = {
'now': 'Orain',
# 'second': 'segundu bat',
'seconds': 'segundu batzuk', # without specifying a number.
#'seconds': '{0} segundu', # specifying a number
'minute': 'minutu bat',
'minutes': '{0} minutu',
'hour': 'ordu bat',
'hours': '{0} ordu',
'day': 'egun bat',
'days': '{0} egun',
'month': 'hilabete bat',
'months': '{0} hilabete',
'year': 'urte bat',
'years': '{0} urte',
}
month_names = ['', 'Urtarrilak', 'Otsailak', 'Martxoak', 'Apirilak', 'Maiatzak', 'Ekainak', 'Uztailak', 'Abuztuak', 'Irailak', 'Urriak', 'Azaroak', 'Abenduak']
month_abbreviations = ['', 'urt', 'ots', 'mar', 'api', 'mai', 'eka', 'uzt', 'abu', 'ira', 'urr', 'aza', 'abe']
day_names = ['', 'Asteleehna', 'Asteartea', 'Asteazkena', 'Osteguna', 'Ostirala', 'Larunbata', 'Igandea']
day_abbreviations = ['', 'al', 'ar', 'az', 'og', 'ol', 'lr', 'ig']

View File

@@ -4,7 +4,7 @@ import thread
import pyatspi import pyatspi
def parse(s): def parse(s):
"""parse a string like control+f into (modifier, key). """parse a string like control+f into (modifier, key).
Unknown modifiers will return ValueError.""" Unknown modifiers will return ValueError."""
m = 0 m = 0
lst = s.split('+') lst = s.split('+')
if not len(lst): return (0, s) if not len(lst): return (0, s)
@@ -13,7 +13,7 @@ Unknown modifiers will return ValueError."""
"shift": 1<<pyatspi.MODIFIER_SHIFT, "shift": 1<<pyatspi.MODIFIER_SHIFT,
"control": 1<<pyatspi.MODIFIER_CONTROL, "control": 1<<pyatspi.MODIFIER_CONTROL,
"alt": 1<<pyatspi.MODIFIER_ALT, "alt": 1<<pyatspi.MODIFIER_ALT,
"win":1<<pyatspi.MODIFIER_META3, "win":1<<pyatspi.MODIFIER_META3,
} }
for item in lst: for item in lst:
if item in d: if item in d:
@@ -45,8 +45,8 @@ class LinuxKeyboardHandler(KeyboardHandler):
t.start() t.start()
def register_key(self, key, function): def register_key(self, key, function):
"""key will be a string, such as control+shift+f. """key will be a string, such as control+shift+f.
We need to convert that, using parse_key, We need to convert that, using parse_key,
into modifier and key to put into our dictionary.""" into modifier and key to put into our dictionary."""
#register key so we know if we have it on event receive. #register key so we know if we have it on event receive.
t = parse(key) t = parse(key)
keys[t] = function keys[t] = function

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,60 +1,61 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
actions = { actions = {
"up": _(u"Go up in the current buffer"), "up": _(u"Go up in the current buffer"),
"down": _(u"Go down in the current buffer"), "down": _(u"Go down in the current buffer"),
"left": _(u"Go to the previous buffer"), "left": _(u"Go to the previous buffer"),
"right": _(u"Go to the next buffer"), "right": _(u"Go to the next buffer"),
"next_account": _(u"Focus the next session"), "next_account": _(u"Focus the next session"),
"previous_account": _(u"Focus the previous session"), "previous_account": _(u"Focus the previous session"),
"show_hide": _(u"Show or hide the GUI"), "show_hide": _(u"Show or hide the GUI"),
"post_tweet": _(u"New tweet"), "post_tweet": _(u"New tweet"),
"post_reply": _(u"Reply"), "post_reply": _(u"Reply"),
"post_retweet": _(u"Retweet"), "post_retweet": _(u"Retweet"),
"send_dm": _(u"Send direct message"), "send_dm": _(u"Send direct message"),
"add_to_favourites": _(u"Like a tweet"), "add_to_favourites": _(u"Like a tweet"),
"toggle_like": _(u"Like/unlike a tweet"), "toggle_like": _(u"Like/unlike a tweet"),
"remove_from_favourites": _(u"Unlike a tweet"), "remove_from_favourites": _(u"Unlike a tweet"),
"follow": _(u"Open the user actions dialogue"), "follow": _(u"Open the user actions dialogue"),
"user_details": _(u"See user details"), "user_details": _(u"See user details"),
"view_item": _(u"Show tweet"), "view_item": _(u"Show tweet"),
"exit": _(u"Quit"), "exit": _(u"Quit"),
"open_timeline": _(u"Open user timeline"), "open_timeline": _(u"Open user timeline"),
"remove_buffer": _(u"Destroy buffer"), "remove_buffer": _(u"Destroy buffer"),
"interact": _(u"Interact with the currently focused tweet."), "interact": _(u"Interact with the currently focused tweet."),
"url": _(u"Open URL"), "url": _(u"Open URL"),
"open_in_browser": _(u"View in Twitter"), "open_in_browser": _(u"View in Twitter"),
"volume_up": _(u"Increase volume by 5%"), "volume_up": _(u"Increase volume by 5%"),
"volume_down": _(u"Decrease volume by 5%"), "volume_down": _(u"Decrease volume by 5%"),
"go_home": _(u"Jump to the first element of a buffer"), "go_home": _(u"Jump to the first element of a buffer"),
"go_end": _(u"Jump to the last element of the current 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_up": _(u"Jump 20 elements up in the current buffer"),
"go_page_down": _(u"Jump 20 elements down in the current buffer"), "go_page_down": _(u"Jump 20 elements down in the current buffer"),
"update_profile": _(u"Edit profile"), "update_profile": _(u"Edit profile"),
"delete": _(u"Delete a tweet or direct message"), "delete": _(u"Delete a tweet or direct message"),
"clear_buffer": _(u"Empty the current buffer"), "clear_buffer": _(u"Empty the current buffer"),
"repeat_item": _(u"Repeat last item"), "repeat_item": _(u"Repeat last item"),
"copy_to_clipboard": _(u"Copy to clipboard"), "copy_to_clipboard": _(u"Copy to clipboard"),
"add_to_list": _(u"Add to list"), "add_to_list": _(u"Add to list"),
"remove_from_list": _(u"Remove from list"), "remove_from_list": _(u"Remove from list"),
"toggle_buffer_mute": _(u"Mute/unmute the active buffer"), "toggle_buffer_mute": _(u"Mute/unmute the active buffer"),
"toggle_session_mute": _(u"Mute/unmute the current session"), "toggle_session_mute": _(u"Mute/unmute the current session"),
"toggle_autoread": _(u"toggle the automatic reading of incoming tweets in the active buffer"), "toggle_autoread": _(u"toggle the automatic reading of incoming tweets in the active buffer"),
"search": _(u"Search on twitter"), "search": _(u"Search on twitter"),
"find": _(u"Find a string in the currently focused buffer"), "find": _(u"Find a string in the currently focused buffer"),
"edit_keystrokes": _(u"Show the keystroke editor"), "edit_keystrokes": _(u"Show the keystroke editor"),
"view_user_lists": _(u"Show lists for a specified user"), "view_user_lists": _(u"Show lists for a specified user"),
"get_more_items": _(u"load previous items"), "get_more_items": _(u"load previous items"),
"reverse_geocode": _(u"Get geolocation"), "reverse_geocode": _(u"Get geolocation"),
"view_reverse_geocode": _(u"Display the tweet's geolocation in a dialog"), "view_reverse_geocode": _(u"Display the tweet's geolocation in a dialog"),
"get_trending_topics": _(u"Create a trending topics buffer"), "get_trending_topics": _(u"Create a trending topics buffer"),
"open_conversation": _(u"View conversation"), "open_conversation": _(u"View conversation"),
"check_for_updates": _(u"Check and download updates"), "check_for_updates": _(u"Check and download updates"),
"lists_manager": _(u"Opens the list manager, which allows you to create, edit, delete and open lists in buffers."), "lists_manager": _(u"Opens the list manager, which allows you to create, edit, delete and open lists in buffers."),
"configuration": _(u"Opens the global settings dialogue"), "configuration": _(u"Opens the global settings dialogue"),
"list_manager": _(u"Opens the list manager"), "list_manager": _(u"Opens the list manager"),
"accountConfiguration": _(u"Opens the account settings dialogue"), "accountConfiguration": _(u"Opens the account settings dialogue"),
"audio": _(u"Try to play an audio file"), "audio": _(u"Try to play an audio file"),
"update_buffer": _(u"Updates the buffer and retrieves possible lost items there."), "update_buffer": _(u"Updates the buffer and retrieves possible lost items there."),
"ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."), "ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."),
"add_alias": _("Adds an alias to an user"),
} }

View File

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

View File

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

View File

@@ -195,7 +195,7 @@ def langToWindowsLocale(lang):
languages = {"en": "eng", languages = {"en": "eng",
"ar": "ara", "ar": "ara",
"ca": "cat", "ca": "cat",
"de": "deu", "de": "deu",
"es": "esp", "es": "esp",
"fi": "fin", "fi": "fin",
"fr": "fre_FRA", "fr": "fre_FRA",

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -6,15 +6,15 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TW Blue 0.85\n" "Project-Id-Version: TW Blue 0.85\n"
"POT-Creation-Date: 2019-03-17 13:34+Hora estndar romance\n" "POT-Creation-Date: 2019-03-17 13:34+Hora estndar romance\n"
"PO-Revision-Date: 2018-08-15 09:47+0400\n" "PO-Revision-Date: 2021-07-05 16:03+0200\n"
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n" "Last-Translator: Artem Plaksin <admin@maniyax.ru>\n"
"Language-Team: Alexander Jaszyn <a.jaszyn@ya.ru>\n" "Language-Team: Alexander Jaszyn <a.jaszyn@ya.ru>\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 1.5.7\n" "X-Generator: Poedit 3.0\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SourceCharset: UTF-8\n"
@@ -84,24 +84,22 @@ msgid "Muted users"
msgstr "Отключенные пользователи" msgstr "Отключенные пользователи"
#: ../src\controller\buffers\twitterBuffers.py:75 #: ../src\controller\buffers\twitterBuffers.py:75
#, fuzzy
msgid "{username}'s timeline" msgid "{username}'s timeline"
msgstr "Открыть ленту пользователя" msgstr "Лента твитов {username}"
#: ../src\controller\buffers\twitterBuffers.py:77 #: ../src\controller\buffers\twitterBuffers.py:77
msgid "{username}'s likes" msgid "{username}'s likes"
msgstr "" msgstr "Понравившееся {username}"
#: ../src\controller\buffers\twitterBuffers.py:79 #: ../src\controller\buffers\twitterBuffers.py:79
msgid "{username}'s followers" msgid "{username}'s followers"
msgstr "" msgstr "Читающие {username}"
#: ../src\controller\buffers\twitterBuffers.py:81 #: ../src\controller\buffers\twitterBuffers.py:81
msgid "{username}'s friends" msgid "{username}'s friends"
msgstr "" msgstr "Список читаемых {username}"
#: ../src\controller\buffers\twitterBuffers.py:83 #: ../src\controller\buffers\twitterBuffers.py:83
#, fuzzy
msgid "Unknown buffer" msgid "Unknown buffer"
msgstr "Неизвестно" msgstr "Неизвестно"
@@ -119,14 +117,12 @@ msgid "Write the tweet here"
msgstr "Напишите текст твита здесь" msgstr "Напишите текст твита здесь"
#: ../src\controller\buffers\twitterBuffers.py:194 #: ../src\controller\buffers\twitterBuffers.py:194
#, fuzzy
msgid "New tweet in {0}" msgid "New tweet in {0}"
msgstr "Новый твит" msgstr "Новый твит в {0}"
#: ../src\controller\buffers\twitterBuffers.py:197 #: ../src\controller\buffers\twitterBuffers.py:197
#, fuzzy
msgid "{0} new tweets in {1}." msgid "{0} new tweets in {1}."
msgstr "Пользователь @{0} процитировал ваш твит: {1}" msgstr "@{0} процитировал ваш твит: {1}"
#: ../src\controller\buffers\twitterBuffers.py:232 #: ../src\controller\buffers\twitterBuffers.py:232
#: ../src\controller\buffers\twitterBuffers.py:676 #: ../src\controller\buffers\twitterBuffers.py:676
@@ -182,7 +178,7 @@ msgstr "Профиль"
#: ../src\controller\buffers\twitterBuffers.py:634 #: ../src\controller\buffers\twitterBuffers.py:634
#: ../src\controller\buffers\twitterBuffers.py:987 #: ../src\controller\buffers\twitterBuffers.py:987
msgid "Opening item in web browser..." msgid "Opening item in web browser..."
msgstr "" msgstr "Открыть в браузере"
#: ../src\controller\buffers\twitterBuffers.py:688 #: ../src\controller\buffers\twitterBuffers.py:688
#: ../src\controller\buffers\twitterBuffers.py:855 #: ../src\controller\buffers\twitterBuffers.py:855
@@ -196,12 +192,10 @@ msgid "Mention"
msgstr "Упомянуть" msgstr "Упомянуть"
#: ../src\controller\buffers\twitterBuffers.py:728 #: ../src\controller\buffers\twitterBuffers.py:728
#, fuzzy
msgid "{0} new direct messages." msgid "{0} new direct messages."
msgstr "Новое личное сообщение" msgstr "Новое личное сообщение"
#: ../src\controller\buffers\twitterBuffers.py:731 #: ../src\controller\buffers\twitterBuffers.py:731
#, fuzzy
msgid "This action is not supported in the buffer yet." msgid "This action is not supported in the buffer yet."
msgstr "Это действие не поддерживается в данном буфере" msgstr "Это действие не поддерживается в данном буфере"
@@ -210,16 +204,16 @@ msgid ""
"Getting more items cannot be done in this buffer. Use the direct messages " "Getting more items cannot be done in this buffer. Use the direct messages "
"buffer instead." "buffer instead."
msgstr "" msgstr ""
"Невозможно получить больше элементов в данном буфере, используйте буфер "
"личных сообщений вместо этого."
#: ../src\controller\buffers\twitterBuffers.py:983 #: ../src\controller\buffers\twitterBuffers.py:983
#, fuzzy
msgid "{0} new followers." msgid "{0} new followers."
msgstr "Новый читатель." msgstr "{0} новых читателей."
#: ../src\controller\buffers\twitterBuffers.py:1266 #: ../src\controller\buffers\twitterBuffers.py:1266
#, fuzzy
msgid "This action is not supported in the buffer, yet." msgid "This action is not supported in the buffer, yet."
msgstr "Это действие не поддерживается в данном буфере" msgstr "Это действие пока не поддерживается в буфере."
#: ../src\controller\mainController.py:273 #: ../src\controller\mainController.py:273
msgid "Ready" msgid "Ready"
@@ -318,9 +312,8 @@ msgid "Select the user"
msgstr "Выберите пользователя" msgstr "Выберите пользователя"
#: ../src\controller\mainController.py:809 ../src\controller\messages.py:236 #: ../src\controller\mainController.py:809 ../src\controller\messages.py:236
#, fuzzy
msgid "MMM D, YYYY. H:m" msgid "MMM D, YYYY. H:m"
msgstr "dddd, MMMM D, YYYY H:m:s" msgstr "MMM D, YYYY. H:m"
#: ../src\controller\mainController.py:934 #: ../src\controller\mainController.py:934
msgid "Conversation with {0}" msgid "Conversation with {0}"
@@ -379,12 +372,11 @@ msgstr "Список уже открыт"
#: ../src\controller\mainController.py:1423 #: ../src\controller\mainController.py:1423
#: ../src\controller\mainController.py:1439 #: ../src\controller\mainController.py:1439
#, fuzzy
msgid "" msgid ""
"An error happened while trying to connect to the server. Please try later." "An error happened while trying to connect to the server. Please try later."
msgstr "" msgstr ""
"Что-то неожиданное произошло при попытке сообщить об ошибке. Пожалуйста, " "При попытке подключиться к серверу произошла ошибка. Пожалуйста, попробуйте "
"повторите попытку позже" "позже."
#: ../src\controller\mainController.py:1475 #: ../src\controller\mainController.py:1475
msgid "The auto-reading of new tweets is enabled for this buffer" msgid "The auto-reading of new tweets is enabled for this buffer"
@@ -874,19 +866,19 @@ msgstr "Игнорировать"
#: ../src\extra\SpellChecker\wx_ui.py:43 #: ../src\extra\SpellChecker\wx_ui.py:43
msgid "I&gnore all" msgid "I&gnore all"
msgstr "Игнорировать все" msgstr "&Игнорировать все"
#: ../src\extra\SpellChecker\wx_ui.py:44 #: ../src\extra\SpellChecker\wx_ui.py:44
msgid "&Replace" msgid "&Replace"
msgstr "Заменить" msgstr "&Заменить"
#: ../src\extra\SpellChecker\wx_ui.py:45 #: ../src\extra\SpellChecker\wx_ui.py:45
msgid "R&eplace all" msgid "R&eplace all"
msgstr "Заменить все" msgstr "З&аменить все"
#: ../src\extra\SpellChecker\wx_ui.py:46 #: ../src\extra\SpellChecker\wx_ui.py:46
msgid "&Add to personal dictionary" msgid "&Add to personal dictionary"
msgstr "Добавить в личный словарь" msgstr "&Добавить в личный словарь"
#: ../src\extra\SpellChecker\wx_ui.py:79 #: ../src\extra\SpellChecker\wx_ui.py:79
msgid "" msgid ""
@@ -1587,9 +1579,8 @@ msgid "Open URL"
msgstr "Открыть ссылку" msgstr "Открыть ссылку"
#: ../src\keystrokeEditor\constants.py:25 #: ../src\keystrokeEditor\constants.py:25
#, fuzzy
msgid "View in Twitter" msgid "View in Twitter"
msgstr "Поиск в твиттере" msgstr "Посмотреть в Твиттере"
#: ../src\keystrokeEditor\constants.py:26 #: ../src\keystrokeEditor\constants.py:26
msgid "Increase volume by 5%" msgid "Increase volume by 5%"
@@ -1693,7 +1684,7 @@ msgstr "Просмотр беседы"
#: ../src\keystrokeEditor\constants.py:51 #: ../src\keystrokeEditor\constants.py:51
msgid "Check and download updates" msgid "Check and download updates"
msgstr "Проверить на наличие обновлений" msgstr "Проверить наличие обновлений"
#: ../src\keystrokeEditor\constants.py:52 #: ../src\keystrokeEditor\constants.py:52
msgid "" msgid ""
@@ -1708,9 +1699,8 @@ msgid "Opens the global settings dialogue"
msgstr "Открыть основные настройки" msgstr "Открыть основные настройки"
#: ../src\keystrokeEditor\constants.py:54 #: ../src\keystrokeEditor\constants.py:54
#, fuzzy
msgid "Opens the list manager" msgid "Opens the list manager"
msgstr "Менеджер Списков" msgstr "Открывает менеджер списков"
#: ../src\keystrokeEditor\constants.py:55 #: ../src\keystrokeEditor\constants.py:55
msgid "Opens the account settings dialogue" msgid "Opens the account settings dialogue"
@@ -1816,6 +1806,9 @@ msgid ""
"If you're sure that {0} isn't running, try deleting the file at {1}. If " "If you're sure that {0} isn't running, try deleting the file at {1}. If "
"you're unsure of how to do this, contact the {0} developers." "you're unsure of how to do this, contact the {0} developers."
msgstr "" msgstr ""
"{0} уже запущен. Закройте другой экземпляр, прежде чем запускать этот. Если "
"вы уверены, что {0} не активен, попробуйте удалить файл по адресу {1}. Если "
"вы не знаете, как это сделать, обратитесь к разработчикам {0}."
#: ../src\sessionmanager\wxUI.py:8 #: ../src\sessionmanager\wxUI.py:8
msgid "Session manager" msgid "Session manager"
@@ -1920,9 +1913,8 @@ msgid "public"
msgstr "Публичный" msgstr "Публичный"
#: ../src\sessions\twitter\session.py:169 #: ../src\sessions\twitter\session.py:169
#, fuzzy
msgid "There are no more items to retrieve in this buffer." msgid "There are no more items to retrieve in this buffer."
msgstr "Координаты отсутствуют" msgstr "В этом буфере больше нет элементов для извлечения."
#: ../src\sessions\twitter\session.py:215 #: ../src\sessions\twitter\session.py:215
msgid "%s failed. Reason: %s" msgid "%s failed. Reason: %s"
@@ -2048,7 +2040,7 @@ msgstr "Тренды"
#: ../src\wxUI\buffers\trends.py:18 #: ../src\wxUI\buffers\trends.py:18
msgid "Tweet about this trend" msgid "Tweet about this trend"
msgstr "Tweet о тренде" msgstr "Твитнуть о тренде"
#: ../src\wxUI\buffers\trends.py:19 ../src\wxUI\menus.py:96 #: ../src\wxUI\buffers\trends.py:19 ../src\wxUI\menus.py:96
msgid "Search topic" msgid "Search topic"
@@ -2265,6 +2257,8 @@ msgid ""
"{0} quit unexpectedly the last time it was run. If the problem persists, " "{0} quit unexpectedly the last time it was run. If the problem persists, "
"please report it to the {0} developers." "please report it to the {0} developers."
msgstr "" msgstr ""
"{0} неожиданно закончил свою работу при последнем запуске. Если проблема не "
"устранена, пожалуйста, сообщите об этом разработчикам {0}."
#: ../src\wxUI\dialogs\attach.py:9 #: ../src\wxUI\dialogs\attach.py:9
msgid "Add an attachment" msgid "Add an attachment"
@@ -2362,6 +2356,7 @@ msgstr "Использование удлинителя твитов (может
#: ../src\wxUI\dialogs\configuration.py:48 #: ../src\wxUI\dialogs\configuration.py:48
msgid "Remember state for mention all and long tweet" msgid "Remember state for mention all and long tweet"
msgstr "" msgstr ""
"Запоминать состояние для упоминаний всех пользователей и длинных твитов"
#: ../src\wxUI\dialogs\configuration.py:51 #: ../src\wxUI\dialogs\configuration.py:51
msgid "Keymap" msgid "Keymap"
@@ -2413,7 +2408,7 @@ msgstr ""
#: ../src\wxUI\dialogs\configuration.py:116 #: ../src\wxUI\dialogs\configuration.py:116
msgid "Retweet mode" msgid "Retweet mode"
msgstr "Стиль ретвита" msgstr "Режим ретвита"
#: ../src\wxUI\dialogs\configuration.py:122 #: ../src\wxUI\dialogs\configuration.py:122
msgid "Show screen names instead of full names" msgid "Show screen names instead of full names"
@@ -2429,11 +2424,11 @@ msgstr ""
#: ../src\wxUI\dialogs\configuration.py:134 #: ../src\wxUI\dialogs\configuration.py:134
msgid "Enable automatic speech feedback" msgid "Enable automatic speech feedback"
msgstr "" msgstr "Включить автоматический речевой вывод"
#: ../src\wxUI\dialogs\configuration.py:136 #: ../src\wxUI\dialogs\configuration.py:136
msgid "Enable automatic Braille feedback" msgid "Enable automatic Braille feedback"
msgstr "" msgstr "Включить автоматический брайлевский вывод"
#: ../src\wxUI\dialogs\configuration.py:144 #: ../src\wxUI\dialogs\configuration.py:144
msgid "Status" msgid "Status"
@@ -2515,7 +2510,7 @@ msgstr "Устройство записи"
#: ../src\wxUI\dialogs\configuration.py:299 #: ../src\wxUI\dialogs\configuration.py:299
msgid "Sound pack" msgid "Sound pack"
msgstr "Пакет звуков" msgstr "Звуковая схема"
#: ../src\wxUI\dialogs\configuration.py:305 #: ../src\wxUI\dialogs\configuration.py:305
msgid "Indicate audio tweets with sound" msgid "Indicate audio tweets with sound"
@@ -2539,7 +2534,7 @@ msgstr "API ключ SndUp"
#: ../src\wxUI\dialogs\configuration.py:353 #: ../src\wxUI\dialogs\configuration.py:353
msgid "{0} preferences" msgid "{0} preferences"
msgstr "Настройки {0}" msgstr "Параметры {0}"
#: ../src\wxUI\dialogs\configuration.py:364 #: ../src\wxUI\dialogs\configuration.py:364
msgid "Proxy" msgid "Proxy"
@@ -2819,9 +2814,8 @@ msgid "Source: "
msgstr "Клиент" msgstr "Клиент"
#: ../src\wxUI\dialogs\message.py:342 ../src\wxUI\dialogs\message.py:420 #: ../src\wxUI\dialogs\message.py:342 ../src\wxUI\dialogs\message.py:420
#, fuzzy
msgid "Date: " msgid "Date: "
msgstr "Дата" msgstr "Дата: "
#: ../src\wxUI\dialogs\message.py:405 #: ../src\wxUI\dialogs\message.py:405
msgid "View" msgid "View"
@@ -2890,7 +2884,7 @@ msgstr "Детали"
#: ../src\wxUI\dialogs\show_user.py:16 #: ../src\wxUI\dialogs\show_user.py:16
msgid "&Go to URL" msgid "&Go to URL"
msgstr "Перейти по ссылке" msgstr "&Перейти по ссылке"
#: ../src\wxUI\dialogs\trends.py:12 #: ../src\wxUI\dialogs\trends.py:12
msgid "View trending topics" msgid "View trending topics"
@@ -3031,12 +3025,11 @@ msgstr "н&е нравится"
#: ../src\wxUI\menus.py:15 ../src\wxUI\menus.py:35 ../src\wxUI\menus.py:51 #: ../src\wxUI\menus.py:15 ../src\wxUI\menus.py:35 ../src\wxUI\menus.py:51
msgid "&Open URL" msgid "&Open URL"
msgstr "Открыть ссылку" msgstr "&Открыть ссылку"
#: ../src\wxUI\menus.py:17 ../src\wxUI\menus.py:53 ../src\wxUI\menus.py:86 #: ../src\wxUI\menus.py:17 ../src\wxUI\menus.py:53 ../src\wxUI\menus.py:86
#, fuzzy
msgid "&Open in Twitter" msgid "&Open in Twitter"
msgstr "Поиск в твиттере" msgstr "Открыть в &Твиттере"
#: ../src\wxUI\menus.py:19 ../src\wxUI\menus.py:37 ../src\wxUI\menus.py:55 #: ../src\wxUI\menus.py:19 ../src\wxUI\menus.py:37 ../src\wxUI\menus.py:55
msgid "&Play audio" msgid "&Play audio"
@@ -3246,7 +3239,7 @@ msgstr "Сайт {0}"
#: ../src\wxUI\view.py:77 #: ../src\wxUI\view.py:77
msgid "Get soundpacks for TWBlue" msgid "Get soundpacks for TWBlue"
msgstr "" msgstr "Получить звуковые схемы для TWBlue"
#: ../src\wxUI\view.py:78 #: ../src\wxUI\view.py:78
msgid "About &{0}" msgid "About &{0}"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,7 @@ log = logging.getLogger("main")
def setup(): def setup():
log.debug("Starting " + application.name + " %s" % (application.version,)) log.debug("Starting " + application.name + " %s" % (application.version,))
config.setup() config.setup()
proxy_setup()
log.debug("Using %s %s" % (platform.system(), platform.architecture()[0])) log.debug("Using %s %s" % (platform.system(), platform.architecture()[0]))
log.debug("Application path is %s" % (paths.app_path(),)) log.debug("Application path is %s" % (paths.app_path(),))
log.debug("config path is %s" % (paths.config_path(),)) log.debug("config path is %s" % (paths.config_path(),))
@@ -77,6 +78,7 @@ def setup():
from controller import mainController from controller import mainController
from sessionmanager import sessionManager from sessionmanager import sessionManager
app = widgetUtils.mainLoopObject() app = widgetUtils.mainLoopObject()
check_pid()
if system == "Windows": if system == "Windows":
if config.app["app-settings"]["donation_dialog_displayed"] == False: if config.app["app-settings"]["donation_dialog_displayed"] == False:
donation() donation()
@@ -90,7 +92,6 @@ def setup():
if hasattr(sm.view, "destroy"): if hasattr(sm.view, "destroy"):
sm.view.destroy() sm.view.destroy()
del sm del sm
check_pid()
r = mainController.Controller() r = mainController.Controller()
r.view.show() r.view.show()
r.do_work() r.do_work()
@@ -101,6 +102,18 @@ def setup():
GLib.idle_add(r.start) GLib.idle_add(r.start)
app.run() app.run()
def proxy_setup():
if config.app["proxy"]["server"] != "" and config.app["proxy"]["type"] > 0:
log.debug("Loading proxy settings")
proxy_url = config.app["proxy"]["server"] + ":" + str(config.app["proxy"]["port"])
if config.app["proxy"]["user"] != "" and config.app["proxy"]["password"] != "":
proxy_url = config.app["proxy"]["user"] + ":" + config.app["proxy"]["password"] + "@" + proxy_url
elif config.app["proxy"]["user"] != "" and config.proxyTypes[config.app["proxy"]["type"]] in ["socks4", "socks4a"]:
proxy_url = config.app["proxy"]["user"] + "@" + proxy_url
proxy_url = config.proxyTypes[config.app["proxy"]["type"]] + "://" + proxy_url
os.environ["HTTP_PROXY"] = proxy_url
os.environ["HTTPS_PROXY"] = proxy_url
def donation(): def donation():
dlg = commonMessageDialogs.donation() dlg = commonMessageDialogs.donation()
if dlg == widgetUtils.YES: if dlg == widgetUtils.YES:

View File

@@ -17,10 +17,10 @@ class list(object):
# self.set_size() # self.set_size()
def set_windows_size(self, column, characters_max): def set_windows_size(self, column, characters_max):
# it = wx.ListItem() # it = wx.ListItem()
# dc = wx.WindowDC(self.list) # dc = wx.WindowDC(self.list)
# dc.SetFont(it.GetFont()) # dc.SetFont(it.GetFont())
# (x, y) = dc.GetTextExtent("r"*characters_max) # (x, y) = dc.GetTextExtent("r"*characters_max)
self.list.SetColumnWidth(column, characters_max*2) self.list.SetColumnWidth(column, characters_max*2)
def set_size(self): def set_size(self):

View File

@@ -1,6 +1,7 @@
# -*- coding: cp1252 # -*- coding: cp1252
from __future__ import unicode_literals from __future__ import unicode_literals
import sys, os import sys, os
import application
def restart_program(): def restart_program():
""" Function that restarts the application if is executed.""" """ Function that restarts the application if is executed."""
@@ -9,4 +10,7 @@ def restart_program():
args.insert(0, sys.executable) args.insert(0, sys.executable)
if sys.platform == 'win32': if sys.platform == 'win32':
args = ['"%s"' % arg for arg in args] args = ['"%s"' % arg for arg in args]
pidpath = os.path.join(os.getenv("temp"), "{}.pid".format(application.name))
if os.path.exists(pidpath):
os.remove(pidpath)
os.execv(sys.executable, args) os.execv(sys.executable, args)

View File

@@ -1,21 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from future import standard_library
standard_library.install_aliases()
import logging import logging
log = logging.getLogger("mysc.thread_utils") log = logging.getLogger("mysc.thread_utils")
import threading import threading
import wx import wx
from pubsub import pub from pubsub import pub
from twython import TwythonRateLimitError
def call_threaded(func, *args, **kwargs): def call_threaded(func, *args, **kwargs):
#Call the given function in a daemonized thread and return the thread. #Call the given function in a daemonized thread and return the thread.
def new_func(*a, **k): def new_func(*a, **k):
try: try:
func(*a, **k) func(*a, **k)
except TwythonRateLimitError:
pass
except: except:
log.exception("Thread %d with function %r, args of %r, and kwargs of %r failed to run." % (threading.current_thread().ident, func, a, k)) log.exception("Thread %d with function %r, args of %r, and kwargs of %r failed to run." % (threading.current_thread().ident, func, a, k))
# pass # pass
@@ -23,17 +17,3 @@ def call_threaded(func, *args, **kwargs):
thread.daemon = True thread.daemon = True
thread.start() thread.start()
return thread return thread
def stream_threaded(func, *args, **kwargs):
def new_func(*a, **k):
try:
func(**k)
except Exception as msg:
log.error("Error in stream with args: %r" % (a,))
log.error(msg.message)
pub.sendMessage("stream-error", session=a[0])
thread = threading.Thread(target=new_func, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return thread

View File

@@ -20,8 +20,8 @@ def setup ():
global speaker global speaker
logging.debug("Initializing output subsystem.") logging.debug("Initializing output subsystem.")
try: try:
# speaker = speech.Speaker(speech.outputs.Sapi5()) # speaker = speech.Speaker(speech.outputs.Sapi5())
# else: # else:
speaker = outputs.auto.Auto() speaker = outputs.auto.Auto()
except: except:
return logging.exception("Output: Error during initialization.") return logging.exception("Output: Error during initialization.")

View File

@@ -23,7 +23,7 @@ def config_path():
elif mode == "installed": elif mode == "installed":
path = os.path.join(data_path(), "config") path = os.path.join(data_path(), "config")
if not os.path.exists(path): if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,)) # log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path) os.mkdir(path)
return path return path
@@ -35,11 +35,11 @@ def logs_path():
elif mode == "installed": elif mode == "installed":
path = os.path.join(data_path(), "logs") path = os.path.join(data_path(), "logs")
if not os.path.exists(path): if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,)) # log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path) os.mkdir(path)
return path return path
def data_path(app_name='socializer'): def data_path(app_name='TW Blue'):
if platform.system() == "Windows": if platform.system() == "Windows":
data_path = os.path.join(os.getenv("AppData"), app_name) data_path = os.path.join(os.getenv("AppData"), app_name)
else: else:
@@ -62,6 +62,6 @@ def com_path():
elif mode == "installed": elif mode == "installed":
path = os.path.join(data_path(), "com_cache") path = os.path.join(data_path(), "com_cache")
if not os.path.exists(path): if not os.path.exists(path):
# log.debug("%s path does not exist, creating..." % (path,)) # log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path) os.mkdir(path)
return path return path

18
src/run_tests.py Normal file
View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
import unittest
testmodules = ["test.test_cache"]
suite = unittest.TestSuite()
for t in testmodules:
try:
# If the module defines a suite() function, call it to get the suite.
mod = __import__(t, globals(), locals(), ['suite'])
suitefn = getattr(mod, 'suite')
suite.addTest(suitefn())
except (ImportError, AttributeError):
# else, just load all the test cases from the module.
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))
unittest.TextTestRunner(verbosity=2).run(suite)

View File

@@ -16,13 +16,13 @@ def setup():
manager = sessionManager() manager = sessionManager()
class sessionManager(object): class sessionManager(object):
# def __init__(self): # def __init__(self):
# FILE = "sessions.conf" # FILE = "sessions.conf"
# SPEC = "app-configuration.defaults" # SPEC = "app-configuration.defaults"
# try: # try:
# self.main = Configuration(paths.config_path(FILE), paths.app_path(SPEC)) # self.main = Configuration(paths.config_path(FILE), paths.app_path(SPEC))
# except ConfigurationResetException: # except ConfigurationResetException:
# pass # pass
def get_current_session(self): def get_current_session(self):
if self.is_valid(config.app["sessions"]["current_session"]): if self.is_valid(config.app["sessions"]["current_session"]):

View File

@@ -1,8 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import str
from builtins import object
import shutil import shutil
import widgetUtils import widgetUtils
import platform import platform
@@ -21,7 +17,7 @@ from sessions.twitter import session
from . import manager from . import manager
import config_utils import config_utils
import config import config
from tweepy.errors import TweepyException
log = logging.getLogger("sessionmanager.sessionManager") log = logging.getLogger("sessionmanager.sessionManager")
class sessionManagerController(object): class sessionManagerController(object):
@@ -85,11 +81,18 @@ class sessionManagerController(object):
s = session.Session(i) s = session.Session(i)
s.get_configuration() s.get_configuration()
if i not in config.app["sessions"]["ignored_sessions"]: if i not in config.app["sessions"]["ignored_sessions"]:
try:
s.login() s.login()
except TweepyException:
self.show_auth_error(s.settings["twitter"]["user_name"])
continue
sessions.sessions[i] = s sessions.sessions[i] = s
self.new_sessions[i] = s self.new_sessions[i] = s
# self.view.destroy() # self.view.destroy()
def show_auth_error(self, user_name):
error = view.auth_error(user_name)
def manage_new_account(self, *args, **kwargs): def manage_new_account(self, *args, **kwargs):
if self.view.new_account_dialog() == widgetUtils.YES: if self.view.new_account_dialog() == widgetUtils.YES:
location = (str(time.time())[-6:]) location = (str(time.time())[-6:])

View File

@@ -76,3 +76,6 @@ class sessionManagerWindow(wx.Dialog):
def destroy(self): def destroy(self):
self.Destroy() self.Destroy()
def auth_error(user_name):
return wx.MessageDialog(None, _("TWBlue is unable to authenticate the account for {} in Twitter. It might be due to an invalid or expired token, revoqued access to the application, or after an account reactivation. Please remove the account manually from your Twitter sessions in order to stop seeing this message.").format(user_name,), _("Authentication error for session {}").format(user_name,), wx.OK).ShowModal()

View File

@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" A base class to be derived in possible new sessions for TWBlue and services.""" """ A base class to be derived in possible new sessions for TWBlue and services."""
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import str
from builtins import object
import os import os
import paths import paths
import output import output
@@ -11,9 +7,8 @@ import time
import sound import sound
import logging import logging
import config_utils import config_utils
import shelve import sqlitedict
import application import application
import os
from . import session_exceptions as Exceptions from . import session_exceptions as Exceptions
log = logging.getLogger("sessionmanager.session") log = logging.getLogger("sessionmanager.session")
@@ -48,6 +43,10 @@ class baseSession(object):
self.logged = False self.logged = False
self.settings = None self.settings = None
self.db={} self.db={}
# Config specification file.
self.config_spec = "conf.defaults"
# Session type.
self.type = "base"
@property @property
def is_logged(self): def is_logged(self):
@@ -57,9 +56,9 @@ class baseSession(object):
""" Get settings for a session.""" """ Get settings for a session."""
file_ = "%s/session.conf" % (self.session_id,) file_ = "%s/session.conf" % (self.session_id,)
log.debug("Creating config file %s" % (file_,)) log.debug("Creating config file %s" % (file_,))
self.settings = config_utils.load_config(os.path.join(paths.config_path(), file_), os.path.join(paths.app_path(), "Conf.defaults")) self.settings = config_utils.load_config(os.path.join(paths.config_path(), file_), os.path.join(paths.app_path(), self.config_spec))
self.init_sound() self.init_sound()
self.deshelve() self.load_persistent_data()
def init_sound(self): def init_sound(self):
try: self.sound = sound.soundSystem(self.settings["sound"]) try: self.sound = sound.soundSystem(self.settings["sound"])
@@ -73,48 +72,88 @@ class baseSession(object):
def authorise(self): def authorise(self):
pass pass
def shelve(self): def get_sized_buffer(self, buffer, size, reversed=False):
"""Shelve the database to allow for persistance.""" """ Returns a list with the amount of items specified by size."""
shelfname=os.path.join(paths.config_path(), str(self.session_id)+"/cache") if isinstance(buffer, list) and size != -1 and len(buffer) > size:
if self.settings["general"]["persist_size"] == 0: log.debug("Requesting {} items from a list of {} items. Reversed mode: {}".format(size, len(buffer), reversed))
if os.path.exists(shelfname+".dat"): if reversed == True:
os.remove(shelfname+".dat") return buffer[:size]
return
try:
if not os.path.exists(shelfname+".dat"):
output.speak("Generating database, this might take a while.",True)
shelf=shelve.open(os.path.join(paths.config_path(), shelfname),'c')
for key,value in list(self.db.items()):
if type(key) != str and type(key) != str:
output.speak("Uh oh, while shelving the database, a key of type " + str(type(key)) + " has been found. It will be converted to type str, but this will cause all sorts of problems on deshelve. Please bring this to the attention of the " + application.name + " developers immediately. More information about the error will be written to the error log.",True)
log.error("Uh oh, " + str(key) + " is of type " + str(type(key)) + "!")
if type(value) == list and self.settings["general"]["persist_size"] != -1 and len(value) > self.settings["general"]["persist_size"]:
shelf[key]=value[self.settings["general"]["persist_size"]:]
else: else:
shelf[key]=value return buffer[len(buffer)-size:]
shelf.close() else:
except: return buffer
output.speak("An exception occurred while shelving the " + application.name + " database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the " + application.name + " developers.",True)
log.exception("Exception while shelving" + shelfname)
os.remove(shelfname)
def deshelve(self): def save_persistent_data(self):
"""Import a shelved database.""" """ Save the data to a persistent sqlite backed file. ."""
shelfname=os.path.join(paths.config_path(), str(self.session_id)+"/cache") dbname=os.path.join(paths.config_path(), str(self.session_id), "cache.db")
log.debug("Saving storage information...")
# persist_size set to 0 means not saving data actually.
if self.settings["general"]["persist_size"] == 0: if self.settings["general"]["persist_size"] == 0:
if os.path.exists(shelfname+".dat"): if os.path.exists(dbname):
os.remove(shelfname+".dat") os.remove(dbname)
return return
# Let's check if we need to create a new SqliteDict object (when loading db in memory) or we just need to call to commit in self (if reading from disk).db.
# If we read from disk, we cannot modify the buffer size here as we could damage the app's integrity.
# We will modify buffer's size (managed by persist_size) upon loading the db into memory in app startup.
if self.settings["general"]["load_cache_in_memory"] and isinstance(self.db, dict):
log.debug("Opening database to dump memory contents...")
db=sqlitedict.SqliteDict(dbname, 'c')
for k in self.db.keys():
sized_buff = self.get_sized_buffer(self.db[k], self.settings["general"]["persist_size"], self.settings["general"]["reverse_timelines"])
db[k] = sized_buff
db.commit(blocking=True)
db.close()
log.debug("Data has been saved in the database.")
else:
try: try:
shelf=shelve.open(os.path.join(paths.config_path(), shelfname),'c') log.debug("Syncing new data to disk...")
for key,value in list(shelf.items()): if hasattr(self.db, "commit"):
self.db[key]=value self.db.commit()
shelf.close()
except: except:
output.speak("An exception occurred while deshelving the " + application.name + " database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the " + application.name + " developers.",True) output.speak(_("An exception occurred while saving the {app} database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the {app} developers.").format(app=application.name),True)
log.exception("Exception while deshelving" + shelfname) log.exception("Exception while saving {}".format(dbname))
os.remove(dbname)
def load_persistent_data(self):
"""Import data from a database file from user config."""
log.debug("Loading storage data...")
dbname=os.path.join(paths.config_path(), str(self.session_id), "cache.db")
# If persist_size is set to 0, we should remove the db file as we are no longer going to save anything.
if self.settings["general"]["persist_size"] == 0:
if os.path.exists(dbname):
os.remove(dbname)
# Let's return from here, as we are not loading anything.
return
# try to load the db file.
try: try:
os.remove(shelfname) log.debug("Opening database...")
db=sqlitedict.SqliteDict(os.path.join(paths.config_path(), dbname), 'c')
# If load_cache_in_memory is set to true, we will load the whole database into memory for faster access.
# This is going to be faster when retrieving specific objects, at the cost of more memory.
# Setting this to False will read the objects from database as they are needed, which might be slower for bigger datasets.
if self.settings["general"]["load_cache_in_memory"]:
log.debug("Loading database contents into memory...")
for k in db.keys():
self.db[k] = db[k]
db.commit(blocking=True)
db.close()
log.debug("Contents were loaded successfully.")
else:
log.debug("Instantiating database from disk.")
self.db = db
# We must make sure we won't load more than the amount of buffer specified.
log.debug("Checking if we will load all content...")
for k in self.db.keys():
sized_buffer = self.get_sized_buffer(self.db[k], self.settings["general"]["persist_size"], self.settings["general"]["reverse_timelines"])
self.db[k] = sized_buffer
if self.db.get("cursors") == None:
cursors = dict(direct_messages=-1)
self.db["cursors"] = cursors
except:
output.speak(_("An exception occurred while loading the {app} database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the {app} developers.").format(app=application.name), True)
log.exception("Exception while loading {}".format(dbname))
try:
os.remove(dbname)
except: except:
pass pass

View File

@@ -1,11 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from future import standard_library
standard_library.install_aliases()
from builtins import str
from builtins import chr
from builtins import range
import platform import platform
system = platform.system() system = platform.system()
from . import utils from . import utils
@@ -20,7 +13,6 @@ import config
from .long_tweets import twishort, tweets from .long_tweets import twishort, tweets
log = logging.getLogger("compose") log = logging.getLogger("compose")
def StripChars(s): def StripChars(s):
"""Converts any html entities in s to their unicode-decoded equivalents and returns a string.""" """Converts any html entities in s to their unicode-decoded equivalents and returns a string."""
entity_re = re.compile(r"&(#\d+|\w+);") entity_re = re.compile(r"&(#\d+|\w+);")
@@ -39,129 +31,124 @@ chars = "abcdefghijklmnopqrstuvwxyz"
def compose_tweet(tweet, db, relative_times, show_screen_names=False, session=None): def compose_tweet(tweet, db, relative_times, show_screen_names=False, session=None):
""" It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is.""" """ It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is."""
if system == "Windows": if system == "Windows":
original_date = arrow.get(tweet["created_at"], "ddd MMM DD H:m:s Z YYYY", locale="en") original_date = arrow.get(tweet.created_at, locale="en")
if relative_times == True: if relative_times == True:
ts = original_date.humanize(locale=languageHandler.curLang[:2]) ts = original_date.humanize(locale=languageHandler.curLang[:2])
else: else:
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2]) ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
else: else:
ts = tweet["created_at"] ts = tweet.created_at
if "message" in tweet: if hasattr(tweet, "message"):
value = "message" value = "message"
elif "full_text" in tweet: elif hasattr(tweet, "full_text"):
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
if "retweeted_status" in tweet and value != "message": if hasattr(tweet, "retweeted_status") and value != "message":
text = StripChars(tweet["retweeted_status"][value]) text = utils.clean_mentions(StripChars(getattr(tweet.retweeted_status, value)))
else: else:
text = StripChars(tweet[value]) text = utils.clean_mentions(StripChars(getattr(tweet, value)))
if show_screen_names: if show_screen_names:
user = tweet["user"]["screen_name"] user = session.get_user(tweet.user).screen_name
else: else:
user = tweet["user"]["name"] user = session.get_user(tweet.user).name
source = re.sub(r"(?s)<.*?>", "", tweet["source"]) source = re.sub(r"(?s)<.*?>", "", tweet.source)
if "retweeted_status" in tweet: if hasattr(tweet, "retweeted_status"):
if ("message" in tweet) == False and tweet["retweeted_status"]["is_quote_status"] == False: if hasattr(tweet, "message") == False and hasattr(tweet.retweeted_status, "is_quote_status") == False:
text = "RT @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], text) text = "RT @%s: %s" % (session.get_user(tweet.retweeted_status.user).screen_name, text)
elif tweet["retweeted_status"]["is_quote_status"]: elif hasattr(tweet.retweeted_status, "is_quote_status"):
text = "%s" % (text) text = "%s" % (text)
else: else:
text = "RT @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], text) text = "RT @%s: %s" % (session.get_user(tweet.retweeted_status.user).screen_name, text)
if ("message" in tweet) == False: if not hasattr(tweet, "message"):
urls = utils.find_urls_in_text(text) if hasattr(tweet, "retweeted_status"):
if "retweeted_status" in tweet: if hasattr(tweet.retweeted_status, "entities"):
for url in range(0, len(urls)): text = utils.expand_urls(text, tweet.retweeted_status.entities)
try:
text = text.replace(urls[url], tweet["retweeted_status"]["entities"]["urls"][url]["expanded_url"])
except: pass
else: else:
for url in range(0, len(urls)): if hasattr(tweet, "entities"):
try: text = utils.expand_urls(text, tweet.entities)
text = text.replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"])
except: pass
if config.app['app-settings']['handle_longtweets']: pass if config.app['app-settings']['handle_longtweets']: pass
return [user+", ", text, ts+", ", source] return [user+", ", text, ts+", ", source]
def compose_direct_message(item, db, relative_times, show_screen_names=False, session=None): def compose_direct_message(item, db, relative_times, show_screen_names=False, session=None):
# for a while this function will be together with compose_dm.
# this one composes direct messages based on events (new API Endpoints).
if system == "Windows": if system == "Windows":
# Let's remove the last 3 digits in the timestamp string. # Let's remove the last 3 digits in the timestamp string.
# Twitter sends their "epoch" timestamp with 3 digits for milliseconds and arrow doesn't like it. # Twitter sends their "epoch" timestamp with 3 digits for milliseconds and arrow doesn't like it.
original_date = arrow.get(int(item["created_timestamp"][:-3])) original_date = arrow.get(int(item.created_timestamp))
if relative_times == True: if relative_times == True:
ts = original_date.humanize(locale=languageHandler.curLang[:2]) ts = original_date.humanize(locale=languageHandler.curLang[:2])
else: else:
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2]) ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
else: else:
ts = item["created_timestamp"] ts = item.created_timestamp
text = StripChars(item["message_create"]["message_data"]["text"]) text = StripChars(item.message_create["message_data"]["text"])
source = "DM" source = "DM"
sender = session.get_user(item["message_create"]["sender_id"]) sender = session.get_user(item.message_create["sender_id"])
if db["user_name"] == sender["screen_name"]: if db["user_name"] == sender.screen_name:
if show_screen_names: if show_screen_names:
user = _(u"Dm to %s ") % (session.get_user(item["message_create"]["target"]["recipient_id"])["screen_name"]) user = _(u"Dm to %s ") % (session.get_user(item.message_create["target"]["recipient_id"]).screen_name)
else: else:
user = _(u"Dm to %s ") % (session.get_user(item["message_create"]["target"]["recipient_id"])["name"]) user = _(u"Dm to %s ") % (session.get_user(item.message_create["target"]["recipient_id"]).name)
else: else:
if show_screen_names: if show_screen_names:
user = sender["screen_name"] user = sender.screen_name
else: else:
user = sender["name"] user = sender.name
if text[-1] in chars: text=text+"." if text[-1] in chars: text=text+"."
urls = utils.find_urls_in_text(text) text = utils.expand_urls(text, item.message_create["message_data"]["entities"])
for url in range(0, len(urls)):
try: text = text.replace(urls[url], item["message_create"]["message_data"]["entities"]["urls"][url]["expanded_url"])
except IndexError: pass
return [user+", ", text, ts+", ", source] return [user+", ", text, ts+", ", source]
def compose_quoted_tweet(quoted_tweet, original_tweet, show_screen_names=False, session=None): def compose_quoted_tweet(quoted_tweet, original_tweet, show_screen_names=False, session=None):
""" It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is.""" """ It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is."""
if "retweeted_status" in quoted_tweet: if hasattr(quoted_tweet, "retweeted_status"):
if "full_text" in quoted_tweet["retweeted_status"]: if hasattr(quoted_tweet.retweeted_status, "full_text"):
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
text = StripChars(quoted_tweet["retweeted_status"][value]) text = StripChars(getattr(quoted_tweet.retweeted_status, value))
else: else:
if "full_text" in quoted_tweet: if hasattr(quoted_tweet, "full_text"):
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
text = StripChars(quoted_tweet[value]) text = utils.clean_mentions(StripChars(getattr(quoted_tweet, value)))
if show_screen_names: if show_screen_names:
quoting_user = quoted_tweet["user"]["screen_name"] quoting_user = session.get_user(quoted_tweet.user).screen_name
else: else:
quoting_user = quoted_tweet["user"]["name"] quoting_user = session.get_user(quoted_tweet.user).name
source = re.sub(r"(?s)<.*?>", "", quoted_tweet["source"]) source = quoted_tweet.source
if "retweeted_status" in quoted_tweet: if hasattr(quoted_tweet, "retweeted_status"):
text = "rt @%s: %s" % (quoted_tweet["retweeted_status"]["user"]["screen_name"], text) text = "rt @%s: %s" % (session.get_user(quoted_tweet.retweeted_status.user).screen_name, text)
if text[-1] in chars: text=text+"." if text[-1] in chars: text=text+"."
original_user = original_tweet["user"]["screen_name"] original_user = session.get_user(original_tweet.user).screen_name
if "message" in original_tweet: if hasattr(original_tweet, "message"):
original_text = original_tweet["message"] original_text = original_tweet.message
elif "full_text" in original_tweet: elif hasattr(original_tweet, "full_text"):
original_text = StripChars(original_tweet["full_text"]) original_text = utils.clean_mentions(StripChars(original_tweet.full_text))
else: else:
original_text = StripChars(original_tweet["text"]) original_text = utils.clean_mentions(StripChars(original_tweet.text))
quoted_tweet["message"] = _(u"{0}. Quoted tweet from @{1}: {2}").format( text, original_user, original_text) quoted_tweet.message = _(u"{0}. Quoted tweet from @{1}: {2}").format( text, original_user, original_text)
quoted_tweet = tweets.clear_url(quoted_tweet) quoted_tweet = tweets.clear_url(quoted_tweet)
quoted_tweet["entities"]["urls"].extend(original_tweet["entities"]["urls"]) if hasattr(original_tweet, "entities") and original_tweet.entities.get("urls"):
if hasattr(quoted_tweet, "entities") == False:
quoted_tweet.entities = {}
if quoted_tweet.entities.get("urls") == None:
quoted_tweet.entities["urls"] = []
quoted_tweet.entities["urls"].extend(original_tweet.entities["urls"])
return quoted_tweet return quoted_tweet
def compose_followers_list(tweet, db, relative_times=True, show_screen_names=False, session=None): def compose_followers_list(tweet, db, relative_times=True, show_screen_names=False, session=None):
if system == "Windows": if system == "Windows":
original_date = arrow.get(tweet["created_at"], "ddd MMM D H:m:s Z YYYY", locale="en") original_date = arrow.get(tweet.created_at, locale="en")
if relative_times == True: if relative_times == True:
ts = original_date.humanize(locale=languageHandler.curLang[:2]) ts = original_date.humanize(locale=languageHandler.curLang[:2])
else: else:
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2]) ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
else: else:
ts = tweet["created_at"] ts = tweet.created_at
if "status" in tweet: if hasattr(tweet, "status"):
if len(tweet["status"]) > 4 and system == "Windows": if system == "Windows":
original_date2 = arrow.get(tweet["status"]["created_at"], "ddd MMM D H:m:s Z YYYY", locale="en") original_date2 = arrow.get(tweet.status.created_at, locale="en")
if relative_times: if relative_times:
ts2 = original_date2.humanize(locale=languageHandler.curLang[:2]) ts2 = original_date2.humanize(locale=languageHandler.curLang[:2])
else: else:
@@ -170,14 +157,14 @@ def compose_followers_list(tweet, db, relative_times=True, show_screen_names=Fal
ts2 = _("Unavailable") ts2 = _("Unavailable")
else: else:
ts2 = _("Unavailable") ts2 = _("Unavailable")
return [_(u"%s (@%s). %s followers, %s friends, %s tweets. Last tweeted %s. Joined Twitter %s") % (tweet["name"], tweet["screen_name"], tweet["followers_count"], tweet["friends_count"], tweet["statuses_count"], ts2, ts)] return [_(u"%s (@%s). %s followers, %s friends, %s tweets. Last tweeted %s. Joined Twitter %s") % (tweet.name, tweet.screen_name, tweet.followers_count, tweet.friends_count, tweet.statuses_count, ts2, ts)]
def compose_list(list): def compose_list(list):
name = list["name"] name = list.name
if list["description"] == None: description = _(u"No description available") if list.description == None: description = _(u"No description available")
else: description = list["description"] else: description = list.description
user = list["user"]["name"] user = list.user.name
members = str(list["member_count"]) members = str(list.member_count)
if list["mode"] == "private": status = _(u"private") if list.mode == "private": status = _(u"private")
else: status = _(u"public") else: status = _(u"public")
return [name, description, user, members, status] return [name, description, user, members, status]

View File

@@ -23,30 +23,30 @@ def is_long(tweet):
""" Check if the passed tweet contains a quote in its metadata. """ Check if the passed tweet contains a quote in its metadata.
tweet dict: a tweet dictionary. tweet dict: a tweet dictionary.
returns True if a quote is detected, False otherwise.""" returns True if a quote is detected, False otherwise."""
if "quoted_status_id" in tweet and "quoted_status" in tweet: if hasattr(tweet, "quoted_status_id") and hasattr(tweet, "quoted_status"):
return tweet["quoted_status_id"] return tweet.quoted_status_id
elif "retweeted_status" in tweet and "quoted_status_id" in tweet["retweeted_status"] and "quoted_status" in tweet["retweeted_status"]: elif hasattr(tweet, "retweeted_status") and hasattr(tweet.retweeted_status, "quoted_status_id") and hasattr(tweet.retweeted_status, "quoted_status"):
return tweet["retweeted_status"]["quoted_status_id"] return tweet.retweeted_status.quoted_status_id
return False return False
def clear_url(tweet): def clear_url(tweet):
""" Reads data from a quoted tweet and removes the link to the Status from the tweet's text. """ Reads data from a quoted tweet and removes the link to the Status from the tweet's text.
tweet dict: a tweet dictionary. tweet dict: a tweet dictionary.
returns a tweet dictionary without the URL to the status ID in its text to display.""" returns a tweet dictionary without the URL to the status ID in its text to display."""
if "retweeted_status" in tweet: if hasattr(tweet, "retweeted_status"):
if "full_text" in tweet["retweeted_status"]: if hasattr(tweet.retweeted_status, "full_text"):
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
urls = utils.find_urls_in_text(tweet["retweeted_status"][value]) urls = utils.find_urls_in_text(getattr(tweet.retweeted_status, value))
try: tweet["message"] = tweet["message"].replace(urls[-1], "") try: tweet.message = tweet.message.replace(urls[-1], "")
except IndexError: pass except IndexError: pass
else: else:
if "full_text" in tweet: if hasattr(tweet, "full_text"):
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
urls = utils.find_urls_in_text(tweet[value]) urls = utils.find_urls_in_text(getattr(tweet, value))
try: tweet["message"] = tweet["message"].replace(urls[-1], "") try: tweet.message = tweet.message.replace(urls[-1], "")
except IndexError: pass except IndexError: pass
return tweet return tweet

View File

@@ -40,25 +40,19 @@ def is_long(tweet):
""" Check if the passed tweet is made with Twishort. """ Check if the passed tweet is made with Twishort.
returns True if is a long tweet, False otherwise.""" returns True if is a long tweet, False otherwise."""
long = False long = False
for url in range(0, len(tweet["entities"]["urls"])): if hasattr(tweet, "entities") and tweet.entities.get("urls"):
for url in range(0, len(tweet.entities["urls"])):
try: try:
if tweet["entities"]["urls"][url] != None and "twishort.com" in tweet["entities"]["urls"][url]["expanded_url"]: if tweet.entities["urls"][url] != None and "twishort.com" in tweet.entities["urls"][url]["expanded_url"]:
long = get_twishort_uri(tweet["entities"]["urls"][url]["expanded_url"]) long = get_twishort_uri(tweet.entities["urls"][url]["expanded_url"])
except IndexError: except IndexError:
pass pass
# sometimes Twitter returns URL's with None objects, so let's take it. # sometimes Twitter returns URL's with None objects, so let's take it.
# see https://github.com/manuelcortez/TWBlue/issues/103 # see https://github.com/manuelcortez/TWBlue/issues/103
except TypeError: except TypeError:
pass pass
if long == False and "retweeted_status" in tweet: if long == False and hasattr(tweet, "retweeted_status"):
for url in range(0, len(tweet["retweeted_status"]["entities"]["urls"])): return is_long(tweet.retweeted_status)
try:
if tweet["retweeted_status"]["entities"]["urls"][url] != None and "twishort.com" in tweet["retweeted_status"]["entities"]["urls"][url]["expanded_url"]:
long = get_twishort_uri(tweet["retweeted_status"]["entities"]["urls"][url]["expanded_url"])
except IndexError:
pass
except TypeError:
pass
return long return long
def get_full_text(uri): def get_full_text(uri):

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
""" Strips unneeded tweet information in order to store tweet objects by using less memory. This is especially useful when buffers start to contain more than a certain amount of items. """
from tweepy.models import Status
def reduce_tweet(tweet):
""" generates a new Tweet model with the fields we currently need, excluding everything else including null values and empty collections. """
allowed_values = ["created_at", "id", "full_text", "text", "message", "in_reply_to_status_id", "in_reply_to_user_id", "is_quote_status", "lang", "source", "coordinates", "quoted_status_id", "extended_entities"]
allowed_entities = ["hashtags", "media", "urls", "user_mentions", "polls"]
status_dict = {}
for key in allowed_values:
if tweet._json.get(key):
status_dict[key] = tweet._json[key]
entities = dict()
for key in allowed_entities:
if tweet._json["entities"].get(key) and tweet._json["entities"].get(key) != None:
entities[key] = tweet._json["entities"][key]
status_dict["entities"] = entities
# If tweet comes from the cached database, it does not include an API, so we can pass None here as we do not use that reference to tweepy's API.
if hasattr(tweet, "_api"):
api = tweet._api
else:
api = None
status = Status().parse(api=api, json=status_dict)
# Quotes and retweets are different objects. So we parse a new tweet when we have a quoted or retweeted status here.
if tweet._json.get("quoted_status"):
quoted_tweet = reduce_tweet(tweet.quoted_status)
status.quoted_status = quoted_tweet
if tweet._json.get("retweeted_status"):
retweeted_tweet = reduce_tweet(tweet.retweeted_status)
status.retweeted_status = retweeted_tweet
# Adds user ID to here so we can reference it later.
# Sometimes, the conversations buffer would send an already reduced tweet here so we will need to return it as is.
if isinstance(tweet.user, str) == False:
status.user = tweet.user.id_str
else:
return tweet
return status

View File

@@ -1,8 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" This is the main session needed to access all Twitter Features.""" """ This is the main session needed to access all Twitter Features."""
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import range
import os import os
import time import time
import logging import logging
@@ -11,13 +8,17 @@ import wx
import config import config
import output import output
import application import application
import appkeys
from pubsub import pub from pubsub import pub
from twython import Twython, TwythonError, TwythonRateLimitError, TwythonAuthError import tweepy
from tweepy.errors import TweepyException, Forbidden, NotFound
from tweepy.models import User as UserModel
from mysc.thread_utils import call_threaded from mysc.thread_utils import call_threaded
from keys import keyring from keys import keyring
from sessions import base from sessions import base
from sessions.twitter import utils, compose from sessions.twitter import utils, compose
from sessions.twitter.long_tweets import tweets, twishort from sessions.twitter.long_tweets import tweets, twishort
from . import reduce, streaming
from .wxUI import authorisationDialog from .wxUI import authorisationDialog
log = logging.getLogger("sessions.twitterSession") log = logging.getLogger("sessions.twitterSession")
@@ -31,52 +32,52 @@ class Session(base.baseSession):
data list: A list with tweets. data list: A list with tweets.
ignore_older bool: if set to True, items older than the first element on the list will be ignored. ignore_older bool: if set to True, items older than the first element on the list will be ignored.
returns the number of items that have been added in this execution""" returns the number of items that have been added in this execution"""
if name == "direct_messages":
return self.order_direct_messages(data)
num = 0 num = 0
last_id = None last_id = None
if (name in self.db) == False: if (name in self.db) == False:
self.db[name] = [] self.db[name] = []
if ("users" in self.db) == False: if ("users" in self.db) == False:
self.db["users"] = {} self.db["users"] = {}
objects = self.db[name]
if ignore_older and len(self.db[name]) > 0: if ignore_older and len(self.db[name]) > 0:
if self.settings["general"]["reverse_timelines"] == False: if self.settings["general"]["reverse_timelines"] == False:
last_id = self.db[name][0]["id"] last_id = self.db[name][0].id
else: else:
last_id = self.db[name][-1]["id"] last_id = self.db[name][-1].id
self.add_users_from_results(data)
for i in data: for i in data:
if ignore_older and last_id != None: if ignore_older and last_id != None:
if i["id"] < last_id: if i.id < last_id:
log.error("Ignoring an older tweet... Last id: {0}, tweet id: {1}".format(last_id, i["id"])) log.error("Ignoring an older tweet... Last id: {0}, tweet id: {1}".format(last_id, i.id))
continue continue
if utils.find_item(i["id"], self.db[name]) == None and utils.is_allowed(i, self.settings, name) == True: if utils.find_item(i, self.db[name]) == None and utils.is_allowed(i, self.settings, name) == True:
i = self.check_quoted_status(i)
i = self.check_long_tweet(i)
if i == False: continue if i == False: continue
if self.settings["general"]["reverse_timelines"] == False: self.db[name].append(i) reduced_object = reduce.reduce_tweet(i)
else: self.db[name].insert(0, i) reduced_object = self.check_quoted_status(reduced_object)
reduced_object = self.check_long_tweet(reduced_object)
if self.settings["general"]["reverse_timelines"] == False: objects.append(reduced_object)
else: objects.insert(0, reduced_object)
num = num+1 num = num+1
if ("user" in i) == True: self.db[name] = objects
if (i["user"]["id"] in self.db["users"]) == False:
self.db["users"][i["user"]["id"]] = i["user"]
return num return num
def order_cursored_buffer(self, name, data): def order_people(self, name, data):
""" Put new items on the local database. Useful for cursored buffers (followers, friends, users of a list and searches) """ Put new items on the local database. Useful for cursored buffers (followers, friends, users of a list and searches)
name str: The name for the buffer stored in the dictionary. name str: The name for the buffer stored in the dictionary.
data list: A list with items and some information about cursors. data list: A list with items and some information about cursors.
returns the number of items that have been added in this execution""" returns the number of items that have been added in this execution"""
# Direct messages should be added to db in other function.
# Because they will be populating two buffers with one endpoint.
if name == "direct_messages":
return self.order_direct_messages(data)
num = 0 num = 0
if (name in self.db) == False: if (name in self.db) == False:
self.db[name] = {} self.db[name] = []
self.db[name]["items"] = [] objects = self.db[name]
for i in data: for i in data:
if utils.find_item(i["id"], self.db[name]["items"]) == None: if utils.find_item(i, self.db[name]) == None:
if self.settings["general"]["reverse_timelines"] == False: self.db[name]["items"].append(i) if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
else: self.db[name]["items"].insert(0, i) else: objects.insert(0, i)
num = num+1 num = num+1
self.db[name] = objects
return num return num
def order_direct_messages(self, data): def order_direct_messages(self, data):
@@ -86,27 +87,47 @@ class Session(base.baseSession):
incoming = 0 incoming = 0
sent = 0 sent = 0
if ("direct_messages" in self.db) == False: if ("direct_messages" in self.db) == False:
self.db["direct_messages"] = {} self.db["direct_messages"] = []
self.db["direct_messages"]["items"] = [] if ("sent_direct_messages" in self.db) == False:
self.db["sent_direct_messages"] = []
objects = self.db["direct_messages"]
sent_objects = self.db["sent_direct_messages"]
for i in data: for i in data:
if i["message_create"]["sender_id"] == self.db["user_id"]: # Twitter returns sender_id as str, which must be converted to int in order to match to our user_id object.
if "sent_direct_messages" in self.db and utils.find_item(i["id"], self.db["sent_direct_messages"]["items"]) == None: if int(i.message_create["sender_id"]) == self.db["user_id"]:
if self.settings["general"]["reverse_timelines"] == False: self.db["sent_direct_messages"]["items"].append(i) if "sent_direct_messages" in self.db and utils.find_item(i, self.db["sent_direct_messages"]) == None:
else: self.db["sent_direct_messages"]["items"].insert(0, i) if self.settings["general"]["reverse_timelines"] == False: sent_objects.append(i)
else: sent_objects.insert(0, i)
sent = sent+1 sent = sent+1
else: else:
if utils.find_item(i["id"], self.db["direct_messages"]["items"]) == None: if utils.find_item(i, self.db["direct_messages"]) == None:
if self.settings["general"]["reverse_timelines"] == False: self.db["direct_messages"]["items"].append(i) if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
else: self.db["direct_messages"]["items"].insert(0, i) else: objects.insert(0, i)
incoming = incoming+1 incoming = incoming+1
self.db["direct_messages"] = objects
self.db["sent_direct_messages"] = sent_objects
pub.sendMessage("sent-dms-updated", total=sent, account=self.db["user_name"]) pub.sendMessage("sent-dms-updated", total=sent, account=self.db["user_name"])
return incoming return incoming
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Session, self).__init__(*args, **kwargs) super(Session, self).__init__(*args, **kwargs)
# Adds here the optional cursors objects.
cursors = dict(direct_messages=-1)
self.db["cursors"] = cursors
self.reconnection_function_active = False self.reconnection_function_active = False
self.counter = 0 self.counter = 0
self.lists = [] self.lists = []
# As users are cached for accessing them with not too many twitter calls,
# there could be a weird situation where a deleted user who sent direct messages to the current account will not be able to be retrieved at twitter.
# So we need to store an "user deleted" object in the cache, but have the ID of the deleted user in a local reference.
# This will be especially useful because if the user reactivates their account later, TWblue will try to retrieve such user again at startup.
# If we wouldn't implement this approach, TWBlue would save permanently the "deleted user" object.
self.deleted_users = {}
self.type = "twitter"
pub.subscribe(self.handle_new_status, "newStatus")
pub.subscribe(self.handle_connected, "streamConnected")
# @_require_configuration # @_require_configuration
def login(self, verify_credentials=True): def login(self, verify_credentials=True):
@@ -115,7 +136,10 @@ class Session(base.baseSession):
if self.settings["twitter"]["user_key"] != None and self.settings["twitter"]["user_secret"] != None: if self.settings["twitter"]["user_key"] != None and self.settings["twitter"]["user_secret"] != None:
try: try:
log.debug("Logging in to twitter...") log.debug("Logging in to twitter...")
self.twitter = Twython(keyring.get("api_key"), keyring.get("api_secret"), self.settings["twitter"]["user_key"], self.settings["twitter"]["user_secret"]) self.auth = tweepy.OAuthHandler(appkeys.twitter_api_key, appkeys.twitter_api_secret)
self.auth.set_access_token(self.settings["twitter"]["user_key"], self.settings["twitter"]["user_secret"])
self.twitter = tweepy.API(self.auth)
self.twitter_v2 = tweepy.Client(consumer_key=appkeys.twitter_api_key, consumer_secret=appkeys.twitter_api_secret, access_token=self.settings["twitter"]["user_key"], access_token_secret=self.settings["twitter"]["user_secret"])
if verify_credentials == True: if verify_credentials == True:
self.credentials = self.twitter.verify_credentials() self.credentials = self.twitter.verify_credentials()
self.logged = True self.logged = True
@@ -134,19 +158,18 @@ class Session(base.baseSession):
if self.logged == True: if self.logged == True:
raise Exceptions.AlreadyAuthorisedError("The authorisation process is not needed at this time.") raise Exceptions.AlreadyAuthorisedError("The authorisation process is not needed at this time.")
else: else:
twitter = Twython(keyring.get("api_key"), keyring.get("api_secret")) self.auth = tweepy.OAuthHandler(appkeys.twitter_api_key, appkeys.twitter_api_secret)
self.auth = twitter.get_authentication_tokens(callback_url="oob") redirect_url = self.auth.get_authorization_url()
webbrowser.open_new_tab(self.auth['auth_url']) webbrowser.open_new_tab(redirect_url)
self.authorisation_dialog = authorisationDialog() self.authorisation_dialog = authorisationDialog()
self.authorisation_dialog.cancel.Bind(wx.EVT_BUTTON, self.authorisation_cancelled) self.authorisation_dialog.cancel.Bind(wx.EVT_BUTTON, self.authorisation_cancelled)
self.authorisation_dialog.ok.Bind(wx.EVT_BUTTON, self.authorisation_accepted) self.authorisation_dialog.ok.Bind(wx.EVT_BUTTON, self.authorisation_accepted)
self.authorisation_dialog.ShowModal() self.authorisation_dialog.ShowModal()
def verify_authorisation(self, pincode): def verify_authorisation(self, pincode):
twitter = Twython(keyring.get("api_key"), keyring.get("api_secret"), self.auth['oauth_token'], self.auth['oauth_token_secret']) self.auth.get_access_token(pincode)
final = twitter.get_authorized_tokens(pincode) self.settings["twitter"]["user_key"] = self.auth.access_token
self.settings["twitter"]["user_key"] = final["oauth_token"] self.settings["twitter"]["user_secret"] = self.auth.access_token_secret
self.settings["twitter"]["user_secret"] = final["oauth_token_secret"]
self.settings.write() self.settings.write()
del self.auth del self.auth
@@ -161,35 +184,6 @@ class Session(base.baseSession):
self.verify_authorisation(pincode) self.verify_authorisation(pincode)
self.authorisation_dialog.Destroy() self.authorisation_dialog.Destroy()
def get_more_items(self, update_function, users=False, dm=False, name=None, *args, **kwargs):
""" Get more items for twitter objects.
update_function str: function to call for getting more items. Must be member of self.twitter.
users, dm bool: If any of these is set to True, the function will treat items as users or dm (they need different handling).
name str: name of the database item to put new element in."""
results = []
if "cursor" in kwargs and kwargs["cursor"] == 0:
output.speak(_(u"There are no more items to retrieve in this buffer."))
return
data = getattr(self.twitter, update_function)(*args, **kwargs)
if users == True:
if type(data) == dict and "next_cursor" in data:
if "next_cursor" in data: # There are more objects to retrieve.
self.db[name]["cursor"] = data["next_cursor"]
else: # Set cursor to 0, wich means no more items available.
self.db[name]["cursor"] = 0
for i in data["users"]: results.append(i)
elif type(data) == list:
results.extend(data[1:])
elif dm == True:
if "next_cursor" in data: # There are more objects to retrieve.
self.db[name]["cursor"] = data["next_cursor"]
else: # Set cursor to 0, wich means no more items available.
self.db[name]["cursor"] = 0
for i in data["events"]: results.append(i)
else:
results.extend(data[1:])
return results
def api_call(self, call_name, action="", _sound=None, report_success=False, report_failure=True, preexec_message="", *args, **kwargs): def api_call(self, call_name, action="", _sound=None, report_success=False, report_failure=True, preexec_message="", *args, **kwargs):
""" Make a call to the Twitter API. If there is a connectionError or another exception not related to Twitter, It will call the method again at least 25 times, waiting a while between calls. Useful for post methods. """ Make a call to the Twitter API. If there is a connectionError or another exception not related to Twitter, It will call the method again at least 25 times, waiting a while between calls. Useful for post methods.
If twitter returns an error, it will not call the method anymore. If twitter returns an error, it will not call the method anymore.
@@ -207,14 +201,14 @@ class Session(base.baseSession):
try: try:
val = getattr(self.twitter, call_name)(*args, **kwargs) val = getattr(self.twitter, call_name)(*args, **kwargs)
finished = True finished = True
except TwythonError as e: except TweepyException as e:
output.speak(e.msg) output.speak(str(e))
val = None val = None
if e.error_code != 403 and e.error_code != 404: if type(e) != NotFound and type(e) != Forvidden:
tries = tries+1 tries = tries+1
time.sleep(5) time.sleep(5)
elif report_failure and hasattr(e, 'message'): elif report_failure:
output.speak(_("%s failed. Reason: %s") % (action, e.msg)) output.speak(_("%s failed. Reason: %s") % (action, str(e)))
finished = True finished = True
# except: # except:
# tries = tries + 1 # tries = tries + 1
@@ -226,16 +220,16 @@ class Session(base.baseSession):
def search(self, name, *args, **kwargs): def search(self, name, *args, **kwargs):
""" Search in twitter, passing args and kwargs as arguments to the Twython function.""" """ Search in twitter, passing args and kwargs as arguments to the Twython function."""
tl = self.twitter.search(*args, **kwargs) tl = self.twitter.search_tweets(*args, **kwargs)
tl["statuses"].reverse() tl.reverse()
return tl["statuses"] return tl
# @_require_login # @_require_login
def get_favourites_timeline(self, name, *args, **kwargs): def get_favourites_timeline(self, name, *args, **kwargs):
""" Gets favourites for the authenticated user or a friend or follower. """ Gets favourites for the authenticated user or a friend or follower.
name str: Name for storage in the database. name str: Name for storage in the database.
args and kwargs are passed directly to the Twython function.""" args and kwargs are passed directly to the Twython function."""
tl = self.call_paged("get_favorites", *args, **kwargs) tl = self.call_paged("favorites", *args, **kwargs)
return self.order_buffer(name, tl) return self.order_buffer(name, tl)
def call_paged(self, update_function, *args, **kwargs): def call_paged(self, update_function, *args, **kwargs):
@@ -249,8 +243,8 @@ class Session(base.baseSession):
data = getattr(self.twitter, update_function)(count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) data = getattr(self.twitter, update_function)(count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs)
results.extend(data) results.extend(data)
for i in range(0, max): for i in range(0, max):
if i == 0: max_id = results[-1]["id"] if i == 0: max_id = results[-1].id
else: max_id = results[0]["id"] else: max_id = results[0].id
data = getattr(self.twitter, update_function)(max_id=max_id, count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) data = getattr(self.twitter, update_function)(max_id=max_id, count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs)
results.extend(data) results.extend(data)
results.reverse() results.reverse()
@@ -259,11 +253,11 @@ class Session(base.baseSession):
# @_require_login # @_require_login
def get_user_info(self): def get_user_info(self):
""" Retrieves some information required by TWBlue for setup.""" """ Retrieves some information required by TWBlue for setup."""
f = self.twitter.get_account_settings() f = self.twitter.get_settings()
sn = f["screen_name"] sn = f["screen_name"]
self.settings["twitter"]["user_name"] = sn self.settings["twitter"]["user_name"] = sn
self.db["user_name"] = sn self.db["user_name"] = sn
self.db["user_id"] = self.twitter.show_user(screen_name=sn)["id_str"] self.db["user_id"] = self.twitter.get_user(screen_name=sn).id
try: try:
self.db["utc_offset"] = f["time_zone"]["utc_offset"] self.db["utc_offset"] = f["time_zone"]["utc_offset"]
except KeyError: except KeyError:
@@ -271,7 +265,7 @@ class Session(base.baseSession):
# Get twitter's supported languages and save them in a global variable # Get twitter's supported languages and save them in a global variable
#so we won't call to this method once per session. #so we won't call to this method once per session.
if len(application.supported_languages) == 0: if len(application.supported_languages) == 0:
application.supported_languages = self.twitter.get_supported_languages() application.supported_languages = self.twitter.supported_languages()
self.get_lists() self.get_lists()
self.get_muted_users() self.get_muted_users()
self.settings.write() self.settings.write()
@@ -279,12 +273,12 @@ class Session(base.baseSession):
# @_require_login # @_require_login
def get_lists(self): def get_lists(self):
""" Gets the lists that the user is subscribed to and stores them in the database. Returns None.""" """ Gets the lists that the user is subscribed to and stores them in the database. Returns None."""
self.db["lists"] = self.twitter.show_lists(reverse=True) self.db["lists"] = self.twitter.get_lists(reverse=True)
# @_require_login # @_require_login
def get_muted_users(self): def get_muted_users(self):
""" Gets muted users (oh really?).""" """ Gets muted users (oh really?)."""
self.db["muted_users"] = self.twitter.list_mute_ids()["ids"] self.db["muted_users"] = self.twitter.get_muted_ids()
# @_require_login # @_require_login
def get_stream(self, name, function, *args, **kwargs): def get_stream(self, name, function, *args, **kwargs):
@@ -353,76 +347,111 @@ class Session(base.baseSession):
return tweet return tweet
def get_quoted_tweet(self, tweet): def get_quoted_tweet(self, tweet):
""" Process a tweet and extract all information related to the quote.""" """ Process a tweet and extract all information related to the quote. """
quoted_tweet = tweet quoted_tweet = tweet
if "full_text" in tweet: if hasattr(tweet, "full_text"):
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
urls = utils.find_urls_in_text(quoted_tweet[value]) if hasattr(quoted_tweet, "entities"):
for url in range(0, len(urls)): setattr(quoted_tweet, value, utils.expand_urls(getattr(quoted_tweet, value), quoted_tweet.entities))
try: quoted_tweet[value] = quoted_tweet[value].replace(urls[url], quoted_tweet["entities"]["urls"][url]["expanded_url"]) if hasattr(quoted_tweet, "is_quote_status") == True and hasattr(quoted_tweet, "quoted_status"):
except IndexError: pass original_tweet = quoted_tweet.quoted_status
if "quoted_status" in quoted_tweet: elif hasattr(quoted_tweet, "retweeted_status") and hasattr(quoted_tweet.retweeted_status, "is_quote_status") == True and hasattr(quoted_tweet.retweeted_status, "quoted_status"):
original_tweet = quoted_tweet["quoted_status"] original_tweet = quoted_tweet.retweeted_status.quoted_status
elif "retweeted_status" in quoted_tweet and "quoted_status" in quoted_tweet["retweeted_status"]:
original_tweet = quoted_tweet["retweeted_status"]["quoted_status"]
else: else:
return quoted_tweet return quoted_tweet
original_tweet = self.check_long_tweet(original_tweet) original_tweet = self.check_long_tweet(original_tweet)
if hasattr(original_tweet, "full_text"):
if "full_text" in original_tweet:
value = "full_text" value = "full_text"
elif "message" in original_tweet: elif hasattr(original_tweet, "message"):
value = "message" value = "message"
else: else:
value = "text" value = "text"
urls = utils.find_urls_in_text(original_tweet[value]) if hasattr(original_tweet, "entities"):
for url in range(0, len(urls)): setattr(original_tweet, value, utils.expand_urls(getattr(original_tweet, value), original_tweet.entities))
try: original_tweet[value] = original_tweet[value].replace(urls[url], original_tweet["entities"]["urls"][url]["expanded_url"]) # ToDo: Shall we check whether we should add show_screen_names here?
except IndexError: pass return compose.compose_quoted_tweet(quoted_tweet, original_tweet, session=self)
return compose.compose_quoted_tweet(quoted_tweet, original_tweet)
def check_long_tweet(self, tweet): def check_long_tweet(self, tweet):
""" Process a tweet and add extra info if it's a long tweet made with Twyshort. """ Process a tweet and add extra info if it's a long tweet made with Twyshort.
tweet dict: a tweet object. tweet dict: a tweet object.
returns a tweet with a new argument message, or original tweet if it's not a long tweet.""" returns a tweet with a new argument message, or original tweet if it's not a long tweet."""
long = False
if hasattr(tweet, "entities") and tweet.entities.get("urls"):
long = twishort.is_long(tweet) long = twishort.is_long(tweet)
if long != False and config.app["app-settings"]["handle_longtweets"]: if long != False and config.app["app-settings"]["handle_longtweets"]:
message = twishort.get_full_text(long) message = twishort.get_full_text(long)
if "quoted_status" in tweet: if hasattr(tweet, "quoted_status"):
tweet["quoted_status"]["message"] = message tweet.quoted_status.message = message
if tweet["quoted_status"]["message"] == False: return False if tweet.quoted_status.message == False: return False
tweet["quoted_status"]["twishort"] = True tweet.quoted_status.twishort = True
for i in tweet["quoted_status"]["entities"]["user_mentions"]: if hasattr(tweet.quoted_status, "entities") and tweet.quoted_status.entities.get("user_mentions"):
if "@%s" % (i["screen_name"]) not in tweet["quoted_status"]["message"] and i["screen_name"] != tweet["user"]["screen_name"]: for i in tweet.quoted_status.entities["user_mentions"]:
if "retweeted_status" in tweet["quoted_status"] and tweet["retweeted_status"]["user"]["screen_name"] == i["screen_name"]: if "@%s" % (i["screen_name"]) not in tweet.quoted_status.message and i["screen_name"] != self.get_user(tweet.user).screen_name:
if hasattr(tweet.quoted_status, "retweeted_status") and self.get_user(tweet.retweeted_status.user).screen_name == i["screen_name"]:
continue continue
tweet["quoted_status"]["message"] = u"@%s %s" % (i["screen_name"], tweet["message"]) tweet.quoted_status.message = u"@%s %s" % (i["screen_name"], tweet.message)
else: else:
tweet["message"] = message tweet.message = message
if tweet["message"] == False: return False if tweet.message == False: return False
tweet["twishort"] = True tweet.twishort = True
for i in tweet["entities"]["user_mentions"]: if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"):
if "@%s" % (i["screen_name"]) not in tweet["message"] and i["screen_name"] != tweet["user"]["screen_name"]: for i in tweet.entities["user_mentions"]:
if "retweeted_status" in tweet and tweet["retweeted_status"]["user"]["screen_name"] == i["screen_name"]: if "@%s" % (i["screen_name"]) not in tweet.message and i["screen_name"] != self.get_user(tweet.user).screen_name:
if hasattr(tweet, "retweeted_status") and self.get_user(tweet.retweeted_status.user).screen_name == i["screen_name"]:
continue continue
tweet.message = u"@%s %s" % (i["screen_name"], tweet.message)
return tweet return tweet
def get_user(self, id): def get_user(self, id):
""" Returns an user object associated with an ID. """ Returns an user object associated with an ID.
id str: User identifier, provided by Twitter. id str: User identifier, provided by Twitter.
returns an user dict.""" returns a tweepy user object."""
if ("users" in self.db) == False or (id in self.db["users"]) == False: if hasattr(id, "id_str"):
log.error("Called get_user function by passing a full user id as a parameter.")
id = id.id_str
# Check if the user has been added to the list of deleted users previously.
if id in self.deleted_users:
log.debug("Returning user {} from the list of deleted users.".format(id))
return self.deleted_users[id]
if ("users" in self.db) == False or (str(id) in self.db["users"]) == False:
log.debug("Requesting user id {} as it is not present in the users database.".format(id))
try: try:
user = self.twitter.show_user(id=id) user = self.twitter.get_user(id=id)
except TwythonError: except TweepyException as err:
user = dict(screen_name="deleted_account", name="Deleted account") user = UserModel(None)
return user user.screen_name = "deleted_user"
self.db["users"][user["id_str"]] = user user.id = id
user.name = _("Deleted account")
if type(err) == NotFound:
self.deleted_users[id] = user
return user return user
else: else:
return self.db["users"][id] log.exception("Error when attempting to retrieve an user from Twitter.")
return user
users = self.db["users"]
users[user.id_str] = user
self.db["users"] = users
user.name = self.get_user_alias(user)
return user
else:
user = self.db["users"][str(id)]
user.name = self.get_user_alias(user)
return user
def get_user_alias(self, user):
""" Retrieves an alias for the passed user model, if exists.
@ user Tweepy.models.user: An user object.
"""
aliases = self.settings.get("user-aliases")
if aliases == None:
log.error("Aliases are not defined for this config spec.")
return user.name
user_alias = aliases.get(user.id_str)
if user_alias != None:
return user_alias
return user.name
def get_user_by_screen_name(self, screen_name): def get_user_by_screen_name(self, screen_name):
""" Returns an user identifier associated with a screen_name. """ Returns an user identifier associated with a screen_name.
@@ -430,12 +459,135 @@ class Session(base.baseSession):
returns an user ID.""" returns an user ID."""
if ("users" in self.db) == False: if ("users" in self.db) == False:
user = utils.if_user_exists(self.twitter, screen_name) user = utils.if_user_exists(self.twitter, screen_name)
self.db["users"][user["id_str"]] = user users = self.db["users"]
return user["id_str"] users[user["id"]] = user
self.db["users"] = users
return user["id"]
else: else:
for i in list(self.db["users"].keys()): for i in list(self.db["users"].keys()):
if self.db["users"][i]["screen_name"] == screen_name: if self.db["users"][i].screen_name == screen_name:
return self.db["users"][i]["id_str"] return self.db["users"][i].id
user = utils.if_user_exists(self.twitter, screen_name) user = utils.if_user_exists(self.twitter, screen_name)
self.db["users"][user["id_str"]] = user users = self.db["users"]
return user["id_str"] users[user.id] = user
self.db["users"] = users
return user.id
def save_users(self, user_ids):
""" Adds all new users to the users database. """
if len(user_ids) == 0:
return
log.debug("Received %d user IDS to be added in the database." % (len(user_ids)))
users_to_retrieve = [user_id for user_id in user_ids if (user_id not in self.db["users"] and user_id not in self.deleted_users)]
# Remove duplicates
users_to_retrieve = list(dict.fromkeys(users_to_retrieve))
if len(users_to_retrieve) == 0:
return
log.debug("TWBlue will get %d new users from Twitter." % (len(users_to_retrieve)))
try:
users = self.twitter.lookup_users(user_id=users_to_retrieve, tweet_mode="extended")
users_db = self.db["users"]
for user in users:
users_db[user.id_str] = user
log.debug("Added %d new users" % (len(users)))
self.db["users"] = users_db
except TweepyException as err:
if type(err) == NotFound: # User not found.
log.error("The specified users {} were not found in twitter.".format(user_ids))
# Creates a deleted user object for every user_id not found here.
# This will make TWBlue to not waste Twitter API calls when attempting to retrieve those users again.
# As deleted_users is not saved across restarts, when restarting TWBlue, it will retrieve the correct users if they enabled their accounts.
for id in users_to_retrieve:
user = UserModel(None)
user.screen_name = "deleted_user"
user.id = id
user.name = _("Deleted account")
self.deleted_users[id] = user
else:
log.exception("An exception happened while attempting to retrieve a list of users from direct messages in Twitter.")
def add_users_from_results(self, data):
users = self.db["users"]
for i in data:
if hasattr(i, "user"):
if isinstance(i.user, str):
log.warning("A String was passed to be added as an user. This is normal only if TWBlue tried to load a conversation.")
continue
if (i.user.id_str in self.db["users"]) == False:
users[i.user.id_str] = i.user
if hasattr(i, "quoted_status") and (i.quoted_status.user.id_str in self.db["users"]) == False:
users[i.quoted_status.user.id_str] = i.quoted_status.user
if hasattr(i, "retweeted_status") and (i.retweeted_status.user.id_str in self.db["users"]) == False:
users[i.retweeted_status.user.id_str] = i.retweeted_status.user
self.db["users"] = users
def start_streaming(self):
if config.app["app-settings"]["no_streaming"]:
return
self.stream = streaming.Stream(twitter_api=self.twitter, user=self.db["user_name"], user_id=self.db["user_id"], muted_users=self.db["muted_users"], consumer_key=appkeys.twitter_api_key, consumer_secret=appkeys.twitter_api_secret, access_token=self.settings["twitter"]["user_key"], access_token_secret=self.settings["twitter"]["user_secret"], chunk_size=1025)
self.stream_thread = call_threaded(self.stream.filter, follow=self.stream.users, stall_warnings=True)
def stop_streaming(self):
if config.app["app-settings"]["no_streaming"]:
return
if hasattr(self, "stream"):
self.stream.running = False
log.debug("Stream stopped for accounr {}".format(self.db["user_name"]))
def handle_new_status(self, status, user):
""" Handles a new status present in the Streaming API. """
if self.logged == False:
return
# Discard processing the status if the streaming sends a tweet for another account.
if self.db["user_name"] != user:
return
# the Streaming API sends non-extended tweets with an optional parameter "extended_tweets" which contains full_text and other data.
# so we have to make sure we check it before processing the normal status.
# As usual, we handle also quotes and retweets at first.
if hasattr(status, "retweeted_status") and hasattr(status.retweeted_status, "extended_tweet"):
status.retweeted_status._json = {**status.retweeted_status._json, **status.retweeted_status._json["extended_tweet"]}
# compose.compose_tweet requires the parent tweet to have a full_text field, so we have to add it to retweets here.
status._json["full_text"] = status._json["text"]
if hasattr(status, "quoted_status") and hasattr(status.quoted_status, "extended_tweet"):
status.quoted_status._json = {**status.quoted_status._json, **status.quoted_status._json["extended_tweet"]}
if status.truncated:
status._json = {**status._json, **status._json["extended_tweet"]}
# Sends status to database, where it will be reduced and changed according to our needs.
buffers_to_send = []
if status.user.id_str in self.stream.users:
buffers_to_send.append("home_timeline")
if status.user.id == self.db["user_id"]:
buffers_to_send.append("sent_tweets")
for user in status.entities["user_mentions"]:
if user["id"] == self.db["user_id"]:
buffers_to_send.append("mentions")
users_with_timeline = [user.split("-")[0] for user in self.db.keys() if user.endswith("-timeline")]
for user in users_with_timeline:
if status.user.id_str == user:
buffers_to_send.append("{}-timeline".format(user))
for buffer in buffers_to_send[::]:
num = self.order_buffer(buffer, [status])
if num == 0:
buffers_to_send.remove(buffer)
# However, we have to do the "reduce and change" process here because the status we sent to the db is going to be a different object that the one sent to database.
status = reduce.reduce_tweet(status)
status = self.check_quoted_status(status)
status = self.check_long_tweet(status)
# Send it to the main controller object.
pub.sendMessage("newTweet", data=status, user=self.db["user_name"], _buffers=buffers_to_send)
def check_streams(self):
if config.app["app-settings"]["no_streaming"]:
return
if not hasattr(self, "stream"):
return
log.debug("Status of running stream for user {}: {}".format(self.db["user_name"], self.stream.running))
if self.stream.running == False:
self.start_streaming()
def handle_connected(self, user):
if self.logged == False:
return
if user != self.db["user_name"]:
log.debug("Connected streaming endpoint on account {}".format(user))

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
""" Streaming support for TWBlue. """
import time
import sys
import six
import requests
import urllib3
import ssl
import tweepy
import logging
from pubsub import pub
log = logging.getLogger("sessions.twitter.streaming")
class Stream(tweepy.Stream):
def __init__(self, twitter_api, user, user_id, muted_users=[], *args, **kwargs):
super(Stream, self).__init__(*args, **kwargs)
log.debug("Starting streaming listener for account {}".format(user))
self.started = False
self.users = []
self.api = twitter_api
self.user = user
self.user_id = user_id
friends = self.api.get_friend_ids()
log.debug("Retrieved {} friends to add to the streaming listener.".format(len(friends)))
self.users.append(str(self.user_id))
log.debug("Got {} muted users.".format(len(muted_users)))
for user in friends:
if user not in muted_users:
self.users.append(str(user))
self.started = True
log.debug("Streaming listener started with {} users to follow.".format(len(self.users)))
def on_connect(self):
pub.sendMessage("streamConnected", user=self.user)
def on_exception(self, ex):
log.exception("Exception received on streaming endpoint for user {}".format(self.user))
def on_status(self, status):
""" Checks data arriving as a tweet. """
# Hide replies to users not followed by current account.
if status.in_reply_to_user_id_str != None and status.in_reply_to_user_id_str not in self.users and status.user.screen_name != self.user:
return
if status.user.id_str in self.users:
pub.sendMessage("newStatus", status=status, user=self.user)

View File

@@ -1,16 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function
from __future__ import unicode_literals
from builtins import str
from builtins import range
import url_shortener, re import url_shortener, re
import output import output
from twython import TwythonError
import config import config
import logging import logging
import requests import requests
import time import time
import sound import sound
from tweepy.errors import TweepyException, NotFound, Forbidden
log = logging.getLogger("twitter.utils") log = logging.getLogger("twitter.utils")
""" Some utilities for the twitter interface.""" """ Some utilities for the twitter interface."""
@@ -25,164 +21,171 @@ bad_chars = '\'\\\n.,[](){}:;"'
def find_urls_in_text(text): def find_urls_in_text(text):
return url_re2.findall(text) return url_re2.findall(text)
def find_urls (tweet): def find_urls (tweet, twitter_media=False):
urls = [] urls = []
if twitter_media and hasattr(tweet, "extended_entities"):
for mediaItem in tweet.extended_entities["media"]:
if mediaItem["type"] == "video":
for variant in mediaItem["video_info"]["variants"]:
if variant["content_type"] == "video/mp4":
urls.append(variant["url"])
break
# Let's add URLS from tweet entities. # Let's add URLS from tweet entities.
if "message_create" in tweet: if hasattr(tweet, "message_create"):
entities = tweet["message_create"]["message_data"]["entities"] entities = tweet.message_create["message_data"]["entities"]
else: else:
entities = tweet["entities"] if hasattr(tweet, "entities") == True:
entities = tweet.entities
if entities.get("urls") != None:
for i in entities["urls"]: for i in entities["urls"]:
if i["expanded_url"] not in urls: if i["expanded_url"] not in urls:
urls.append(i["expanded_url"]) urls.append(i["expanded_url"])
if "quoted_status" in tweet: if hasattr(tweet, "quoted_status"):
for i in tweet["quoted_status"]["entities"]["urls"]: urls.extend(find_urls(tweet.quoted_status, twitter_media))
if i["expanded_url"] not in urls: if hasattr(tweet, "retweeted_status"):
urls.append(i["expanded_url"]) urls.extend(find_urls(tweet.retweeted_status, twitter_media))
if "retweeted_status" in tweet: if hasattr(tweet, "message"):
for i in tweet["retweeted_status"]["entities"]["urls"]:
if i["expanded_url"] not in urls:
urls.append(i["expanded_url"])
if "quoted_status" in tweet["retweeted_status"]:
for i in tweet["retweeted_status"]["quoted_status"]["entities"]["urls"]:
if i["expanded_url"] not in urls:
urls.append(i["expanded_url"])
if "message" in tweet:
i = "message" i = "message"
elif "full_text" in tweet: elif hasattr(tweet, "full_text"):
i = "full_text" i = "full_text"
else: else:
i = "text" i = "text"
if "message_create" in tweet: if hasattr(tweet, "message_create"):
extracted_urls = find_urls_in_text(tweet["message_create"]["message_data"]["text"]) extracted_urls = find_urls_in_text(tweet.message_create["message_data"]["text"])
else: else:
extracted_urls = find_urls_in_text(tweet[i]) extracted_urls = find_urls_in_text(getattr(tweet, i))
# Don't include t.co links (mostly they are photos or shortened versions of already added URLS). # Don't include t.co links (mostly they are photos or shortened versions of already added URLS).
for i in extracted_urls: for i in extracted_urls:
if i not in urls and "https://t.co" not in i: if i not in urls and "https://t.co" not in i:
urls.append(i) urls.append(i)
return urls return urls
def find_item(id, listItem): def find_item(item, listItems):
for i in range(0, len(listItem)): for i in range(0, len(listItems)):
if listItem[i]["id"] == id: return i if listItems[i].id == item.id:
return i
# Check also retweets.
if hasattr(item, "retweeted_status") and item.retweeted_status.id == listItems[i].id:
return i
return None return None
def find_list(name, lists): def find_list(name, lists):
for i in range(0, len(lists)): for i in range(0, len(lists)):
if lists[i]["name"] == name: return lists[i]["id"] if lists[i].name == name: return lists[i].id
def find_previous_reply(id, listItem):
for i in range(0, len(listItem)):
if listItem[i]["id_str"] == str(id): return i
return None
def find_next_reply(id, listItem):
for i in range(0, len(listItem)):
if listItem[i]["in_reply_to_status_id_str"] == str(id): return i
return None
def is_audio(tweet): def is_audio(tweet):
if hasattr(tweet, "quoted_status") and hasattr(tweet.quoted_status, "extended_entities"):
result = is_audio(tweet.quoted_status)
if result != None:
return result
if hasattr(tweet, "retweeted_status") and hasattr(tweet.retweeted_status, "extended_entities"):
result = is_audio(tweet.retweeted_status)
if result == True:
return result
# Checks firstly for Twitter videos and audios.
if hasattr(tweet, "extended_entities"):
for mediaItem in tweet.extended_entities["media"]:
if mediaItem["type"] == "video":
return True
try: try:
if len(find_urls(tweet)) < 1: if len(find_urls(tweet)) < 1:
return False return False
if "message_create" in tweet: if hasattr(tweet, "message_create"):
entities = tweet["message_create"]["message_data"]["entities"] entities = tweet.message_create["message_data"]["entities"]
else: else:
entities = tweet["entities"] if hasattr(tweet, "entities") == False or tweet.entities.get("hashtags") == None:
return False
entities = tweet.entities
if len(entities["hashtags"]) > 0: if len(entities["hashtags"]) > 0:
for i in entities["hashtags"]: for i in entities["hashtags"]:
if i["text"] == "audio": if i["text"] == "audio":
return True return True
except IndexError: except IndexError:
print(tweet["entities"]["hashtags"])
log.exception("Exception while executing is_audio hashtag algorithm") log.exception("Exception while executing is_audio hashtag algorithm")
def is_geocoded(tweet): def is_geocoded(tweet):
if "coordinates" in tweet and tweet["coordinates"] != None: if hasattr(tweet, "coordinates") and tweet.coordinates != None:
return True return True
def is_media(tweet): def is_media(tweet):
if "message_create" in tweet: if hasattr(tweet, "message_create"):
entities = tweet["message_create"]["message_data"]["entities"] entities = tweet.message_create["message_data"]["entities"]
else: else:
entities = tweet["entities"] if hasattr(tweet, "entities") == False or tweet.entities.get("hashtags") == None:
if ("media" in entities) == False: return False
entities = tweet.entities
if entities.get("media") == None:
return False return False
for i in entities["media"]: for i in entities["media"]:
if "type" in i and i["type"] == "photo": if i.get("type") != None and i.get("type") == "photo":
return True return True
return False return False
def get_all_mentioned(tweet, conf, field="screen_name"): def get_all_mentioned(tweet, conf, field="screen_name"):
""" Gets all users that has been mentioned.""" """ Gets all users that have been mentioned."""
results = [] results = []
for i in tweet["entities"]["user_mentions"]: if hasattr(tweet, "retweeted_status"):
if i["screen_name"] != conf["user_name"] and i["screen_name"] != tweet["user"]["screen_name"]: results.extend(get_all_mentioned(tweet.retweeted_status, conf, field))
if i[field] not in results: if hasattr(tweet, "quoted_status"):
results.append(i[field]) results.extend(get_all_mentioned(tweet.quoted_status, conf, field))
if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"):
for i in tweet.entities["user_mentions"]:
if i["screen_name"] != conf["user_name"] and i["id_str"] != tweet.user:
if i.get(field) not in results:
results.append(i.get(field))
return results return results
def get_all_users(tweet, conf): def get_all_users(tweet, session):
string = [] string = []
if "retweeted_status" in tweet: user = session.get_user(tweet.user)
string.append(tweet["user"]["screen_name"]) if user.screen_name != session.db["user_name"]:
tweet = tweet["retweeted_status"] string.append(user.screen_name)
if "sender" in tweet: if hasattr(tweet, "retweeted_status"):
string.append(tweet["sender"]["screen_name"]) string.extend(get_all_users(tweet.retweeted_status, session))
else: if hasattr(tweet, "quoted_status"):
if tweet["user"]["screen_name"] != conf["user_name"]: string.extend(get_all_users(tweet.quoted_status, session))
string.append(tweet["user"]["screen_name"]) if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"):
for i in tweet["entities"]["user_mentions"]: for i in tweet.entities["user_mentions"]:
if i["screen_name"] != conf["user_name"] and i["screen_name"] != tweet["user"]["screen_name"]: if i["screen_name"] != session.db["user_name"] and i["screen_name"] != user.screen_name:
if i["screen_name"] not in string: if i["screen_name"] not in string:
string.append(i["screen_name"]) string.append(i["screen_name"])
# Attempt to remove duplicates, tipically caused by nested tweets.
string = list(dict.fromkeys(string))
if len(string) == 0: if len(string) == 0:
string.append(tweet["user"]["screen_name"]) string.append(user.screen_name)
return string return string
def if_user_exists(twitter, user): def if_user_exists(twitter, user):
try: try:
data = twitter.show_user(screen_name=user) data = twitter.get_user(screen_name=user)
return data return data
except TwythonError as err: except TweepyException as err:
if err.error_code == 404: if type(err) == NotFound:
return None return None
else: else:
return user return user
def api_call(parent=None, call_name=None, preexec_message="", success="", success_snd="", *args, **kwargs):
if preexec_message:
output.speak(preexec_message, True)
try:
val = getattr(parent.twitter.twitter, call_name)(*args, **kwargs)
output.speak(success)
parent.parent.sound.play(success_snd)
except TwythonError as e:
output.speak("Error %s: %s" % (e.error_code, e.msg), True)
parent.parent.sound.play("error.ogg")
return val
def is_allowed(tweet, settings, buffer_name): def is_allowed(tweet, settings, buffer_name):
clients = settings["twitter"]["ignored_clients"] clients = settings["twitter"]["ignored_clients"]
if "sender" in tweet: return True if hasattr(tweet, "sender"): return True
allowed = True allowed = True
tweet_data = {} tweet_data = {}
if "retweeted_status" in tweet: if hasattr(tweet, "retweeted_status"):
tweet_data["retweet"] = True tweet_data["retweet"] = True
if tweet["in_reply_to_status_id_str"] != None: if hasattr(tweet, "in_reply_to_status_id"):
tweet_data["reply"] = True tweet_data["reply"] = True
if "quoted_status" in tweet: if hasattr(tweet, "quoted_status"):
tweet_data["quote"] = True tweet_data["quote"] = True
if "retweeted_status" in tweet: tweet = tweet["retweeted_status"] if hasattr(tweet, "retweeted_status"):
source = re.sub(r"(?s)<.*?>", "", tweet["source"]) tweet = tweet.retweeted_status
source = tweet.source
for i in clients: for i in clients:
if i.lower() == source.lower(): if i.lower() == source.lower():
return False return False
return filter_tweet(tweet, tweet_data, settings, buffer_name) return filter_tweet(tweet, tweet_data, settings, buffer_name)
def filter_tweet(tweet, tweet_data, settings, buffer_name): def filter_tweet(tweet, tweet_data, settings, buffer_name):
if "full_text" in tweet: if hasattr(tweet, "full_text"):
value = "full_text" value = "full_text"
else: else:
value = "text" value = "text"
@@ -210,24 +213,51 @@ def filter_tweet(tweet, tweet_data, settings, buffer_name):
if allow_replies == "False" and "reply" in tweet_data: if allow_replies == "False" and "reply" in tweet_data:
return False return False
if word != "" and settings["filters"][i]["if_word_exists"]: if word != "" and settings["filters"][i]["if_word_exists"]:
if word in tweet[value]: if word in getattr(tweet, value):
return False return False
elif word != "" and settings["filters"][i]["if_word_exists"] == False: elif word != "" and settings["filters"][i]["if_word_exists"] == False:
if word not in tweet[value]: if word not in getattr(tweet, value):
return False return False
if settings["filters"][i]["in_lang"] == "True": if settings["filters"][i]["in_lang"] == "True":
if tweet["lang"] not in settings["filters"][i]["languages"]: if getattr(tweet, lang) not in settings["filters"][i]["languages"]:
return False return False
elif settings["filters"][i]["in_lang"] == "False": elif settings["filters"][i]["in_lang"] == "False":
if tweet["lang"] in settings["filters"][i]["languages"]: if tweet.lang in settings["filters"][i]["languages"]:
return False return False
return True return True
def twitter_error(error): def twitter_error(error):
if error.error_code == 403: if type(error) == Forbidden:
msg = _(u"Sorry, you are not authorised to see this status.") msg = _(u"Sorry, you are not authorised to see this status.")
elif error.error_code == 404: elif type(error) == NotFound:
msg = _(u"No status found with that ID") msg = _(u"No status found with that ID")
else: else:
msg = _(u"Error code {0}").format(error.error_code,) msg = _(u"Error {0}").format(str(error),)
output.speak(msg) output.speak(msg)
def expand_urls(text, entities):
""" Expand all URLS present in text with information found in entities"""
if entities.get("urls") == None:
return text
urls = find_urls_in_text(text)
for url in entities["urls"]:
if url["url"] in text:
text = text.replace(url["url"], url["expanded_url"])
return text
def clean_mentions(text):
new_text = text
mentionned_people = [u for u in re.finditer("(?<=^|(?<=[^a-zA-Z0-9-\.]))@([A-Za-z0-9_]+)", text)]
if len(mentionned_people) <= 2:
return text
end = -2
total_users = 0
for user in mentionned_people:
if abs(user.start()-end) < 3:
new_text = new_text.replace(user.group(0), "", 1)
total_users = total_users+1
end = user.end()
if total_users-2 < 1:
return text
new_text = _("{user_1}, {user_2} and {all_users} more: {text}").format(user_1=mentionned_people[0].group(0), user_2=mentionned_people[1].group(0), all_users=total_users-2, text=new_text)
return new_text

View File

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

Some files were not shown because too many files have changed in this diff Show More