mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2025-08-25 09:29:22 +00:00
Compare commits
178 Commits
v2022.8.28
...
v2022.12.5
Author | SHA1 | Date | |
---|---|---|---|
cadcc56182 | |||
619f58ad90 | |||
c480554e01 | |||
6da81a9734 | |||
b559726535 | |||
768f0bc396 | |||
5bbf069d61 | |||
d177ef5be2 | |||
b6dd539dc6 | |||
fabca1207d | |||
bf4c7ff7c7 | |||
8d2fb59ba8 | |||
b7497791b4 | |||
48730ead63 | |||
98ecd000a9 | |||
4a9098021f | |||
8850e5fdde | |||
e42bd85274 | |||
d3914a4e34 | |||
73de8d4f49 | |||
ce458a8d4d | |||
149ce51f49 | |||
2fe9c35c0b | |||
39fb5b4830 | |||
71ca547abe | |||
1c5c1067e6 | |||
df4d3eb0e6 | |||
927bbae1be | |||
cbc4fd0632 | |||
10d4d47a17 | |||
52d64d86d8 | |||
2ab61ebee6 | |||
3ad01d1ab0 | |||
7835e09c5b | |||
4c5d2b5e04 | |||
8f72ee97c9 | |||
aebdcae9cf | |||
3b5ef02def | |||
35599ee0ef | |||
6e03578e86 | |||
7cbf873db5 | |||
fefd88b71c | |||
7c80c9d842 | |||
f5e52c6387 | |||
4c43f82b60 | |||
2caaaa9d87 | |||
3fa39c712e | |||
252a93f82d | |||
f5328379e8 | |||
2e8c8c6db4 | |||
862dbd0b8a | |||
120da217f5 | |||
035de92496 | |||
2a0e73ad33 | |||
701a557357 | |||
de1d94e679 | |||
64d5b7e684 | |||
2c9048618f | |||
![]() |
d66f4a2640 | ||
bfbb280c27 | |||
cad7c9a9fd | |||
81ff530a71 | |||
39fc982665 | |||
803f5fbe89 | |||
0d1d4c887d | |||
f8f13be6d3 | |||
5f645508ba | |||
3a3cb3963c | |||
2c298577cc | |||
97815f807a | |||
f24f97baae | |||
2a10f029f0 | |||
3deffa57de | |||
d6985be896 | |||
0aad2f0ab3 | |||
f151d6554d | |||
62d6ae2277 | |||
b405e384c8 | |||
7aa986163b | |||
59409e61a5 | |||
edbc74262a | |||
e07efa46b3 | |||
bedb2e2a4f | |||
96b5eec8e0 | |||
c6433d8655 | |||
c0654658b5 | |||
d71d3695eb | |||
4b6a7c5d83 | |||
368e089639 | |||
33647da6e8 | |||
ca39db649b | |||
07dc813cb6 | |||
a852a429f4 | |||
1e3a0d9b2e | |||
9959ac24d9 | |||
90f9f18deb | |||
6983d11b73 | |||
cd5b71a26e | |||
aa3ca82f25 | |||
bf9b8a4f46 | |||
2be460b662 | |||
1d4057ac5b | |||
b9731a3c75 | |||
d53decb165 | |||
02b8330ca0 | |||
12046c1f56 | |||
6f69426f03 | |||
c8c242a27f | |||
60daa548ca | |||
40989a54ed | |||
87f2976419 | |||
b6b81e2b36 | |||
dfcd63b9b6 | |||
23af944fba | |||
cf9add1fc9 | |||
a63f19a70f | |||
8ad266ad1b | |||
b593071364 | |||
0e814a765a | |||
2c7eab60a8 | |||
d43738a2ec | |||
0f885712b6 | |||
14a10989e5 | |||
d5253f651b | |||
31e901b000 | |||
26e7ff009f | |||
1ded773e84 | |||
b8637ba8ca | |||
4dc4f46b84 | |||
e00ec84df3 | |||
8dd122cfb2 | |||
dcfacf8cdd | |||
bc1522833c | |||
4b7382e61f | |||
585d8e2b0c | |||
f34829d72d | |||
![]() |
fd1d60b815 | ||
![]() |
31e194a038 | ||
![]() |
e25712920a | ||
![]() |
27867a1961 | ||
![]() |
1425dcd313 | ||
![]() |
b9cff1a9c9 | ||
fc1ed54963 | |||
![]() |
442abe82db | ||
1b693cd798 | |||
![]() |
8426d8851f | ||
![]() |
eca5aa7bfe | ||
485b4c3c6c | |||
b35cdbd7b4 | |||
dbafeb9872 | |||
c6865a7742 | |||
fcb2ce119b | |||
9f74d568f0 | |||
e3137f4c3d | |||
3f58112a61 | |||
307085b0d5 | |||
44ea77b605 | |||
085c9038b5 | |||
af4e72ee27 | |||
9322241747 | |||
116a0288d4 | |||
d0322e7131 | |||
000cf1aff1 | |||
cf56b518d6 | |||
1262a9d784 | |||
e7a30b418a | |||
8924f787d4 | |||
b81aff7f8d | |||
1dfe256cfe | |||
ee8e4b1d8b | |||
0317eff6a5 | |||
793bd2cf5b | |||
6b9540a0a8 | |||
5687cf6a62 | |||
1fd2b5914b | |||
d863aafa8c | |||
5bce84b786 | |||
9eab9ad5f0 |
@@ -1,6 +1,6 @@
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
PYTHON: "C:\\python37\\python.exe"
|
||||
PYTHON: "C:\\python310\\python.exe"
|
||||
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
|
||||
|
||||
stages:
|
||||
@@ -17,9 +17,10 @@ twblue32:
|
||||
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||
- echo ${time}
|
||||
- echo "started by ${GITLAB_USER_NAME}"
|
||||
- choco install python --version 3.7.9 -y -ForceX86
|
||||
- choco install python --version 3.10.8 -y -ForceX86
|
||||
- '&$env:PYTHON -V'
|
||||
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||
- '&$env:PYTHON -m pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp310-cp310-win32.whl?raw=true'
|
||||
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
|
||||
stage: build
|
||||
interruptible: true
|
||||
@@ -56,7 +57,7 @@ twblue64:
|
||||
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
|
||||
- echo ${time}
|
||||
- echo "started by ${GITLAB_USER_NAME}"
|
||||
- choco install python --version 3.7.9 -y
|
||||
- choco install python --version 3.10.8 -y
|
||||
- '&$env:PYTHON -V'
|
||||
- '&$env:PYTHON -m pip install --upgrade pip'
|
||||
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
|
||||
|
19
README.md
19
README.md
@@ -21,6 +21,15 @@ See [TWBlue's webpage](http://twblue.es) for more details.
|
||||
|
||||
This document describes how to run tw blue from source and how to build a binary version which doesn't need Python and the other dependencies to run.
|
||||
|
||||
### Generating application keys
|
||||
|
||||
In order to communicate with Twitter, you will need to generate a set of API keys in their [developer portal](https://developer.twitter.com/en/portal/dashboard) (If you haven't signed up, [visit this site to register as a developer](https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api)) and create a module called appkeys.py, within the src directory, with the following content, replacing the example values with your set of API keys:
|
||||
|
||||
```
|
||||
twitter_api_key='xxxxxxxxxx'
|
||||
twitter_api_secret='xxxxxxxxxx'
|
||||
```
|
||||
|
||||
### Required dependencies.
|
||||
|
||||
Although most dependencies can be found in the windows-dependencies directory, we provide links to their official websites. If you are cloning with git, don't forget to initialize and update the submodules to get the windows-dependencies folder. You can use these two commands to perform this task from git bash:
|
||||
@@ -31,18 +40,22 @@ Although most dependencies can be found in the windows-dependencies directory, w
|
||||
|
||||
#### Dependencies packaged in windows installers
|
||||
|
||||
* [Python,](https://python.org) version 3.7.9
|
||||
If you want to build both x86 and x64 binaries, you can install python x86 to C:\python38 and python x64 to C:\python38x64, for example.
|
||||
* [Python,](https://python.org) version 3.10.8
|
||||
If you want to build both x86 and x64 binaries, you can install python x64 to C:\python310 and python x86 to C:\python310-32, for example.
|
||||
|
||||
#### Dependencies that must be installed using pip
|
||||
|
||||
Python installs a tool called Pip that allows to install packages in a simple way. You can find it in the python scripts directory. To install packages using Pip, you have to navigate to the scripts directory using a command prompt, for example:
|
||||
|
||||
`cd C:\python37x64\scripts`
|
||||
`cd C:\python310\scripts`
|
||||
|
||||
You can also add the scripts folder to your path environment variable or choose the corresponding option when installing Python.
|
||||
Note: pip and setuptools are included in the Python installer since version 2.7.9.
|
||||
|
||||
Note: If you are using Python for 32-bit systems, you will need to install WXPython for 32-bits before running the command for installing everything else. You can do so by running the following command:
|
||||
|
||||
`pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp310-cp310-win32.whl?raw=true`
|
||||
|
||||
Pip is able to install packages listed in a special text file, called the requirements file. To install all remaining dependencies, perform the following command:
|
||||
|
||||
`pip install -r requirements.txt`
|
||||
|
@@ -2,6 +2,39 @@ TWBlue Changelog
|
||||
|
||||
## changes in this version
|
||||
|
||||
Most of all changes in this release are focused on adding Mastodon support to TWBlue. The features present to handle Twitter should not have been altered in any way. We were not intended to release this version so soon, but unfortunately, Twitter started to present issues in some regions with one particular API endpoint we were using, making impossible for everyone in such regions to use the application. We will release more updates to fix any possible issue regarding Twitter API, but please take into account that this is sometimes an issue happening in Twitter's servers and while we do our best to make TWBlue work despite those problems, you might encounter glitches from time to time.
|
||||
|
||||
* TWBlue now builds with Python 3.10.8. ([#493](https://github.com/MCV-Software/TWBlue/issues/493))
|
||||
* This change also drops support for Windows 7.
|
||||
* The TWBlue interface has not been translated yet, as we are releasing this update to fix an important Twitter issue for some regions.
|
||||
* Twitter sessions should be able to be opened properly again in TWBlue, in regions where it didn't work since last week.
|
||||
* It is now possible to log in to instances of mastodon, hometown and similar software (Pleroma should work as well, although it has not been tested at this time). From the session manager, clicking on the “new account” button will bring up a menu from which you can select whether you want to log in to Twitter or Mastodon. For instances that have a different character limit than the one set by Mastodon, TWBlue will detect the new limit and adjust the dialogs to allow you to use it correctly.
|
||||
* Most of the TWBlue GUI has been adapted so that the buffers reflect the change of social network (in mastodon, for example, the buttons to write posts say post instead of tweet). However, the menu bar has not yet been updated. This means that most of the options still refer to Twitter, although they can be used with mastodon accounts. For example, if you select the “tweet” menu in the menu bar, and then select the “Retweet” option, TWBlue will actually do a “boost” if the buffer you are in is a Mastodon account buffer.
|
||||
* Keystrokes for the invisible interface also refer to terms used in Twitter, but can be applied to Mastodon as well.
|
||||
* There are some features, within TWBlue, that are not yet compatible with mastodon accounts. These are as follows:
|
||||
* User autocompletion.
|
||||
* Currently, it is not possible to update account settings for mastodon sessions. However, if you know how to edit configuration files, you can close TWBlue, change your session file with any text editor and restart the application to update what you want.
|
||||
* The template editor is not yet available for mastodon accounts.
|
||||
* Filters have not yet been implemented in TWBlue mastodon support.
|
||||
* User aliases are not implemented yet.
|
||||
* It is not possible to view a user’s profile, nor edit your own, for now. However, you can use the keystroke to open the item in the browser when focusing a user to access their profile website. This only works in buffers where users are listed.
|
||||
* You cannot manage lists in TWBlue at the moment.
|
||||
* Most of the buffers planned for mastodon should just work. Among those currently tested are: home (main timeline for the logged-in user), Local (public posts for the instance), federated (public posts for all federating instances), mentions, direct messages, sent posts, favorites, bookmarks, followers, following, blocked users, muted users, user searches and timelines for users.
|
||||
* The difference between favorites and bookmarks is that the author of the post can see who has marked his posts as favorites, but bookmarks are completely private. In any buffer containing mastodon posts, except direct messages, the GUI will display an option to add the post to favorites or bookmarks.
|
||||
* Direct messages in mastodon are posts, exactly like normal posts, but with their privacy setting set so that they can only be seen by the accounts that are mentioned. In the direct message buffer, a conversation will appear for each item in the buffer. The conversation represents a thread of messages, but TWBlue can only display the last of the messages sent. This is similar to what happens on platforms like Telegram, where you can only see the list of conversations at the beginning. To see the entire thread of direct messages present in a conversation, you can use the command to open the conversation, or go to the “tweet” menu in the menu bar, and then towards the “view conversation” option. This will create a new conversation buffer that will be located just after the direct messages buffer (for the GUI, the buffer will be located just inside the direct messages buffer in the buffer tree). When a private post appears (whose visibility only allows the mentioned accounts to see it), TWBlue will display that post in the home buffer, in mentions and also will update/create the conversation with that item. This is because Mastodon does not differentiate between a private message and a normal post. You can reply to the post in any buffer to continue the conversation. If you reply to any post, the privacy set in the original post is maintained by default, but can also be changed.
|
||||
* The buffer showing the federated timeline has been disabled from settings. This is because on servers that federate with many instances it can load many posts in a very short time. To enable this buffer, for now, edit the TWBlue configuration while the application is closed, and add the “federated” buffer in the option called “buffer_order”. As soon as buffers can be shown or hidden, this process can be done through the GUI.
|
||||
* There is a Streaming API that allows the elements for the start buffers, mentions, direct messages, sent posts and followers to appear in real time. This feature is implemented by default and should also just work.
|
||||
* Timelines for users only allow to get all posts from users who are in the same instance. For users belonging to other instances, you can get the posts that have been downloaded to your instance since your instance “knows” the remote user.
|
||||
* Timelines for followers and following can be fully retrieved only for users belonging to the same instance. Remote users may yield unclear results.
|
||||
* You can search by users (by opening a search and selecting the “users” radio button). The search can be done by local users, such as twblue, or by remote users, such as @twblue@maaw.social.
|
||||
* In all buffers, a maximum of 40 items are retrieved per load, but more can be retrieved by using the option to load more items in the buffer.
|
||||
* In post buffers, you can do most of the actions already supported in TWBlue (boost, add/remove from favorites or bookmarks, reply, send message to user, open post URL, play audio or video, open post on web, view conversation, open action dialog for user).
|
||||
* In user buffers, you can send private message to the user, and open user actions dialog, which in turn allows you to follow/unfollow, block/unblock and mute/unmute.
|
||||
* When writing posts, it is possible to attach up to 4 images, 4 givs, or even a video, poll, or audio. It is also possible to add the “sensitive content” tag to posts, change privacy and write a content warning text. It is possible to create threads using the “add post” button.
|
||||
* When replying to a post, TWBlue will place the username of all participants in the item you reply to. The privacy options will default to those of the original post.
|
||||
|
||||
## Changes in version 2022.8.28
|
||||
|
||||
* the user autocompletion feature has been completely rewritten to be easier to use, particularly for people with many followers/following users:
|
||||
* In the account settings dialog, there's a button that opens up a new dialog that allows you to "scan" your account in order to add all users from your followers/following list. This process will read your data directly from Twitter and depending in the amount of people you have in your account it might take too many API calls. Please use it with caution. You can, for example, do the process separately for your followers/following people so it will be easier to handle, in case you have a massive amount of people. If TWBlue is unable to complete the scan, you will see an error and will be prompted to try again in 15 minutes, once your API calls have refreshed.
|
||||
* It is possible to use the user autocompletion functionality in dialogs where you can select an user, for example when adding or removing someone from a list, or displaying lists for someone.
|
||||
|
@@ -1,9 +1,10 @@
|
||||
wxpython==4.1.1
|
||||
wxpython
|
||||
pytest
|
||||
coverage
|
||||
wheel
|
||||
six
|
||||
future
|
||||
configobj
|
||||
markdown
|
||||
future
|
||||
requests
|
||||
oauthlib
|
||||
requests-oauthlib
|
||||
@@ -12,7 +13,6 @@ pypubsub
|
||||
geopy
|
||||
arrow
|
||||
python-dateutil
|
||||
futures
|
||||
winpaths
|
||||
PySocks
|
||||
win_inet_pton
|
||||
@@ -31,6 +31,7 @@ backports.functools_lru_cache
|
||||
cx_freeze
|
||||
tweepy
|
||||
twitter-text-parser
|
||||
mastodon.py
|
||||
pyenchant
|
||||
sqlitedict
|
||||
cx-Logging
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import base as base
|
||||
from . import twitter as twitter
|
||||
from . import twitter as twitter
|
||||
from . import mastodon as mastodon
|
@@ -138,4 +138,7 @@ class Buffer(object):
|
||||
try:
|
||||
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
|
||||
except AttributeError:
|
||||
pass
|
||||
pass
|
||||
|
||||
def view_item(self):
|
||||
pass
|
@@ -1,2 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .base import BaseBuffer
|
||||
from .base import BaseBuffer
|
||||
from .mentions import MentionsBuffer
|
||||
from .conversations import ConversationBuffer, ConversationListBuffer
|
||||
from .users import UserBuffer
|
@@ -1,141 +1,498 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Common logic to all buffers in TWBlue."""
|
||||
import logging
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import wx
|
||||
import output
|
||||
import sound
|
||||
import widgetUtils
|
||||
import arrow
|
||||
import webbrowser
|
||||
import output
|
||||
import config
|
||||
import sound
|
||||
import languageHandler
|
||||
import logging
|
||||
from audio_services import youtube_utils
|
||||
from controller.buffers.base import base
|
||||
from controller.mastodon import messages
|
||||
from sessions.mastodon import compose, utils, templates
|
||||
from mysc.thread_utils import call_threaded
|
||||
from pubsub import pub
|
||||
from extra import ocr
|
||||
from wxUI import buffers, dialogs, commonMessageDialogs
|
||||
from wxUI.dialogs.mastodon import menus
|
||||
from wxUI.dialogs.mastodon import dialogs as mastodon_dialogs
|
||||
|
||||
log = logging.getLogger("controller.buffers.base.base")
|
||||
log = logging.getLogger("controller.buffers.mastodon.base")
|
||||
|
||||
class Buffer(object):
|
||||
""" A basic buffer object. This should be the base class for all other derived buffers."""
|
||||
class BaseBuffer(base.Buffer):
|
||||
def __init__(self, parent, function, name, sessionObject, account, sound=None, compose_func="compose_post", *args, **kwargs):
|
||||
super(BaseBuffer, self).__init__(parent, function, *args, **kwargs)
|
||||
log.debug("Initializing buffer %s, account %s" % (name, account,))
|
||||
self.create_buffer(parent, name)
|
||||
self.invisible = True
|
||||
self.name = name
|
||||
self.type = self.buffer.type
|
||||
self.session = sessionObject
|
||||
self.compose_function = getattr(compose, compose_func)
|
||||
log.debug("Compose_function: %s" % (self.compose_function,))
|
||||
self.account = account
|
||||
self.buffer.account = account
|
||||
self.bind_events()
|
||||
self.sound = sound
|
||||
if "-timeline" in self.name or "-followers" in self.name or "-following" in self.name:
|
||||
self.finished_timeline = False
|
||||
|
||||
def __init__(self, parent=None, function=None, session=None, *args, **kwargs):
|
||||
"""Inits the main controller for this buffer:
|
||||
@ parent wx.Treebook object: Container where we will put this buffer.
|
||||
@ function str or None: function to be called periodically and update items on this buffer.
|
||||
@ session sessionmanager.session object or None: Session handler for settings, database and data access.
|
||||
"""
|
||||
super(Buffer, self).__init__()
|
||||
self.function = function
|
||||
# Compose_function will be used to render an object on this buffer. Normally, signature is as follows:
|
||||
# compose_function(item, db, relative_times, show_screen_names=False, session=None)
|
||||
# Read more about compose functions in sessions/twitter/compose.py.
|
||||
self.compose_function = None
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
# This will be used as a reference to the wx.Panel object wich stores the buffer GUI.
|
||||
self.buffer = None
|
||||
# This should countains the account associated to this buffer.
|
||||
self.account = ""
|
||||
# This controls whether the start_stream function should be called when starting the program.
|
||||
self.needs_init = True
|
||||
# if this is set to False, the buffer will be ignored on the invisible interface.
|
||||
self.invisible = False
|
||||
# Control variable, used to track time of execution for calls to start_stream.
|
||||
self.execution_time = 0
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = buffers.mastodon.basePanel(parent, name)
|
||||
|
||||
def clear_list(self):
|
||||
pass
|
||||
|
||||
def get_event(self, ev):
|
||||
""" Catch key presses in the WX interface and generate the corresponding event names."""
|
||||
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio"
|
||||
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url"
|
||||
elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
|
||||
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
|
||||
elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list"
|
||||
elif ev.GetKeyCode() == wx.WXK_DELETE: event = "destroy_status"
|
||||
# Raise a Special event when pressed Shift+F10 because Wx==4.1.x does not seems to trigger this by itself.
|
||||
# See https://github.com/manuelcortez/TWBlue/issues/353
|
||||
elif ev.GetKeyCode() == wx.WXK_F10 and ev.ShiftDown(): event = "show_menu"
|
||||
else:
|
||||
event = None
|
||||
ev.Skip()
|
||||
if event != None:
|
||||
try:
|
||||
### ToDo: Remove after WX fixes issue #353 in the widgets.
|
||||
if event == "show_menu":
|
||||
return self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
|
||||
getattr(self, event)()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def volume_down(self):
|
||||
""" Decreases volume by 5%"""
|
||||
if self.session.settings["sound"]["volume"] > 0.0:
|
||||
if self.session.settings["sound"]["volume"] <= 0.05:
|
||||
self.session.settings["sound"]["volume"] = 0.0
|
||||
else:
|
||||
self.session.settings["sound"]["volume"] -=0.05
|
||||
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0))
|
||||
self.session.sound.play("volume_changed.ogg")
|
||||
self.session.settings.write()
|
||||
|
||||
def volume_up(self):
|
||||
""" Increases volume by 5%."""
|
||||
if self.session.settings["sound"]["volume"] < 1.0:
|
||||
if self.session.settings["sound"]["volume"] >= 0.95:
|
||||
self.session.settings["sound"]["volume"] = 1.0
|
||||
else:
|
||||
self.session.settings["sound"]["volume"] +=0.05
|
||||
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100))
|
||||
self.session.sound.play("volume_changed.ogg")
|
||||
self.session.settings.write()
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True):
|
||||
pass
|
||||
|
||||
def get_more_items(self):
|
||||
output.speak(_(u"This action is not supported for this buffer"), True)
|
||||
|
||||
def put_items_on_list(self, items):
|
||||
pass
|
||||
|
||||
def remove_buffer(self):
|
||||
return False
|
||||
|
||||
def remove_item(self, item):
|
||||
f = self.buffer.list.get_selected()
|
||||
self.buffer.list.remove_item(item)
|
||||
self.buffer.list.select_item(f)
|
||||
|
||||
def bind_events(self):
|
||||
pass
|
||||
|
||||
def get_object(self):
|
||||
return self.buffer
|
||||
|
||||
def get_message(self):
|
||||
pass
|
||||
|
||||
def set_list_position(self, reversed=False):
|
||||
if reversed == False:
|
||||
self.buffer.list.select_item(-1)
|
||||
else:
|
||||
self.buffer.list.select_item(0)
|
||||
|
||||
def reply(self):
|
||||
pass
|
||||
|
||||
def send_message(self):
|
||||
pass
|
||||
|
||||
def share_item(self):
|
||||
pass
|
||||
|
||||
def can_share(self):
|
||||
pass
|
||||
|
||||
def destroy_status(self):
|
||||
pass
|
||||
def get_buffer_name(self):
|
||||
""" Get buffer name from a set of different techniques."""
|
||||
# firstly let's take the easier buffers.
|
||||
basic_buffers = dict(home_timeline=_("Home"), local_timeline=_("Local"), federated_timeline=_("Federated"), mentions=_("Mentions"), bookmarks=_("Bookmarks"), direct_messages=_("Direct messages"), sent=_("Sent"), favorites=_("Favorites"), followers=_("Followers"), following=_("Following"), blocked=_("Blocked users"), muted=_("Muted users"), notifications=_("Notifications"))
|
||||
if self.name in list(basic_buffers.keys()):
|
||||
return basic_buffers[self.name]
|
||||
# Check user timelines
|
||||
elif hasattr(self, "username"):
|
||||
if "-timeline" in self.name:
|
||||
return _(u"{username}'s timeline").format(username=self.username,)
|
||||
elif "-followers" in self.name:
|
||||
return _(u"{username}'s followers").format(username=self.username,)
|
||||
elif "-following" in self.name:
|
||||
return _(u"{username}'s following").format(username=self.username,)
|
||||
log.error("Error getting name for buffer %s" % (self.name,))
|
||||
return _(u"Unknown buffer")
|
||||
|
||||
def post_status(self, *args, **kwargs):
|
||||
title = _("Post")
|
||||
caption = _("Write your post here")
|
||||
post = messages.post(session=self.session, title=title, caption=caption)
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, posts=post_data, visibility=post.get_visibility(), **kwargs)
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def get_formatted_message(self):
|
||||
return self.compose_function(self.get_item(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])[1]
|
||||
|
||||
def get_message(self):
|
||||
post = self.get_item()
|
||||
if post == None:
|
||||
return
|
||||
template = self.session.settings["templates"]["post"]
|
||||
|
||||
t = templates.render_post(post, template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
||||
return t
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
self.execution_time = current_time
|
||||
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
|
||||
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
min_id = None
|
||||
# toDo: Implement reverse timelines properly here.
|
||||
if (self.name != "favorites" and self.name != "bookmarks") and self.name in self.session.db and len(self.session.db[self.name]) > 0:
|
||||
min_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
results = getattr(self.session.api, self.function)(min_id=min_id, limit=count, *self.args, **self.kwargs)
|
||||
results.reverse()
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
|
||||
if "-timeline" in self.name:
|
||||
self.username = self.session.db[self.name][0]["account"].username
|
||||
self.finished_timeline = True
|
||||
self.put_items_on_list(number_of_items)
|
||||
if number_of_items > 0 and self.name != "sent_posts" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def auto_read(self, number_of_items):
|
||||
if number_of_items == 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
post = self.session.db[self.name][-1]
|
||||
else:
|
||||
post = self.session.db[self.name][0]
|
||||
output.speak(_("New post in {0}").format(self.get_buffer_name()))
|
||||
output.speak(" ".join(self.compose_function(post, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])))
|
||||
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
|
||||
output.speak(_("{0} new posts in {1}.").format(number_of_items, self.get_buffer_name()))
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
max_id = self.session.db[self.name][0].id
|
||||
else:
|
||||
max_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
items = getattr(self.session.api, self.function)(max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], *self.args, **self.kwargs)
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
for i in elements:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(True, *post)
|
||||
else:
|
||||
for i in items:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
self.buffer.list.select_item(selection)
|
||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||
|
||||
def remove_buffer(self, force=False):
|
||||
if "-timeline" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.kwargs.get("id") in self.session.settings["other_buffers"]["timelines"]:
|
||||
self.session.settings["other_buffers"]["timelines"].remove(self.kwargs.get("id"))
|
||||
self.session.settings.write()
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
else:
|
||||
output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True)
|
||||
return False
|
||||
|
||||
def put_items_on_list(self, number_of_items):
|
||||
list_to_use = self.session.db[self.name]
|
||||
if number_of_items == 0 and self.session.settings["general"]["persist_size"] == 0: return
|
||||
log.debug("The list contains %d items " % (self.buffer.list.get_count(),))
|
||||
log.debug("Putting %d items on the list" % (number_of_items,))
|
||||
if self.buffer.list.get_count() == 0:
|
||||
for i in list_to_use:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
|
||||
elif self.buffer.list.get_count() > 0 and number_of_items > 0:
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items = list_to_use[len(list_to_use)-number_of_items:]
|
||||
for i in items:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
else:
|
||||
items = list_to_use[0:number_of_items]
|
||||
items.reverse()
|
||||
for i in items:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(True, *post)
|
||||
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
|
||||
|
||||
def add_new_item(self, item):
|
||||
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
else:
|
||||
self.buffer.list.insert_item(True, *post)
|
||||
if self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
|
||||
output.speak(" ".join(post[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
|
||||
|
||||
def update_item(self, item, position):
|
||||
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.list.SetItem(position, 1, post[1])
|
||||
|
||||
def bind_events(self):
|
||||
log.debug("Binding events...")
|
||||
self.buffer.set_focus_function(self.onFocus)
|
||||
widgetUtils.connect_event(self.buffer.list.list, widgetUtils.KEYPRESS, self.get_event)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.post)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.share_item, self.buffer.boost)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.send_message, self.buffer.dm)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.toggle_favorite, self.buffer.fav)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.toggle_bookmark, self.buffer.bookmark)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
def show_menu(self, ev, pos=0, *args, **kwargs):
|
||||
if self.buffer.list.get_count() == 0:
|
||||
return
|
||||
menu = menus.base()
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.audio, menuitem=menu.play)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.destroy_status, menuitem=menu.remove)
|
||||
if hasattr(menu, "openInBrowser"):
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.open_in_browser, menuitem=menu.openInBrowser)
|
||||
if pos != 0:
|
||||
self.buffer.PopupMenu(menu, pos)
|
||||
else:
|
||||
self.buffer.PopupMenu(menu, self.buffer.list.list.GetPosition())
|
||||
|
||||
def view(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="view_item")
|
||||
|
||||
def copy(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="copy_to_clipboard")
|
||||
|
||||
def user_actions(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="follow")
|
||||
|
||||
def fav(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="add_to_favourites")
|
||||
|
||||
def unfav(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="remove_from_favourites")
|
||||
|
||||
def delete_item_(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="delete_item")
|
||||
|
||||
def url_(self, *args, **kwargs):
|
||||
self.url()
|
||||
|
||||
def show_menu_by_key(self, ev):
|
||||
if self.buffer.list.get_count() == 0:
|
||||
return
|
||||
if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU:
|
||||
self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
|
||||
|
||||
def get_item(self):
|
||||
index = self.buffer.list.get_selected()
|
||||
if index > -1 and self.session.db.get(self.name) != None:
|
||||
return self.session.db[self.name][index]
|
||||
|
||||
def can_share(self):
|
||||
post = self.get_item()
|
||||
if post.visibility == "direct":
|
||||
return False
|
||||
return True
|
||||
|
||||
def reply(self, *args):
|
||||
item = self.get_item()
|
||||
visibility = item.visibility
|
||||
if visibility == "direct":
|
||||
title = _("Conversation with {}").format(item.account.username)
|
||||
caption = _("Write your message here")
|
||||
else:
|
||||
title = _("Reply to {}").format(item.account.username)
|
||||
caption = _("Write your reply here")
|
||||
if item.reblog != None:
|
||||
users = ["@{} ".format(user.acct) for user in item.reblog.mentions if user.id != self.session.db["user_id"]]
|
||||
if item.reblog.account.acct != item.account.acct and "@{} ".format(item.reblog.account.acct) not in users:
|
||||
users.append("@{} ".format(item.reblog.account.acct))
|
||||
else:
|
||||
users = ["@{} ".format(user.acct) for user in item.mentions if user.id != self.session.db["user_id"]]
|
||||
if "@{} ".format(item.account.acct) not in users and item.account.id != self.session.db["user_id"]:
|
||||
users.insert(0, "@{} ".format(item.account.acct))
|
||||
users_str = "".join(users)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
visibility_settings = dict(public=0, unlisted=1, private=2, direct=3)
|
||||
post.message.visibility.SetSelection(visibility_settings.get(visibility))
|
||||
# Respect content warning settings.
|
||||
if item.sensitive:
|
||||
post.message.sensitive.SetValue(item.sensitive)
|
||||
post.message.spoiler.ChangeValue(item.spoiler_text)
|
||||
post.message.on_sensitivity_changed()
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, reply_to=item.id, posts=post_data, visibility=post.get_visibility())
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
title = _("Conversation with {}").format(item.account.username)
|
||||
caption = _("Write your message here")
|
||||
if item.reblog != None:
|
||||
users = ["@{} ".format(user.acct) for user in item.reblog.mentions if user.id != self.session.db["user_id"]]
|
||||
if item.reblog.account.acct != item.account.acct and "@{} ".format(item.reblog.account.acct) not in users:
|
||||
users.append("@{} ".format(item.reblog.account.acct))
|
||||
else:
|
||||
users = ["@{} ".format(user.acct) for user in item.mentions if user.id != self.session.db["user_id"]]
|
||||
if item.account.acct not in users and item.account.id != self.session.db["user_id"]:
|
||||
users.insert(0, "@{} ".format(item.account.acct))
|
||||
users_str = "".join(users)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
post.message.visibility.SetSelection(3)
|
||||
if item.sensitive:
|
||||
post.message.sensitive.SetValue(item.sensitive)
|
||||
post.message.spoiler.ChangeValue(item.spoiler_text)
|
||||
post.message.on_sensitivity_changed()
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, posts=post_data, visibility="direct", reply_to=item.id)
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def share_item(self, *args, **kwargs):
|
||||
if self.can_share() == False:
|
||||
return output.speak(_("This action is not supported on conversation posts."))
|
||||
post = self.get_item()
|
||||
id = post.id
|
||||
if self.session.settings["general"]["boost_mode"] == "ask":
|
||||
answer = mastodon_dialogs.boost_question()
|
||||
if answer == True:
|
||||
self._direct_boost(id)
|
||||
else:
|
||||
self._direct_boost(id)
|
||||
|
||||
def _direct_boost(self, id):
|
||||
item = self.session.api_call(call_name="status_reblog", _sound="retweet_send.ogg", id=id)
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
post = self.get_item()
|
||||
if self.session.settings["general"]["relative_times"] == True:
|
||||
original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at)
|
||||
ts = original_date.humanize(locale=languageHandler.getLanguage())
|
||||
self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 2, ts)
|
||||
if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post):
|
||||
self.session.sound.play("audio.ogg")
|
||||
if self.session.settings['sound']['indicate_img'] and utils.is_image(post):
|
||||
self.session.sound.play("image.ogg")
|
||||
can_share = self.can_share()
|
||||
pub.sendMessage("toggleShare", shareable=can_share)
|
||||
self.buffer.boost.Enable(can_share)
|
||||
|
||||
def audio(self, url='', *args, **kwargs):
|
||||
if sound.URLPlayer.player.is_playing():
|
||||
return sound.URLPlayer.stop_audio()
|
||||
item = self.get_item()
|
||||
if item == None:
|
||||
return
|
||||
urls = utils.get_media_urls(item)
|
||||
if len(urls) == 1:
|
||||
url=urls[0]
|
||||
elif len(urls) > 1:
|
||||
urls_list = dialogs.urlList.urlList()
|
||||
urls_list.populate_list(urls)
|
||||
if urls_list.get_response() == widgetUtils.OK:
|
||||
url=urls_list.get_string()
|
||||
if hasattr(urls_list, "destroy"): urls_list.destroy()
|
||||
if url != '':
|
||||
# try:
|
||||
sound.URLPlayer.play(url, self.session.settings["sound"]["volume"])
|
||||
# except:
|
||||
# log.error("Exception while executing audio method.")
|
||||
|
||||
def url(self, url='', announce=True, *args, **kwargs):
|
||||
if url == '':
|
||||
post = self.get_item()
|
||||
if post.reblog != None:
|
||||
urls = utils.find_urls(post.reblog)
|
||||
else:
|
||||
urls = utils.find_urls(post)
|
||||
if len(urls) == 1:
|
||||
url=urls[0]
|
||||
elif len(urls) > 1:
|
||||
urls_list = dialogs.urlList.urlList()
|
||||
urls_list.populate_list(urls)
|
||||
if urls_list.get_response() == widgetUtils.OK:
|
||||
url=urls_list.get_string()
|
||||
if hasattr(urls_list, "destroy"): urls_list.destroy()
|
||||
if url != '':
|
||||
if announce:
|
||||
output.speak(_(u"Opening URL..."), True)
|
||||
webbrowser.open_new_tab(url)
|
||||
|
||||
def clear_list(self):
|
||||
dlg = commonMessageDialogs.clear_list()
|
||||
if dlg == widgetUtils.YES:
|
||||
self.session.db[self.name] = []
|
||||
self.buffer.list.clear()
|
||||
|
||||
def destroy_status(self, *args, **kwargs):
|
||||
index = self.buffer.list.get_selected()
|
||||
item = self.session.db[self.name][index]
|
||||
if item.account.id != self.session.db["user_id"] or item.reblog != None:
|
||||
output.speak(_("You can delete only your own posts."))
|
||||
return
|
||||
answer = mastodon_dialogs.delete_post_dialog()
|
||||
if answer == True:
|
||||
items = self.session.db[self.name]
|
||||
try:
|
||||
self.session.api.status_delete(id=item.id)
|
||||
items.pop(index)
|
||||
self.buffer.list.remove_item(index)
|
||||
except Exception as e:
|
||||
self.session.sound.play("error.ogg")
|
||||
self.session.db[self.name] = items
|
||||
|
||||
def user_details(self):
|
||||
item = self.get_item()
|
||||
pass
|
||||
|
||||
def save_positions(self):
|
||||
try:
|
||||
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
|
||||
except AttributeError:
|
||||
pass
|
||||
def get_item_url(self):
|
||||
post = self.get_item()
|
||||
if post.reblog != None:
|
||||
return post.reblog.url
|
||||
return post.url
|
||||
|
||||
def open_in_browser(self, *args, **kwargs):
|
||||
url = self.get_item_url()
|
||||
output.speak(_("Opening item in web browser..."))
|
||||
webbrowser.open(url)
|
||||
|
||||
def add_to_favorites(self):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def remove_from_favorites(self):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def toggle_favorite(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
item = self.session.api.status(item.id)
|
||||
if item.favourited == False:
|
||||
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def toggle_bookmark(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
item = self.session.api.status(item.id)
|
||||
if item.bookmarked == False:
|
||||
call_threaded(self.session.api_call, call_name="status_bookmark", preexec_message=_("Adding to bookmarks..."), _sound="favourite.ogg", id=item.id)
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="status_unbookmark", preexec_message=_("Removing from bookmarks..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def view_item(self):
|
||||
post = self.get_item()
|
||||
# Update object so we can retrieve newer stats
|
||||
post = self.session.api.status(id=post.id)
|
||||
print(post)
|
||||
msg = messages.viewPost(post, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url())
|
||||
|
||||
def ocr_image(self):
|
||||
post = self.get_item()
|
||||
media_list = []
|
||||
pass
|
241
src/controller/buffers/mastodon/conversations.py
Normal file
241
src/controller/buffers/mastodon/conversations.py
Normal file
@@ -0,0 +1,241 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import logging
|
||||
import wx
|
||||
import widgetUtils
|
||||
import output
|
||||
from controller.mastodon import messages
|
||||
from controller.buffers.mastodon.base import BaseBuffer
|
||||
from mysc.thread_utils import call_threaded
|
||||
from sessions.mastodon import utils, templates
|
||||
from wxUI import buffers, commonMessageDialogs
|
||||
log = logging.getLogger("controller.buffers.mastodon.conversations")
|
||||
|
||||
class ConversationListBuffer(BaseBuffer):
|
||||
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = buffers.mastodon.conversationListPanel(parent, name)
|
||||
|
||||
def get_item(self):
|
||||
index = self.buffer.list.get_selected()
|
||||
if index > -1 and self.session.db.get(self.name) != None and len(self.session.db[self.name]) > index:
|
||||
return self.session.db[self.name][index]["last_status"]
|
||||
|
||||
def get_conversation(self):
|
||||
index = self.buffer.list.get_selected()
|
||||
if index > -1 and self.session.db.get(self.name) != None:
|
||||
return self.session.db[self.name][index]
|
||||
|
||||
def get_formatted_message(self):
|
||||
return self.compose_function(self.get_conversation(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])[1]
|
||||
|
||||
def get_message(self):
|
||||
conversation = self.get_conversation()
|
||||
if conversation == None:
|
||||
return
|
||||
template = self.session.settings["templates"]["conversation"]
|
||||
post_template = self.session.settings["templates"]["post"]
|
||||
t = templates.render_conversation(conversation=conversation, template=template, post_template=post_template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
||||
return t
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
self.execution_time = current_time
|
||||
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
|
||||
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
min_id = None
|
||||
# toDo: Implement reverse timelines properly here.
|
||||
# if (self.name != "favorites" and self.name != "bookmarks") and self.name in self.session.db and len(self.session.db[self.name]) > 0:
|
||||
# min_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
results = getattr(self.session.api, self.function)(min_id=min_id, limit=count, *self.args, **self.kwargs)
|
||||
results.reverse()
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
new_position, number_of_items = self.order_buffer(results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
self.put_items_on_list(number_of_items)
|
||||
if new_position > -1:
|
||||
self.buffer.list.select_item(new_position)
|
||||
if number_of_items > 0 and self.name != "sent_posts" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
max_id = self.session.db[self.name][0].last_status.id
|
||||
else:
|
||||
max_id = self.session.db[self.name][-1].last_status.id
|
||||
try:
|
||||
items = getattr(self.session.api, self.function)(max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], *self.args, **self.kwargs)
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
for i in elements:
|
||||
conversation = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(True, *conversation)
|
||||
else:
|
||||
for i in items:
|
||||
conversation = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(False, *conversation)
|
||||
self.buffer.list.select_item(selection)
|
||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||
|
||||
def get_item_position(self, conversation):
|
||||
for i in range(len(self.session.db[self.name])):
|
||||
if self.session.db[self.name][i].id == conversation.id:
|
||||
return i
|
||||
|
||||
def order_buffer(self, data):
|
||||
num = 0
|
||||
focus_object = None
|
||||
if self.session.db.get(self.name) == None:
|
||||
self.session.db[self.name] = []
|
||||
objects = self.session.db[self.name]
|
||||
for i in data:
|
||||
position = self.get_item_position(i)
|
||||
if position != None:
|
||||
conversation = self.session.db[self.name][position]
|
||||
if conversation.last_status.id != i.last_status.id:
|
||||
focus_object = i
|
||||
objects.pop(position)
|
||||
self.buffer.list.remove_item(position)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
objects.append(i)
|
||||
else:
|
||||
objects.insert(0, i)
|
||||
num = num+1
|
||||
else:
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
objects.append(i)
|
||||
else:
|
||||
objects.insert(0, i)
|
||||
num = num+1
|
||||
self.session.db[self.name] = objects
|
||||
if focus_object == None:
|
||||
return (-1, num)
|
||||
new_position = self.get_item_position(focus_object)
|
||||
if new_position != None:
|
||||
return (new_position, num)
|
||||
return (-1, num)
|
||||
|
||||
def bind_events(self):
|
||||
log.debug("Binding events...")
|
||||
self.buffer.set_focus_function(self.onFocus)
|
||||
widgetUtils.connect_event(self.buffer.list.list, widgetUtils.KEYPRESS, self.get_event)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.post)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
def fav(self):
|
||||
pass
|
||||
|
||||
def unfav(self):
|
||||
pass
|
||||
|
||||
def can_share(self):
|
||||
return False
|
||||
|
||||
def send_message(self):
|
||||
return self.reply()
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
post = self.get_item()
|
||||
if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post):
|
||||
self.session.sound.play("audio.ogg")
|
||||
if self.session.settings['sound']['indicate_img'] and utils.is_image(post):
|
||||
self.session.sound.play("image.ogg")
|
||||
|
||||
def destroy_status(self):
|
||||
pass
|
||||
|
||||
def reply(self, *args):
|
||||
item = self.get_item()
|
||||
conversation = self.get_conversation()
|
||||
visibility = item.visibility
|
||||
title = _("Reply to conversation with {}").format(conversation.accounts[0].username)
|
||||
caption = _("Write your message here")
|
||||
users = ["@{} ".format(user.acct) for user in conversation.accounts]
|
||||
users_str = "".join(users)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
visibility_settings = dict(public=0, unlisted=1, private=2, direct=3)
|
||||
post.message.visibility.SetSelection(visibility_settings.get(visibility))
|
||||
if item.sensitive:
|
||||
post.message.sensitive.SetValue(item.sensitive)
|
||||
post.message.spoiler.ChangeValue(item.spoiler_text)
|
||||
post.message.on_sensitivity_changed()
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, reply_to=item.id, posts=post_data, visibility=visibility)
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
class ConversationBuffer(BaseBuffer):
|
||||
|
||||
def __init__(self, post, *args, **kwargs):
|
||||
self.post = post
|
||||
super(ConversationBuffer, self).__init__(*args, **kwargs)
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
self.execution_time = current_time
|
||||
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
|
||||
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
self.post = self.session.api.status(id=self.post.id)
|
||||
# toDo: Implement reverse timelines properly here.
|
||||
try:
|
||||
results = []
|
||||
items = getattr(self.session.api, self.function)(*self.args, **self.kwargs)
|
||||
[results.append(item) for item in items.ancestors]
|
||||
results.append(self.post)
|
||||
[results.append(item) for item in items.descendants]
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
self.put_items_on_list(number_of_items)
|
||||
if number_of_items > 0 and self.name != "sent_posts" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
|
||||
def get_more_items(self):
|
||||
output.speak(_(u"This action is not supported for this buffer"), True)
|
||||
|
||||
def remove_buffer(self, force=False):
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
71
src/controller/buffers/mastodon/mentions.py
Normal file
71
src/controller/buffers/mastodon/mentions.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import logging
|
||||
from controller.buffers.mastodon.base import BaseBuffer
|
||||
from sessions.mastodon import utils
|
||||
|
||||
log = logging.getLogger("controller.buffers.mastodon.mentions")
|
||||
|
||||
class MentionsBuffer(BaseBuffer):
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
self.execution_time = current_time
|
||||
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
|
||||
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
min_id = None
|
||||
# toDo: Implement reverse timelines properly here.
|
||||
# if self.name != "favorites" and self.name in self.session.db and len(self.session.db[self.name]) > 0:
|
||||
# min_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
items = getattr(self.session.api, self.function)(min_id=min_id, limit=count, exclude_types=["follow", "favourite", "reblog", "poll", "follow_request"], *self.args, **self.kwargs)
|
||||
items.reverse()
|
||||
results = [item.status for item in items if item.get("status") and item.type == "mention"]
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
self.put_items_on_list(number_of_items)
|
||||
if number_of_items > 0 and self.name != "sent_posts" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
max_id = self.session.db[self.name][0].id
|
||||
else:
|
||||
max_id = self.session.db[self.name][-1].id
|
||||
try:
|
||||
items = getattr(self.session.api, self.function)(max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], exclude_types=["follow", "favourite", "reblog", "poll", "follow_request"], *self.args, **self.kwargs)
|
||||
items = [item.status for item in items if item.get("status") and item.type == "mention"]
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
for i in elements:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(True, *post)
|
||||
else:
|
||||
for i in items:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
self.buffer.list.select_item(selection)
|
||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
202
src/controller/buffers/mastodon/users.py
Normal file
202
src/controller/buffers/mastodon/users.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import logging
|
||||
import wx
|
||||
import widgetUtils
|
||||
import output
|
||||
from mysc.thread_utils import call_threaded
|
||||
from controller.buffers.mastodon.base import BaseBuffer
|
||||
from controller.mastodon import messages
|
||||
from sessions.mastodon import templates, utils
|
||||
from wxUI import buffers, commonMessageDialogs
|
||||
|
||||
log = logging.getLogger("controller.buffers.mastodon.conversations")
|
||||
|
||||
class UserBuffer(BaseBuffer):
|
||||
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = buffers.mastodon.userPanel(parent, name)
|
||||
|
||||
def get_message(self):
|
||||
user = self.get_item()
|
||||
if user == None:
|
||||
return
|
||||
template = self.session.settings["templates"]["person"]
|
||||
t = templates.render_user(user=user, template=template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
|
||||
return t
|
||||
|
||||
def bind_events(self):
|
||||
widgetUtils.connect_event(self.buffer.list.list, widgetUtils.KEYPRESS, self.get_event)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.post)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.send_message, self.buffer.message)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.user_actions, self.buffer.actions)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
def fav(self):
|
||||
pass
|
||||
|
||||
def unfav(self):
|
||||
pass
|
||||
|
||||
def can_share(self):
|
||||
return False
|
||||
|
||||
def reply(self, *args, **kwargs):
|
||||
return self.send_message()
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
title = _("New conversation with {}").format(item.username)
|
||||
caption = _("Write your message here")
|
||||
users_str = "@{} ".format(item.acct)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
post.message.visibility.SetSelection(3)
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, posts=post_data, visibility="direct")
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def audio(self):
|
||||
pass
|
||||
|
||||
def url(self):
|
||||
pass
|
||||
|
||||
def destroy_status(self):
|
||||
pass
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
|
||||
self.execution_time = current_time
|
||||
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
|
||||
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
|
||||
count = self.session.settings["general"]["max_posts_per_call"]
|
||||
# toDo: Implement reverse timelines properly here.
|
||||
try:
|
||||
results = getattr(self.session.api, self.function)(limit=count, *self.args, **self.kwargs)
|
||||
if hasattr(results, "_pagination_next") and self.name not in self.session.db["pagination_info"]:
|
||||
self.session.db["pagination_info"][self.name] = results._pagination_next
|
||||
results.reverse()
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
number_of_items = self.session.order_buffer(self.name, results)
|
||||
log.debug("Number of items retrieved: %d" % (number_of_items,))
|
||||
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
|
||||
if "-followers" in self.name or "-following" in self.name:
|
||||
self.username = self.session.api.account(id=self.kwargs.get("id")).username
|
||||
self.finished_timeline = True
|
||||
self.put_items_on_list(number_of_items)
|
||||
if number_of_items > 0 and self.name != "sent_posts" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
|
||||
self.session.sound.play(self.sound)
|
||||
# Autoread settings
|
||||
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
|
||||
self.auto_read(number_of_items)
|
||||
return number_of_items
|
||||
|
||||
def get_more_items(self):
|
||||
elements = []
|
||||
pagination_info = self.session.db["pagination_info"].get(self.name)
|
||||
if pagination_info == None:
|
||||
output.speak(_("There are no more items in this buffer."))
|
||||
return
|
||||
try:
|
||||
items = self.session.api.fetch_next(pagination_info)
|
||||
if hasattr(items, "_pagination_next"):
|
||||
self.session.db["pagination_info"][self.name] = items._pagination_next
|
||||
except Exception as e:
|
||||
log.exception("Error %s" % (str(e)))
|
||||
return
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
else:
|
||||
items_db.append(i)
|
||||
self.session.db[self.name] = items_db
|
||||
selection = self.buffer.list.get_selected()
|
||||
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
for i in elements:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(True, *post)
|
||||
else:
|
||||
for i in items:
|
||||
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
|
||||
self.buffer.list.insert_item(False, *post)
|
||||
self.buffer.list.select_item(selection)
|
||||
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
|
||||
|
||||
def get_item_url(self):
|
||||
item = self.get_item()
|
||||
return item.url
|
||||
|
||||
def user_details(self):
|
||||
item = self.get_item()
|
||||
pass
|
||||
|
||||
def add_to_favorites(self):
|
||||
pass
|
||||
|
||||
def remove_from_favorites(self):
|
||||
pass
|
||||
|
||||
def toggle_favorite(self):
|
||||
pass
|
||||
|
||||
def view_item(self):
|
||||
item = self.get_item()
|
||||
print(item)
|
||||
|
||||
def ocr_image(self):
|
||||
pass
|
||||
|
||||
def remove_buffer(self, force=False):
|
||||
if "-followers" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.kwargs.get("id") in self.session.settings["other_buffers"]["followers_timelines"]:
|
||||
self.session.settings["other_buffers"]["followers_timelines"].remove(self.kwargs.get("id"))
|
||||
self.session.settings.write()
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
elif "-following" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.kwargs.get("id") in self.session.settings["other_buffers"]["following_timelines"]:
|
||||
self.session.settings["other_buffers"]["following_timelines"].remove(self.kwargs.get("id"))
|
||||
self.session.settings.write()
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
elif "-searchUser" in self.name:
|
||||
if force == False:
|
||||
dlg = commonMessageDialogs.remove_buffer()
|
||||
else:
|
||||
dlg = widgetUtils.YES
|
||||
if dlg == widgetUtils.YES:
|
||||
if self.name in self.session.db:
|
||||
self.session.db.pop(self.name)
|
||||
return True
|
||||
elif dlg == widgetUtils.NO:
|
||||
return False
|
||||
else:
|
||||
output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True)
|
||||
return False
|
@@ -1,14 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
import wx
|
||||
from wxUI import buffers, dialogs, commonMessageDialogs, menus
|
||||
from controller import user
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import buffers, dialogs, commonMessageDialogs
|
||||
from controller import messages
|
||||
import wx
|
||||
import widgetUtils
|
||||
import arrow
|
||||
import webbrowser
|
||||
@@ -24,7 +16,10 @@ from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from tweepy.cursor import Cursor
|
||||
from pubsub import pub
|
||||
from extra import ocr
|
||||
from sessions.twitter.long_tweets import twishort, tweets
|
||||
from wxUI import buffers, dialogs, commonMessageDialogs, menus
|
||||
from controller.twitter import user, messages
|
||||
|
||||
log = logging.getLogger("controller.buffers")
|
||||
|
||||
@@ -40,9 +35,9 @@ class BaseBuffer(base.Buffer):
|
||||
super(BaseBuffer, self).__init__(parent, function, *args, **kwargs)
|
||||
log.debug("Initializing buffer %s, account %s" % (name, account,))
|
||||
if bufferType != None:
|
||||
self.buffer = getattr(buffers, bufferType)(parent, name)
|
||||
self.buffer = getattr(buffers.twitter, bufferType)(parent, name)
|
||||
else:
|
||||
self.buffer = buffers.basePanel(parent, name)
|
||||
self.buffer = buffers.twitter.basePanel(parent, name)
|
||||
self.invisible = True
|
||||
self.name = name
|
||||
self.type = self.buffer.type
|
||||
@@ -309,9 +304,6 @@ class BaseBuffer(base.Buffer):
|
||||
self.buffer.list.insert_item(True, *tweet)
|
||||
if self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
|
||||
output.speak(" ".join(tweet[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
|
||||
#Improve performance on Windows
|
||||
# if platform.system() == "Windows":
|
||||
# call_threaded(utils.is_audio,item)
|
||||
|
||||
def bind_events(self):
|
||||
log.debug("Binding events...")
|
||||
@@ -322,7 +314,6 @@ class BaseBuffer(base.Buffer):
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.share_item, self.buffer.retweet)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.send_message, self.buffer.dm)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
|
||||
# Replace for the correct way in other platforms.
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
|
||||
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
|
||||
|
||||
@@ -480,7 +471,7 @@ class BaseBuffer(base.Buffer):
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
tweet = self.get_tweet()
|
||||
if platform.system() == "Windows" and self.session.settings["general"]["relative_times"] == True:
|
||||
if self.session.settings["general"]["relative_times"] == True:
|
||||
# fix this:
|
||||
original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at, locale="en")
|
||||
ts = original_date.humanize(locale=languageHandler.getLanguage())
|
||||
@@ -589,4 +580,84 @@ class BaseBuffer(base.Buffer):
|
||||
def open_in_browser(self, *args, **kwargs):
|
||||
url = self.get_item_url()
|
||||
output.speak(_(u"Opening item in web browser..."))
|
||||
webbrowser.open(url)
|
||||
webbrowser.open(url)
|
||||
|
||||
def add_to_favorites(self):
|
||||
id = self.get_tweet().id
|
||||
call_threaded(self.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id)
|
||||
|
||||
def remove_from_favorites(self):
|
||||
id = self.get_tweet().id
|
||||
call_threaded(self.session.api_call, call_name="destroy_favorite", id=id)
|
||||
|
||||
def toggle_favorite(self):
|
||||
id = self.get_tweet().id
|
||||
tweet = self.session.twitter.get_status(id=id, include_ext_alt_text=True, tweet_mode="extended")
|
||||
if tweet.favorited == False:
|
||||
call_threaded(self.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id)
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="destroy_favorite", id=id)
|
||||
|
||||
def view_item(self):
|
||||
if self.type == "dm" or self.name == "direct_messages":
|
||||
non_tweet = self.get_formatted_message()
|
||||
item = self.get_right_tweet()
|
||||
original_date = arrow.get(int(item.created_timestamp))
|
||||
date = original_date.shift(seconds=self.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
|
||||
msg = messages.viewTweet(non_tweet, [], False, date=date)
|
||||
else:
|
||||
tweet, tweetsList = self.get_full_tweet()
|
||||
msg = messages.viewTweet(tweet, tweetsList, utc_offset=self.session.db["utc_offset"], item_url=self.get_item_url())
|
||||
|
||||
def reverse_geocode(self, geocoder):
|
||||
try:
|
||||
tweet = self.get_tweet()
|
||||
if tweet.coordinates != None:
|
||||
x = tweet.coordinates["coordinates"][0]
|
||||
y = tweet.coordinates["coordinates"][1]
|
||||
address = geocoder.reverse_geocode(y, x, language = languageHandler.curLang)
|
||||
return address
|
||||
else:
|
||||
output.speak(_("There are no coordinates in this tweet"))
|
||||
# except GeocoderError:
|
||||
# output.speak(_(u"There are no results for the coordinates in this tweet"))
|
||||
except ValueError:
|
||||
output.speak(_(u"Error decoding coordinates. Try again later."))
|
||||
except KeyError:
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def ocr_image(self):
|
||||
tweet = self.get_tweet()
|
||||
media_list = []
|
||||
if hasattr(tweet, "entities") and tweet.entities.get("media") != None:
|
||||
[media_list.append(i) for i in tweet.entities["media"] if i not in media_list]
|
||||
elif hasattr(tweet, "retweeted_status") and tweet.retweeted_status.get("media") != None:
|
||||
[media_list.append(i) for i in tweet.retweeted_status.entities["media"] if i not in media_list]
|
||||
elif hasattr(tweet, "quoted_status") and tweet.quoted_status.entities.get("media") != None:
|
||||
[media_list.append(i) for i in tweet.quoted_status.entities["media"] if i not in media_list]
|
||||
if len(media_list) > 1:
|
||||
image_list = [_(u"Picture {0}").format(i,) for i in range(0, len(media_list))]
|
||||
dialog = dialogs.urlList.urlList(title=_(u"Select the picture"))
|
||||
if dialog.get_response() == widgetUtils.OK:
|
||||
img = media_list[dialog.get_item()]
|
||||
else:
|
||||
return
|
||||
elif len(media_list) == 1:
|
||||
img = media_list[0]
|
||||
else:
|
||||
output.speak(_(u"Invalid buffer"))
|
||||
return
|
||||
if self.session.settings["mysc"]["ocr_language"] != "":
|
||||
ocr_lang = self.session.settings["mysc"]["ocr_language"]
|
||||
else:
|
||||
ocr_lang = ocr.OCRSpace.short_langs.index(tweet.lang)
|
||||
ocr_lang = ocr.OCRSpace.OcrLangs[ocr_lang]
|
||||
api = ocr.OCRSpace.OCRSpaceAPI()
|
||||
try:
|
||||
text = api.OCR_URL(img["media_url"], lang=ocr_lang)
|
||||
except ocr.OCRSpace.APIError as er:
|
||||
output.speak(_(u"Unable to extract text"))
|
||||
return
|
||||
msg = messages.viewTweet(text["ParsedText"], [], False)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import platform
|
||||
import widgetUtils
|
||||
import arrow
|
||||
import webbrowser
|
||||
@@ -7,12 +6,12 @@ import output
|
||||
import config
|
||||
import languageHandler
|
||||
import logging
|
||||
from controller import messages
|
||||
from sessions.twitter import compose, utils, templates
|
||||
from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from pubsub import pub
|
||||
from wxUI import commonMessageDialogs
|
||||
from controller.twitter import messages
|
||||
from . import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.dmBuffer")
|
||||
@@ -70,7 +69,7 @@ class DirectMessagesBuffer(base.BaseBuffer):
|
||||
self.session.db["sent_direct_messages"] = sent_dms
|
||||
user_ids = [item.message_create["sender_id"] for item in items]
|
||||
self.session.save_users(user_ids)
|
||||
pub.sendMessage("more-sent-dms", data=sent, account=self.session.db["user_name"])
|
||||
pub.sendMessage("twitter.more_sent_dms", data=sent, account=self.session.db["user_name"])
|
||||
selected = self.buffer.list.get_selected()
|
||||
if self.session.settings["general"]["reverse_timelines"] == True:
|
||||
for i in received:
|
||||
@@ -99,7 +98,7 @@ class DirectMessagesBuffer(base.BaseBuffer):
|
||||
|
||||
def onFocus(self, *args, **kwargs):
|
||||
tweet = self.get_tweet()
|
||||
if platform.system() == "Windows" and self.session.settings["general"]["relative_times"] == True:
|
||||
if self.session.settings["general"]["relative_times"] == True:
|
||||
# fix this:
|
||||
original_date = arrow.get(int(tweet.created_timestamp))
|
||||
ts = original_date.humanize(locale=languageHandler.getLanguage())
|
||||
@@ -163,3 +162,10 @@ class SentDirectMessagesBuffer(DirectMessagesBuffer):
|
||||
dm = self.get_right_tweet()
|
||||
t = templates.render_dm(dm, template, self.session, relative_times=self.session.settings["general"]["relative_times"], offset_seconds=self.session.db["utc_offset"])
|
||||
return t
|
||||
|
||||
def view_item(self):
|
||||
non_tweet = self.get_formatted_message()
|
||||
item = self.get_right_tweet()
|
||||
original_date = arrow.get(int(item.created_timestamp))
|
||||
date = original_date.shift(seconds=self.session.db["utc_offset"]).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
|
||||
msg = messages.viewTweet(non_tweet, [], False, date=date)
|
||||
|
@@ -1,13 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
from wxUI import dialogs, commonMessageDialogs
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import dialogs, commonMessageDialogs
|
||||
import widgetUtils
|
||||
import logging
|
||||
from tweepy.cursor import Cursor
|
||||
from wxUI import dialogs, commonMessageDialogs
|
||||
from . import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.listBuffer")
|
||||
|
@@ -1,13 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
from wxUI import commonMessageDialogs, menus
|
||||
from controller import user
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import dialogs, commonMessageDialogs
|
||||
from controller import messages
|
||||
import widgetUtils
|
||||
import webbrowser
|
||||
import output
|
||||
@@ -16,7 +8,9 @@ import logging
|
||||
from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from pubsub import pub
|
||||
from controller.twitter import user, messages
|
||||
from sessions.twitter import compose, templates
|
||||
from wxUI import commonMessageDialogs, menus
|
||||
from . import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.peopleBuffer")
|
||||
@@ -252,4 +246,9 @@ class PeopleBuffer(base.BaseBuffer):
|
||||
def get_item_url(self, *args, **kwargs):
|
||||
tweet = self.get_tweet()
|
||||
url = "https://twitter.com/{screen_name}".format(screen_name=tweet.screen_name)
|
||||
return url
|
||||
return url
|
||||
|
||||
def view_item(self):
|
||||
item_url = self.get_item_url()
|
||||
non_tweet = self.get_formatted_message()
|
||||
msg = messages.viewTweet(non_tweet, [], False, item_url=item_url)
|
@@ -1,15 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
import locale
|
||||
if platform.system() == "Windows":
|
||||
from wxUI import commonMessageDialogs
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import commonMessageDialogs
|
||||
import widgetUtils
|
||||
import logging
|
||||
from tweepy.errors import TweepyException
|
||||
from wxUI import commonMessageDialogs
|
||||
from . import base, people
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.searchBuffer")
|
||||
@@ -67,6 +62,10 @@ class ConversationBuffer(SearchBuffer):
|
||||
last_thread_id = None
|
||||
last_reply_id = None
|
||||
|
||||
def __init__(self, tweet, *args, **kwargs):
|
||||
self.tweet = tweet
|
||||
super(ConversationBuffer, self).__init__(*args, **kwargs)
|
||||
|
||||
def start_stream(self, start=False, mandatory=False, play_sound=True, avoid_autoreading=False):
|
||||
current_time = time.time()
|
||||
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory == True:
|
||||
|
@@ -1,20 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
import wx
|
||||
from wxUI import buffers, commonMessageDialogs, menus
|
||||
from controller import user, messages
|
||||
elif platform.system() == "Linux":
|
||||
from gi.repository import Gtk
|
||||
from gtkUI import buffers, commonMessageDialogs
|
||||
from controller import messages
|
||||
import wx
|
||||
import widgetUtils
|
||||
import output
|
||||
import logging
|
||||
from mysc.thread_utils import call_threaded
|
||||
from tweepy.errors import TweepyException
|
||||
from pubsub import pub
|
||||
from wxUI import buffers, commonMessageDialogs, menus
|
||||
from controller.twitter import user, messages
|
||||
from controller.buffers import base
|
||||
|
||||
log = logging.getLogger("controller.buffers.twitter.trends")
|
||||
@@ -26,7 +20,7 @@ class TrendsBuffer(base.Buffer):
|
||||
self.session = sessionObject
|
||||
self.account = account
|
||||
self.invisible = True
|
||||
self.buffer = buffers.trendsPanel(parent, name)
|
||||
self.buffer = buffers.twitter.trendsPanel(parent, name)
|
||||
self.buffer.account = account
|
||||
self.type = self.buffer.type
|
||||
self.bind_events()
|
||||
|
File diff suppressed because it is too large
Load Diff
1
src/controller/mastodon/__init__.py
Normal file
1
src/controller/mastodon/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
179
src/controller/mastodon/handler.py
Normal file
179
src/controller/mastodon/handler.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import wx
|
||||
import logging
|
||||
from pubsub import pub
|
||||
from wxUI.dialogs.mastodon import dialogs
|
||||
from wxUI.dialogs.mastodon import search as search_dialogs
|
||||
from wxUI.dialogs.mastodon import dialogs
|
||||
from wxUI import commonMessageDialogs
|
||||
from sessions.twitter import utils
|
||||
from . import userActions
|
||||
|
||||
log = logging.getLogger("controller.mastodon.handler")
|
||||
|
||||
class Handler(object):
|
||||
|
||||
def __init__(self):
|
||||
super(Handler, self).__init__()
|
||||
|
||||
def create_buffers(self, session, createAccounts=True, controller=None):
|
||||
session.get_user_info()
|
||||
name = session.get_name()
|
||||
if createAccounts == True:
|
||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
||||
root_position =controller.view.search(name, name)
|
||||
for i in session.settings['general']['buffer_order']:
|
||||
if i == 'home':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Home"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="timeline_home", name="home_timeline", sessionObject=session, account=name, sound="tweet_received.ogg"))
|
||||
elif i == 'local':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Local"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="timeline_local", name="local_timeline", sessionObject=session, account=name, sound="tweet_received.ogg"))
|
||||
elif i == 'federated':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Federated"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="timeline_public", name="federated_timeline", sessionObject=session, account=name, sound="tweet_received.ogg"))
|
||||
elif i == 'mentions':
|
||||
pub.sendMessage("createBuffer", buffer_type="MentionsBuffer", session_type=session.type, buffer_title=_("Mentions"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="notifications", name="mentions", sessionObject=session, account=name, sound="mention_received.ogg"))
|
||||
elif i == 'direct_messages':
|
||||
pub.sendMessage("createBuffer", buffer_type="ConversationListBuffer", session_type=session.type, buffer_title=_("Direct messages"), parent_tab=root_position, start=False, kwargs=dict(compose_func="compose_conversation", parent=controller.view.nb, function="conversations", name="direct_messages", sessionObject=session, account=name, sound="dm_received.ogg"))
|
||||
elif i == 'sent':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Sent"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="sent", sessionObject=session, account=name, sound="tweet_received.ogg", id=session.db["user_id"]))
|
||||
elif i == 'favorites':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Favorites"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="favourites", name="favorites", sessionObject=session, account=name, sound="favourite.ogg"))
|
||||
elif i == 'bookmarks':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Bookmarks"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="bookmarks", name="bookmarks", sessionObject=session, account=name, sound="favourite.ogg"))
|
||||
elif i == 'followers':
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Followers"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="followers", sessionObject=session, account=name, sound="update_followers.ogg", id=session.db["user_id"]))
|
||||
elif i == 'following':
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Following"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="following", sessionObject=session, account=name, sound="update_followers.ogg", id=session.db["user_id"]))
|
||||
elif i == 'muted':
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Muted users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="mutes", name="muted", sessionObject=session, account=name))
|
||||
elif i == 'blocked':
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Blocked users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="blocks", name="blocked", sessionObject=session, account=name))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="timelines", account=name))
|
||||
timelines_position =controller.view.search("timelines", name)
|
||||
for i in session.settings["other_buffers"]["timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=i, parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline".format(i), sessionObject=session, account=name, sound="tweet_timeline.ogg", id=i))
|
||||
for i in session.settings["other_buffers"]["followers_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Followers for {}").format(i), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (i,), sessionObject=session, account=name, sound="new_event.ogg", id=i))
|
||||
for i in session.settings["other_buffers"]["following_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Following for {}").format(i), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-following" % (i,), sessionObject=session, account=name, sound="new_event.ogg", id=i))
|
||||
# pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Lists"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="lists", name))
|
||||
# lists_position =controller.view.search("lists", session.db["user_name"])
|
||||
# for i in session.settings["other_buffers"]["lists"]:
|
||||
# pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=session.type, buffer_title=_(u"List for {}").format(i), parent_tab=lists_position, start=False, kwargs=dict(parent=controller.view.nb, function="list_timeline", name="%s-list" % (i,), sessionObject=session, name, bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Searches"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="searches", account=name))
|
||||
# searches_position =controller.view.search("searches", session.db["user_name"])
|
||||
# for i in session.settings["other_buffers"]["tweet_searches"]:
|
||||
# pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_(u"Search for {}").format(i), parent_tab=searches_position, start=False, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (i,), sessionObject=session, name, bufferType="searchPanel", sound="search_updated.ogg", q=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
# for i in session.settings["other_buffers"]["trending_topic_buffers"]:
|
||||
# pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (i), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (i,), sessionObject=session, name, trendsFor=i, sound="trends_updated.ogg"))
|
||||
|
||||
def start_buffer(self, controller, buffer):
|
||||
if hasattr(buffer, "finished_timeline") and buffer.finished_timeline == False:
|
||||
change_title = True
|
||||
else:
|
||||
change_title = False
|
||||
try:
|
||||
buffer.start_stream(play_sound=False)
|
||||
except Exception as err:
|
||||
log.exception("Error %s starting buffer %s on account %s, with args %r and kwargs %r." % (str(err), buffer.name, buffer.account, buffer.args, buffer.kwargs))
|
||||
if change_title:
|
||||
pub.sendMessage("buffer-title-changed", buffer=buffer)
|
||||
|
||||
def open_conversation(self, controller, buffer):
|
||||
post = buffer.get_item()
|
||||
if post.reblog != None:
|
||||
post = post.reblog
|
||||
conversations_position =controller.view.search("direct_messages", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="ConversationBuffer", session_type=buffer.session.type, buffer_title=_("Conversation with {0}").format(post.account.acct), parent_tab=conversations_position, start=True, kwargs=dict(parent=controller.view.nb, function="status_context", name="%s-conversation" % (post.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="search_updated.ogg", post=post, id=post.id))
|
||||
|
||||
def follow(self, buffer):
|
||||
if not hasattr(buffer, "get_item"):
|
||||
return
|
||||
item = buffer.get_item()
|
||||
if buffer.type == "user":
|
||||
users = [item.acct]
|
||||
elif buffer.type == "baseBuffer":
|
||||
if item.reblog != None:
|
||||
users = [user.acct for user in item.reblog.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.reblog.account.acct not in users and item.account.id != buffer.session.db["user_id"]:
|
||||
users.insert(0, item.reblog.account.acct)
|
||||
else:
|
||||
users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.account.acct not in users:
|
||||
users.insert(0, item.account.acct)
|
||||
u = userActions.userActions(buffer.session, users)
|
||||
|
||||
def search(self, controller, session, value):
|
||||
log.debug("Creating a new search...")
|
||||
dlg = search_dialogs.searchDialog(value)
|
||||
if dlg.ShowModal() == wx.ID_OK and dlg.term.GetValue() != "":
|
||||
term = dlg.term.GetValue()
|
||||
searches_position =controller.view.search("searches", session.get_name())
|
||||
if dlg.posts.GetValue() == True:
|
||||
if term not in session.settings["other_buffers"]["post_searches"]:
|
||||
session.settings["other_buffers"]["post_searches"].append(term)
|
||||
session.settings.write()
|
||||
# pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (term,), sessionObject=session, account=session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", q=term, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
else:
|
||||
log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,))
|
||||
return
|
||||
elif dlg.users.GetValue() == True:
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_search", name="%s-searchUser" % (term,), sessionObject=session, account=session.get_name(), sound="search_updated.ogg", q=term))
|
||||
dlg.Destroy()
|
||||
|
||||
# ToDo: explore how to play sound & save config differently.
|
||||
# currently, TWBlue will play the sound and save the config for the timeline even if the buffer did not load or something else.
|
||||
def open_timeline(self, controller, buffer):
|
||||
if not hasattr(buffer, "get_item"):
|
||||
return
|
||||
item = buffer.get_item()
|
||||
if buffer.type == "user":
|
||||
users = [item.acct]
|
||||
elif buffer.type == "baseBuffer":
|
||||
if item.reblog != None:
|
||||
users = [user.acct for user in item.reblog.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.reblog.account.acct not in users and item.account.id != buffer.session.db["user_id"]:
|
||||
users.insert(0, item.reblog.account.acct)
|
||||
else:
|
||||
users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.account.acct not in users and item.account.id != buffer.session.db["user_id"]:
|
||||
users.insert(0, item.account.acct)
|
||||
u = userActions.UserTimeline(buffer.session, users)
|
||||
if u.dialog.ShowModal() == wx.ID_OK:
|
||||
action = u.process_action()
|
||||
if action == None:
|
||||
return
|
||||
user = u.user
|
||||
if action == "posts":
|
||||
if user.statuses_count == 0:
|
||||
dialogs.no_posts()
|
||||
return
|
||||
if user.id in buffer.session.settings["other_buffers"]["timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id))
|
||||
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif action == "followers":
|
||||
if user.followers_count == 0:
|
||||
dialogs.no_followers()
|
||||
return
|
||||
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
|
||||
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif action == "following":
|
||||
if user.following_count == 0:
|
||||
dialogs.no_following()
|
||||
return
|
||||
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
|
||||
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
buffer.session.settings.write()
|
227
src/controller/mastodon/messages.py
Normal file
227
src/controller/mastodon/messages.py
Normal file
@@ -0,0 +1,227 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import wx
|
||||
import widgetUtils
|
||||
import config
|
||||
import output
|
||||
from controller.twitter import messages
|
||||
from sessions.mastodon import templates
|
||||
from wxUI.dialogs.mastodon import postDialogs
|
||||
|
||||
class post(messages.basicTweet):
|
||||
def __init__(self, session, title, caption, text="", *args, **kwargs):
|
||||
# take max character limit from session as this might be different for some instances.
|
||||
self.max = session.char_limit
|
||||
self.title = title
|
||||
self.session = session
|
||||
self.message = postDialogs.Post(caption=caption, text=text, *args, **kwargs)
|
||||
self.message.SetTitle(title)
|
||||
self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
|
||||
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
|
||||
widgetUtils.connect_event(self.message.text, widgetUtils.ENTERED_TEXT, self.text_processor)
|
||||
widgetUtils.connect_event(self.message.translate, widgetUtils.BUTTON_PRESSED, self.translate)
|
||||
widgetUtils.connect_event(self.message.add, widgetUtils.BUTTON_PRESSED, self.on_attach)
|
||||
widgetUtils.connect_event(self.message.remove_attachment, widgetUtils.BUTTON_PRESSED, self.remove_attachment)
|
||||
# ToDo: Add autocomplete feature to mastodon and uncomment this.
|
||||
# widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
|
||||
widgetUtils.connect_event(self.message.add_post, widgetUtils.BUTTON_PRESSED, self.add_post)
|
||||
widgetUtils.connect_event(self.message.remove_post, widgetUtils.BUTTON_PRESSED, self.remove_post)
|
||||
self.attachments = []
|
||||
self.thread = []
|
||||
self.text_processor()
|
||||
|
||||
def add_post(self, event, update_gui=True, *args, **kwargs):
|
||||
text = self.message.text.GetValue()
|
||||
attachments = self.attachments[::]
|
||||
postdata = dict(text=text, attachments=attachments, sensitive=self.message.sensitive.GetValue(), spoiler_text=None)
|
||||
if postdata.get("sensitive") == True:
|
||||
postdata.update(spoiler_text=self.message.spoiler.GetValue())
|
||||
self.thread.append(postdata)
|
||||
self.attachments = []
|
||||
if update_gui:
|
||||
self.message.reset_controls()
|
||||
self.message.add_item(item=[text, len(attachments)], list_type="post")
|
||||
self.message.text.SetFocus()
|
||||
self.text_processor()
|
||||
|
||||
def get_post_data(self):
|
||||
self.add_post(event=None, update_gui=False)
|
||||
return self.thread
|
||||
|
||||
def text_processor(self, *args, **kwargs):
|
||||
super(post, self).text_processor(*args, **kwargs)
|
||||
if len(self.thread) > 0:
|
||||
if hasattr(self.message, "posts"):
|
||||
self.message.posts.Enable(True)
|
||||
self.message.remove_post.Enable(True)
|
||||
else:
|
||||
self.message.posts.Enable(False)
|
||||
self.message.remove_post.Enable(False)
|
||||
if len(self.attachments) > 0:
|
||||
self.message.attachments.Enable(True)
|
||||
self.message.remove_attachment.Enable(True)
|
||||
else:
|
||||
self.message.attachments.Enable(False)
|
||||
self.message.remove_attachment.Enable(False)
|
||||
if len(self.message.text.GetValue()) > 0 or len(self.attachments) > 0:
|
||||
self.message.add_post.Enable(True)
|
||||
else:
|
||||
self.message.add_post.Enable(False)
|
||||
|
||||
def remove_post(self, *args, **kwargs):
|
||||
post = self.message.posts.GetFocusedItem()
|
||||
if post > -1 and len(self.thread) > post:
|
||||
self.thread.pop(post)
|
||||
self.message.remove_item(list_type="post")
|
||||
self.text_processor()
|
||||
self.message.text.SetFocus()
|
||||
|
||||
def can_attach(self):
|
||||
if len(self.attachments) == 0:
|
||||
return True
|
||||
elif len(self.attachments) == 1 and (self.attachments[0]["type"] == "poll" or self.attachments[0]["type"] == "video" or self.attachments[0]["type"] == "audio"):
|
||||
return False
|
||||
elif len(self.attachments) < 4:
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_attach(self, *args, **kwargs):
|
||||
can_attach = self.can_attach()
|
||||
menu = self.message.attach_menu(can_attach)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_image, self.message.add_image)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_video, self.message.add_video)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_audio, self.message.add_audio)
|
||||
self.message.Bind(wx.EVT_MENU, self.on_attach_poll, self.message.add_poll)
|
||||
self.message.PopupMenu(menu, self.message.add.GetPosition())
|
||||
|
||||
def on_attach_image(self, *args, **kwargs):
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
image, description = self.message.get_image()
|
||||
if image != None:
|
||||
if image.endswith("gif"):
|
||||
image_type = "gif"
|
||||
else:
|
||||
image_type = "photo"
|
||||
imageInfo = {"type": image_type, "file": image, "description": description}
|
||||
if len(self.attachments) > 0 and image_type == "gif":
|
||||
return self.message.unable_to_attach_file()
|
||||
self.attachments.append(imageInfo)
|
||||
self.message.add_item(item=[os.path.basename(imageInfo["file"]), imageInfo["type"], imageInfo["description"]])
|
||||
self.text_processor()
|
||||
|
||||
def on_attach_video(self, *args, **kwargs):
|
||||
if len(self.attachments) >= 4:
|
||||
return self.message.unable_to_attach_file()
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
video = self.message.get_video()
|
||||
if video != None:
|
||||
videoInfo = {"type": "video", "file": video, "description": ""}
|
||||
self.attachments.append(videoInfo)
|
||||
self.message.add_item(item=[os.path.basename(videoInfo["file"]), videoInfo["type"], videoInfo["description"]])
|
||||
self.text_processor()
|
||||
|
||||
def on_attach_audio(self, *args, **kwargs):
|
||||
if len(self.attachments) >= 4:
|
||||
return self.message.unable_to_attach_file()
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
audio = self.message.get_audio()
|
||||
if audio != None:
|
||||
audioInfo = {"type": "audio", "file": audio, "description": ""}
|
||||
self.attachments.append(audioInfo)
|
||||
self.message.add_item(item=[os.path.basename(audioInfo["file"]), audioInfo["type"], audioInfo["description"]])
|
||||
self.text_processor()
|
||||
|
||||
def on_attach_poll(self, *args, **kwargs):
|
||||
if len(self.attachments) > 0:
|
||||
return self.message.unable_to_attach_poll()
|
||||
can_attach = self.can_attach()
|
||||
big_media_present = False
|
||||
for a in self.attachments:
|
||||
if a["type"] == "video" or a["type"] == "audio" or a["type"] == "poll":
|
||||
big_media_present = True
|
||||
break
|
||||
if can_attach == False or big_media_present == True:
|
||||
return self.message.unable_to_attach_file()
|
||||
dlg = postDialogs.poll()
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
day = 86400
|
||||
periods = [300, 1800, 3600, 21600, day, day*2, day*3, day*4, day*5, day*6, day*7]
|
||||
period = periods[dlg.period.GetSelection()]
|
||||
poll_options = dlg.get_options()
|
||||
multiple = dlg.multiple.GetValue()
|
||||
hide_totals = dlg.hide_votes.GetValue()
|
||||
data = dict(type="poll", file="", description=_("Poll with {} options").format(len(poll_options)), options=poll_options, expires_in=period, multiple=multiple, hide_totals=hide_totals)
|
||||
self.attachments.append(data)
|
||||
self.message.add_item(item=[data["file"], data["type"], data["description"]])
|
||||
self.text_processor()
|
||||
dlg.Destroy()
|
||||
|
||||
def get_data(self):
|
||||
self.add_post(event=None, update_gui=False)
|
||||
return self.thread
|
||||
|
||||
def get_visibility(self):
|
||||
visibility_settings = ["public", "unlisted", "private", "direct"]
|
||||
return visibility_settings[self.message.visibility.GetSelection()]
|
||||
|
||||
class viewPost(post):
|
||||
def __init__(self, post, offset_hours=0, date="", item_url=""):
|
||||
if post.reblog != None:
|
||||
post = post.reblog
|
||||
author = post.account.display_name if post.account.display_name != "" else post.account.username
|
||||
title = _(u"Post from {}").format(author)
|
||||
image_description = templates.process_image_descriptions(post.media_attachments)
|
||||
text = templates.process_text(post, safe=False)
|
||||
date = templates.process_date(post.created_at, relative_times=False, offset_hours=offset_hours)
|
||||
privacy_settings = dict(public=_("Public"), unlisted=_("Not listed"), private=_("followers only"), direct=_("Direct"))
|
||||
privacy = privacy_settings.get(post.visibility)
|
||||
boost_count = str(post.reblogs_count)
|
||||
favs_count = str(post.favourites_count)
|
||||
# Gets the client from where this post was made.
|
||||
source_obj = post.get("application")
|
||||
if source_obj == None:
|
||||
source = _("Remote instance")
|
||||
else:
|
||||
source = source_obj.get("name")
|
||||
self.message = postDialogs.viewPost(text=text, boosts_count=boost_count, favs_count=favs_count, source=source, date=date, privacy=privacy)
|
||||
self.message.SetTitle(title)
|
||||
if image_description != "":
|
||||
self.message.image_description.Enable(True)
|
||||
self.message.image_description.ChangeValue(image_description)
|
||||
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
|
||||
if item_url != "":
|
||||
self.message.enable_button("share")
|
||||
widgetUtils.connect_event(self.message.share, widgetUtils.BUTTON_PRESSED, self.share)
|
||||
self.item_url = item_url
|
||||
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
|
||||
self.message.ShowModal()
|
||||
|
||||
# We won't need text_processor in this dialog, so let's avoid it.
|
||||
def text_processor(self):
|
||||
pass
|
||||
|
||||
def share(self, *args, **kwargs):
|
||||
if hasattr(self, "item_url"):
|
||||
output.copy(self.item_url)
|
||||
output.speak(_("Link copied to clipboard."))
|
101
src/controller/mastodon/userActions.py
Normal file
101
src/controller/mastodon/userActions.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import widgetUtils
|
||||
import output
|
||||
from wxUI.dialogs.mastodon import userActions as userActionsDialog
|
||||
from wxUI.dialogs.mastodon import userTimeline as userTimelineDialog
|
||||
from pubsub import pub
|
||||
from mastodon import MastodonError, MastodonNotFoundError
|
||||
from extra.autocompletionUsers import completion
|
||||
|
||||
log = logging.getLogger("controller.mastodon.userActions")
|
||||
|
||||
class BasicUserSelector(object):
|
||||
def __init__(self, session, users=[]):
|
||||
super(BasicUserSelector, self).__init__()
|
||||
self.session = session
|
||||
self.create_dialog(users=users)
|
||||
|
||||
def create_dialog(self, users):
|
||||
pass
|
||||
|
||||
def autocomplete_users(self, *args, **kwargs):
|
||||
c = completion.autocompletionUsers(self.dialog, self.session.session_id)
|
||||
c.show_menu("dm")
|
||||
|
||||
def search_user(self, user):
|
||||
try:
|
||||
user = self.session.api.account_lookup(user)
|
||||
return user
|
||||
except MastodonError:
|
||||
log.exception("Error searching for user %s.".format(user))
|
||||
|
||||
class userActions(BasicUserSelector):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(userActions, self).__init__(*args, **kwargs)
|
||||
if self.dialog.get_response() == widgetUtils.OK:
|
||||
self.process_action()
|
||||
|
||||
def create_dialog(self, users):
|
||||
self.dialog = userActionsDialog.UserActionsDialog(users)
|
||||
widgetUtils.connect_event(self.dialog.autocompletion, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
|
||||
|
||||
def process_action(self):
|
||||
action = self.dialog.get_action()
|
||||
user = self.dialog.get_user()
|
||||
user = self.search_user(user)
|
||||
if user == None:
|
||||
return
|
||||
getattr(self, action)(user)
|
||||
|
||||
def follow(self, user):
|
||||
try:
|
||||
self.session.api.account_follow(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unfollow(self, user):
|
||||
try:
|
||||
result = self.session.api.account_unfollow(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def mute(self, user):
|
||||
try:
|
||||
id = self.session.api.account_mute(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unmute(self, user):
|
||||
try:
|
||||
id = self.session.api.account_unmute(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def block(self, user):
|
||||
try:
|
||||
id = self.session.api.account_block(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unblock(self, user):
|
||||
try:
|
||||
id = self.session.api.account_unblock(user.id)
|
||||
except MastodonError as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
class UserTimeline(BasicUserSelector):
|
||||
|
||||
def create_dialog(self, users):
|
||||
self.dialog = userTimelineDialog.UserTimeline(users)
|
||||
widgetUtils.connect_event(self.dialog.autocompletion, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
|
||||
|
||||
def process_action(self):
|
||||
action = self.dialog.get_action()
|
||||
user = self.dialog.get_user()
|
||||
user = self.search_user(user)
|
||||
if user == None:
|
||||
return
|
||||
self.user = user
|
||||
return action
|
@@ -1,25 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import webbrowser
|
||||
import threading
|
||||
import logging
|
||||
import sound_lib
|
||||
import paths
|
||||
import widgetUtils
|
||||
import config
|
||||
import languageHandler
|
||||
import output
|
||||
import application
|
||||
import config_utils
|
||||
import keys
|
||||
from collections import OrderedDict
|
||||
from pubsub import pub
|
||||
from mysc import autostart as autostart_windows
|
||||
from wxUI.dialogs import configuration
|
||||
from wxUI import commonMessageDialogs
|
||||
from extra.autocompletionUsers import scan, manage
|
||||
from extra.ocr import OCRSpace
|
||||
from .editTemplateController import EditTemplate
|
||||
|
||||
log = logging.getLogger("Settings")
|
||||
|
||||
@@ -132,234 +121,3 @@ class globalSettingsController(object):
|
||||
config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user")
|
||||
config.app["proxy"]["password"] = self.dialog.get_value("proxy", "password")
|
||||
config.app.write()
|
||||
|
||||
class accountSettingsController(globalSettingsController):
|
||||
def __init__(self, buffer, window):
|
||||
self.user = buffer.session.db["user_name"]
|
||||
self.buffer = buffer
|
||||
self.window = window
|
||||
self.config = buffer.session.settings
|
||||
super(accountSettingsController, self).__init__()
|
||||
|
||||
def create_config(self):
|
||||
self.dialog.create_general_account()
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
|
||||
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
|
||||
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
|
||||
self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"])
|
||||
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_tweets_per_call"])
|
||||
self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"])
|
||||
rt = self.config["general"]["retweet_mode"]
|
||||
if rt == "ask":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Ask"))
|
||||
elif rt == "direct":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet without comments"))
|
||||
else:
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments"))
|
||||
self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"]))
|
||||
self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"])
|
||||
self.dialog.create_reporting()
|
||||
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"])
|
||||
self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
|
||||
tweet_template = self.config["templates"]["tweet"]
|
||||
dm_template = self.config["templates"]["dm"]
|
||||
sent_dm_template = self.config["templates"]["dm_sent"]
|
||||
person_template = self.config["templates"]["person"]
|
||||
self.dialog.create_templates(tweet_template=tweet_template, dm_template=dm_template, sent_dm_template=sent_dm_template, person_template=person_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.tweet, widgetUtils.BUTTON_PRESSED, self.edit_tweet_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.dm, widgetUtils.BUTTON_PRESSED, self.edit_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.sent_dm, widgetUtils.BUTTON_PRESSED, self.edit_sent_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.person, widgetUtils.BUTTON_PRESSED, self.edit_person_template)
|
||||
self.dialog.create_other_buffers()
|
||||
buffer_values = self.get_buffers_list()
|
||||
self.dialog.buffers.insert_buffers(buffer_values)
|
||||
self.dialog.buffers.connect_hook_func(self.toggle_buffer_active)
|
||||
widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state)
|
||||
widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up)
|
||||
widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down)
|
||||
|
||||
self.dialog.create_ignored_clients(self.config["twitter"]["ignored_clients"])
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.add, widgetUtils.BUTTON_PRESSED, self.add_ignored_client)
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.remove, widgetUtils.BUTTON_PRESSED, self.remove_ignored_client)
|
||||
self.input_devices = sound_lib.input.Input.get_device_names()
|
||||
self.output_devices = sound_lib.output.Output.get_device_names()
|
||||
self.soundpacks = []
|
||||
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ]
|
||||
self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks)
|
||||
self.dialog.set_value("sound", "volumeCtrl", self.config["sound"]["volume"]*100)
|
||||
self.dialog.set_value("sound", "input", self.config["sound"]["input_device"])
|
||||
self.dialog.set_value("sound", "output", self.config["sound"]["output_device"])
|
||||
self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"])
|
||||
self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"])
|
||||
self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"])
|
||||
self.dialog.set_value("sound", "indicate_geo", self.config["sound"]["indicate_geo"])
|
||||
self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"])
|
||||
self.dialog.create_extras(OCRSpace.translatable_langs)
|
||||
self.dialog.set_value("extras", "sndup_apiKey", self.config["sound"]["sndup_api_key"])
|
||||
language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"])
|
||||
self.dialog.extras.ocr_lang.SetSelection(language_index)
|
||||
self.dialog.realize()
|
||||
self.dialog.set_title(_(u"Account settings for %s") % (self.user,))
|
||||
self.response = self.dialog.get_response()
|
||||
|
||||
def edit_tweet_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["tweet"]
|
||||
control = EditTemplate(template=template, type="tweet")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["tweet"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.tweet.SetLabel(_("Edit template for tweets. Current template: {}").format(result))
|
||||
|
||||
def edit_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.dm.SetLabel(_("Edit template for direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_sent_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm_sent"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm_sent"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.sent_dm.SetLabel(_("Edit template for sent direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_person_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["person"]
|
||||
control = EditTemplate(template=template, type="person")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["person"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.person.SetLabel(_("Edit template for persons. Current template: {}").format(result))
|
||||
|
||||
def save_configuration(self):
|
||||
if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in relative times.")
|
||||
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
|
||||
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
|
||||
self.config["general"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
|
||||
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
|
||||
if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
|
||||
self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in database strategy management.")
|
||||
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
|
||||
if self.dialog.get_value("general", "persist_size") == '':
|
||||
self.config["general"]["persist_size"] =-1
|
||||
else:
|
||||
try:
|
||||
self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size"))
|
||||
except ValueError:
|
||||
output.speak("Invalid cache size, setting to default.",True)
|
||||
self.config["general"]["persist_size"] =1764
|
||||
|
||||
if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in timeline order.")
|
||||
self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines")
|
||||
rt = self.dialog.get_value("general", "retweet_mode")
|
||||
if rt == _(u"Ask"):
|
||||
self.config["general"]["retweet_mode"] = "ask"
|
||||
elif rt == _(u"Retweet without comments"):
|
||||
self.config["general"]["retweet_mode"] = "direct"
|
||||
else:
|
||||
self.config["general"]["retweet_mode"] = "comment"
|
||||
buffers_list = self.dialog.buffers.get_list()
|
||||
if buffers_list != self.config["general"]["buffer_order"]:
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in buffer ordering.")
|
||||
self.config["general"]["buffer_order"] = buffers_list
|
||||
self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting")
|
||||
self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting")
|
||||
self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()]
|
||||
if self.config["sound"]["input_device"] != self.dialog.sound.get("input"):
|
||||
self.config["sound"]["input_device"] = self.dialog.sound.get("input")
|
||||
try:
|
||||
self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"]))
|
||||
except:
|
||||
self.config["sound"]["input_device"] = "default"
|
||||
if self.config["sound"]["output_device"] != self.dialog.sound.get("output"):
|
||||
self.config["sound"]["output_device"] = self.dialog.sound.get("output")
|
||||
try:
|
||||
self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"]))
|
||||
except:
|
||||
self.config["sound"]["output_device"] = "default"
|
||||
self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0
|
||||
self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute")
|
||||
self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack")
|
||||
self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio")
|
||||
self.config["sound"]["indicate_geo"] = self.dialog.get_value("sound", "indicate_geo")
|
||||
self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img")
|
||||
self.config["sound"]["sndup_api_key"] = self.dialog.get_value("extras", "sndup_apiKey")
|
||||
self.buffer.session.sound.config = self.config["sound"]
|
||||
self.buffer.session.sound.check_soundpack()
|
||||
self.config.write()
|
||||
|
||||
def toggle_state(self,*args,**kwargs):
|
||||
return self.dialog.buffers.change_selected_item()
|
||||
|
||||
def on_autocompletion_scan(self, *args, **kwargs):
|
||||
configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window)
|
||||
to_scan = configuration.show_dialog()
|
||||
if to_scan == True:
|
||||
configuration.prepare_progress_dialog()
|
||||
t = threading.Thread(target=configuration.scan)
|
||||
t.start()
|
||||
|
||||
|
||||
|
||||
def on_autocompletion_manage(self, *args, **kwargs):
|
||||
configuration = manage.autocompletionManage(self.buffer.session)
|
||||
configuration.show_settings()
|
||||
|
||||
def add_ignored_client(self, *args, **kwargs):
|
||||
client = commonMessageDialogs.get_ignored_client()
|
||||
if client == None: return
|
||||
if client not in self.config["twitter"]["ignored_clients"]:
|
||||
self.config["twitter"]["ignored_clients"].append(client)
|
||||
self.dialog.ignored_clients.append(client)
|
||||
|
||||
def remove_ignored_client(self, *args, **kwargs):
|
||||
if self.dialog.ignored_clients.get_clients() == 0: return
|
||||
id = self.dialog.ignored_clients.get_client_id()
|
||||
self.config["twitter"]["ignored_clients"].pop(id)
|
||||
self.dialog.ignored_clients.remove_(id)
|
||||
|
||||
def get_buffers_list(self):
|
||||
all_buffers=OrderedDict()
|
||||
all_buffers['home']=_(u"Home")
|
||||
all_buffers['mentions']=_(u"Mentions")
|
||||
all_buffers['dm']=_(u"Direct Messages")
|
||||
all_buffers['sent_dm']=_(u"Sent direct messages")
|
||||
all_buffers['sent_tweets']=_(u"Sent tweets")
|
||||
all_buffers['favorites']=_(u"Likes")
|
||||
all_buffers['followers']=_(u"Followers")
|
||||
all_buffers['friends']=_(u"Friends")
|
||||
all_buffers['blocks']=_(u"Blocked users")
|
||||
all_buffers['muted']=_(u"Muted users")
|
||||
list_buffers = []
|
||||
hidden_buffers=[]
|
||||
all_buffers_keys = list(all_buffers.keys())
|
||||
# Check buffers shown first.
|
||||
for i in self.config["general"]["buffer_order"]:
|
||||
if i in all_buffers_keys:
|
||||
list_buffers.append((i, all_buffers[i], True))
|
||||
# This second pass will retrieve all hidden buffers.
|
||||
for i in all_buffers_keys:
|
||||
if i not in self.config["general"]["buffer_order"]:
|
||||
hidden_buffers.append((i, all_buffers[i], False))
|
||||
list_buffers.extend(hidden_buffers)
|
||||
return list_buffers
|
||||
|
||||
def toggle_buffer_active(self, ev):
|
||||
change = self.dialog.buffers.get_event(ev)
|
||||
if change == True:
|
||||
self.dialog.buffers.change_selected_item()
|
||||
|
1
src/controller/twitter/__init__.py
Normal file
1
src/controller/twitter/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
340
src/controller/twitter/handler.py
Normal file
340
src/controller/twitter/handler.py
Normal file
@@ -0,0 +1,340 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import widgetUtils
|
||||
import output
|
||||
from pubsub import pub
|
||||
from tweepy.errors import TweepyException, Forbidden
|
||||
from mysc import restart
|
||||
from sessions.twitter import utils, compose
|
||||
from controller import userSelector
|
||||
from wxUI import dialogs, commonMessageDialogs
|
||||
from . import filters, lists, settings, userActions, trendingTopics, user
|
||||
|
||||
log = logging.getLogger("controller.twitter.handler")
|
||||
|
||||
class Handler(object):
|
||||
|
||||
def __init__(self):
|
||||
super(Handler, self).__init__()
|
||||
|
||||
def create_buffers(self, session, createAccounts=True, controller=None):
|
||||
session.get_user_info()
|
||||
name = session.get_name()
|
||||
if createAccounts == True:
|
||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
||||
root_position =controller.view.search(name, name)
|
||||
for i in session.settings['general']['buffer_order']:
|
||||
if i == 'home':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Home"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="home_timeline", name="home_timeline", sessionObject=session, account=session.get_name(), sound="tweet_received.ogg", include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'mentions':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Mentions"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="mentions_timeline", name="mentions", sessionObject=session, account=session.get_name(), sound="mention_received.ogg", include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'dm':
|
||||
pub.sendMessage("createBuffer", buffer_type="DirectMessagesBuffer", session_type=session.type, buffer_title=_("Direct messages"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_direct_messages", name="direct_messages", sessionObject=session, account=session.get_name(), bufferType="dmPanel", compose_func="compose_direct_message", sound="dm_received.ogg"))
|
||||
elif i == 'sent_dm':
|
||||
pub.sendMessage("createBuffer", buffer_type="SentDirectMessagesBuffer", session_type=session.type, buffer_title=_("Sent direct messages"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function=None, name="sent_direct_messages", sessionObject=session, account=session.get_name(), bufferType="dmPanel", compose_func="compose_direct_message"))
|
||||
elif i == 'sent_tweets':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Sent tweets"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="user_timeline", name="sent_tweets", sessionObject=session, account=session.get_name(), screen_name=session.db["user_name"], include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'favorites':
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Likes"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_favorites", name="favourites", sessionObject=session, account=session.get_name(), sound="favourite.ogg", include_ext_alt_text=True, tweet_mode="extended"))
|
||||
elif i == 'followers':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Followers"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_followers", name="followers", sessionObject=session, account=session.get_name(), sound="update_followers.ogg", screen_name=session.db["user_name"]))
|
||||
elif i == 'friends':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Following"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_friends", name="friends", sessionObject=session, account=session.get_name(), screen_name=session.db["user_name"]))
|
||||
elif i == 'blocks':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Blocked users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_blocks", name="blocked", sessionObject=session, account=session.get_name()))
|
||||
elif i == 'muted':
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Muted users"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_mutes", name="muted", sessionObject=session, account=session.get_name()))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="timelines", account=name))
|
||||
timelines_position =controller.view.search("timelines", name)
|
||||
for i in session.settings["other_buffers"]["timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_(u"Timeline for {}").format(i,), parent_tab=timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="user_timeline", name="%s-timeline" % (i,), sessionObject=session, account=session.get_name(), sound="tweet_timeline.ogg", bufferType=None, user_id=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Likes timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="favs_timelines", account=name))
|
||||
favs_timelines_position =controller.view.search("favs_timelines", name)
|
||||
for i in session.settings["other_buffers"]["favourites_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=session.type, buffer_title=_("Likes for {}").format(i,), parent_tab=favs_timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_favorites", name="%s-favorite" % (i,), sessionObject=session, account=name, bufferType=None, sound="favourites_timeline_updated.ogg", user_id=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Followers timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="followers_timelines", account=session.get_name()))
|
||||
followers_timelines_position =controller.view.search("followers_timelines", name)
|
||||
for i in session.settings["other_buffers"]["followers_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_("Followers for {}").format(i,), parent_tab=followers_timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_followers", name="%s-followers" % (i,), sessionObject=session, account=session.get_name(), sound="new_event.ogg", user_id=i))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Following timelines"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="friends_timelines", account=name))
|
||||
friends_timelines_position =controller.view.search("friends_timelines", name)
|
||||
for i in session.settings["other_buffers"]["friends_timelines"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=session.type, buffer_title=_(u"Friends for {}").format(i,), parent_tab=friends_timelines_position, start=False, kwargs=dict(parent=controller.view.nb, function="get_friends", name="%s-friends" % (i,), sessionObject=session, account=session.get_name(), sound="new_event.ogg", user_id=i))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Lists"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="lists", account=name))
|
||||
lists_position =controller.view.search("lists", name)
|
||||
for i in session.settings["other_buffers"]["lists"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=session.type, buffer_title=_(u"List for {}").format(i), parent_tab=lists_position, start=False, kwargs=dict(parent=controller.view.nb, function="list_timeline", name="%s-list" % (i,), sessionObject=session, account=session.get_name(), bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), include_ext_alt_text=True, tweet_mode="extended"))
|
||||
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Searches"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="searches", account=name))
|
||||
searches_position =controller.view.search("searches", name)
|
||||
for i in session.settings["other_buffers"]["tweet_searches"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_(u"Search for {}").format(i), parent_tab=searches_position, start=False, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (i,), sessionObject=session, account=session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", q=i, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
for i in session.settings["other_buffers"]["trending_topic_buffers"]:
|
||||
pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (i), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (i,), sessionObject=session, account=session.get_name(), trendsFor=i, sound="trends_updated.ogg"))
|
||||
|
||||
def filter(self, buffer):
|
||||
# Let's prevent filtering of some buffers (people buffers, direct messages, events and sent items).
|
||||
if (buffer.name == "direct_messages" or buffer.name == "sent_tweets") or buffer.type == "people":
|
||||
output.speak(_("Filters cannot be applied on this buffer"))
|
||||
return
|
||||
new_filter = filters.filter(buffer)
|
||||
|
||||
def manage_filters(self, session):
|
||||
manage_filters = filters.filterManager(session)
|
||||
|
||||
def view_user_lists(self, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
selector = userSelector.userSelector(users=users, session_id=buffer.session.session_id)
|
||||
user = selector.get_user()
|
||||
if user == None:
|
||||
return
|
||||
l = lists.listsController(buffer.session, user=user)
|
||||
|
||||
def add_to_list(self, controller, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
selector = userSelector.userSelector(users=users, session_id=buffer.session.session_id)
|
||||
user = selector.get_user()
|
||||
if user == None:
|
||||
return
|
||||
dlg = dialogs.lists.addUserListDialog()
|
||||
dlg.populate_list([compose.compose_list(item) for item in buffer.session.db["lists"]])
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
try:
|
||||
list = buffer.session.twitter.add_list_member(list_id=buffer.session.db["lists"][dlg.get_item()].id, screen_name=user)
|
||||
older_list = utils.find_item(buffer.session.db["lists"][dlg.get_item()].id, buffer.session.db["lists"])
|
||||
listBuffer = controller.search_buffer("%s-list" % (buffer.session.db["lists"][dlg.get_item()].name.lower()), buff.session.get_name())
|
||||
if listBuffer != None:
|
||||
listBuffer.get_user_ids()
|
||||
buffer.session.db["lists"].pop(older_list)
|
||||
buffer.session.db["lists"].append(list)
|
||||
except TweepyException as e:
|
||||
log.exception("error %s" % (str(e)))
|
||||
output.speak("error %s" % (str(e)))
|
||||
|
||||
def remove_from_list(self, controller, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
selector = userSelector.userSelector(users=users, session_id=buffer.session.session_id)
|
||||
user = selector.get_user()
|
||||
if user == None:
|
||||
return
|
||||
dlg = dialogs.lists.removeUserListDialog()
|
||||
dlg.populate_list([compose.compose_list(item) for item in buffer.session.db["lists"]])
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
try:
|
||||
list = buffer.session.twitter.remove_list_member(list_id=buffer.session.db["lists"][dlg.get_item()].id, screen_name=user)
|
||||
older_list = utils.find_item(buffer.session.db["lists"][dlg.get_item()].id, buffer.session.db["lists"])
|
||||
listBuffer = controller.search_buffer("%s-list" % (buffer.session.db["lists"][dlg.get_item()].name.lower()), buffer.session.get_name())
|
||||
if listBuffer != None:
|
||||
listBuffer.get_user_ids()
|
||||
buffer.session.db["lists"].pop(older_list)
|
||||
buffer.session.db["lists"].append(list)
|
||||
except TweepyException as e:
|
||||
output.speak("error %s" % (str(e)))
|
||||
log.exception("error %s" % (str(e)))
|
||||
|
||||
def list_manager(self, session, lists_buffer_position):
|
||||
return lists.listsController(session=session, lists_buffer_position=lists_buffer_position)
|
||||
|
||||
def account_settings(self, buffer, controller):
|
||||
d = settings.accountSettingsController(buffer, controller)
|
||||
if d.response == widgetUtils.OK:
|
||||
d.save_configuration()
|
||||
if d.needs_restart == True:
|
||||
commonMessageDialogs.needs_restart()
|
||||
buffer.session.settings.write()
|
||||
buffer.session.save_persistent_data()
|
||||
restart.restart_program()
|
||||
|
||||
def follow(self, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
u = userActions.userActionsController(buffer, users)
|
||||
|
||||
def add_alias(self, buffer):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
dlg = dialogs.userAliasDialogs.addAliasDialog(_("Add an user alias"), users)
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
user, alias = dlg.get_user()
|
||||
if user == "" or alias == "":
|
||||
return
|
||||
user_id = buffer.session.get_user_by_screen_name(user)
|
||||
buffer.session.settings["user-aliases"][str(user_id)] = alias
|
||||
buffer.session.settings.write()
|
||||
output.speak(_("Alias has been set correctly for {}.").format(user))
|
||||
pub.sendMessage("alias-added")
|
||||
|
||||
# ToDo: explore how to play sound & save config differently.
|
||||
# currently, TWBlue will play the sound and save the config for the timeline even if the buffer did not load or something else.
|
||||
def open_timeline(self, controller, buffer, default="tweets"):
|
||||
if not hasattr(buffer, "get_right_tweet"):
|
||||
return
|
||||
tweet = buffer.get_right_tweet()
|
||||
if buffer.type == "people":
|
||||
users = [tweet.screen_name]
|
||||
elif buffer.type == "dm":
|
||||
users = [buffer.session.get_user(tweet.message_create["sender_id"]).screen_name]
|
||||
else:
|
||||
users = utils.get_all_users(tweet, buffer.session)
|
||||
dlg = dialogs.userSelection.selectUserDialog(users=users, default=default)
|
||||
if dlg.get_response() == widgetUtils.OK:
|
||||
usr = utils.if_user_exists(buffer.session.twitter, dlg.get_user())
|
||||
if usr != None:
|
||||
if usr == dlg.get_user():
|
||||
commonMessageDialogs.suspended_user()
|
||||
return
|
||||
if usr.protected == True:
|
||||
if usr.following == False:
|
||||
commonMessageDialogs.no_following()
|
||||
return
|
||||
tl_type = dlg.get_action()
|
||||
if tl_type == "tweets":
|
||||
if usr.statuses_count == 0:
|
||||
commonMessageDialogs.no_tweets()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
timelines_position =controller.view.search("timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(usr.screen_name,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="user_timeline", name="%s-timeline" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", bufferType=None, user_id=usr.id_str, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
buffer.session.settings["other_buffers"]["timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif tl_type == "favourites":
|
||||
if usr.favourites_count == 0:
|
||||
commonMessageDialogs.no_favs()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["favourites_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
favs_timelines_position =controller.view.search("favs_timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Likes for {}").format(usr.screen_name,), parent_tab=favs_timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="get_favorites", name="%s-favorite" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), bufferType=None, sound="favourites_timeline_updated.ogg", user_id=usr.id_str, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
buffer.session.settings["other_buffers"]["favourites_timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif tl_type == "followers":
|
||||
if usr.followers_count == 0:
|
||||
commonMessageDialogs.no_followers()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["followers_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
followers_timelines_position =controller.view.search("followers_timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(usr.screen_name,), parent_tab=followers_timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="get_followers", name="%s-followers" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", user_id=usr.id_str))
|
||||
buffer.session.settings["other_buffers"]["followers_timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
elif tl_type == "friends":
|
||||
if usr.friends_count == 0:
|
||||
commonMessageDialogs.no_friends()
|
||||
return
|
||||
if usr.id_str in buffer.session.settings["other_buffers"]["friends_timelines"]:
|
||||
commonMessageDialogs.timeline_exist()
|
||||
return
|
||||
friends_timelines_position =controller.view.search("friends_timelines", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="PeopleBuffer", session_type=buffer.session.type, buffer_title=_("Friends for {}").format(usr.screen_name,), parent_tab=friends_timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="get_friends", name="%s-friends" % (usr.id_str,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", user_id=usr.id_str))
|
||||
buffer.session.settings["other_buffers"]["friends_timelines"].append(usr.id_str)
|
||||
buffer.session.sound.play("create_timeline.ogg")
|
||||
else:
|
||||
commonMessageDialogs.user_not_exist()
|
||||
buffer.session.settings.write()
|
||||
|
||||
def open_conversation(self, controller, buffer):
|
||||
tweet = buffer.get_right_tweet()
|
||||
if hasattr(tweet, "retweeted_status") and tweet.retweeted_status != None:
|
||||
tweet = tweet.retweeted_status
|
||||
user = buffer.session.get_user(tweet.user).screen_name
|
||||
searches_position =controller.view.search("searches", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="ConversationBuffer", session_type=buffer.session.type, buffer_title=_(u"Conversation with {0}").format(user), parent_tab=searches_position, start=True, kwargs=dict(tweet=tweet, parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (tweet.id,), sessionObject=buffer.session, account=buffer.session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", since_id=tweet.id, q="@{0}".format(user)))
|
||||
|
||||
def get_trending_topics(self, controller, session):
|
||||
trends = trendingTopics.trendingTopicsController(session)
|
||||
if trends.dialog.get_response() == widgetUtils.OK:
|
||||
woeid = trends.get_woeid()
|
||||
if woeid in session.settings["other_buffers"]["trending_topic_buffers"]:
|
||||
return
|
||||
root_position =controller.view.search(session.get_name(), session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (trends.get_string()), parent_tab=root_position, start=True, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (woeid,), sessionObject=session, account=session.get_name(), trendsFor=woeid, sound="trends_updated.ogg"))
|
||||
session.settings["other_buffers"]["trending_topic_buffers"].append(str(woeid))
|
||||
session.settings.write()
|
||||
|
||||
def start_buffer(self, controller, buffer):
|
||||
if hasattr(buffer, "finished_timeline") and buffer.finished_timeline == False:
|
||||
change_title = True
|
||||
else:
|
||||
change_title = False
|
||||
try:
|
||||
if "mentions" in buffer.name or "direct_messages" in buffer.name:
|
||||
buffer.start_stream()
|
||||
else:
|
||||
buffer.start_stream(play_sound=False)
|
||||
except TweepyException as err:
|
||||
log.exception("Error %s starting buffer %s on account %s, with args %r and kwargs %r." % (str(err), buffer.name, buffer.account, buffer.args, buffer.kwargs))
|
||||
# Determine if this error was caused by a block applied to the current user (IE permission errors).
|
||||
if type(err) == Forbidden:
|
||||
buff = controller.view.search(buffer.name, buffer.account)
|
||||
buffer.remove_buffer(force=True)
|
||||
commonMessageDialogs.blocked_timeline()
|
||||
if controller.get_current_buffer() == buffer:
|
||||
controller.right()
|
||||
controller.view.delete_buffer(buff)
|
||||
controller.buffers.remove(buffer)
|
||||
del buffer
|
||||
if change_title:
|
||||
pub.sendMessage("buffer-title-changed", buffer=buffer)
|
||||
|
||||
def update_profile(self, session):
|
||||
r = user.profileController(session)
|
||||
|
||||
def search(self, controller, session, value):
|
||||
log.debug("Creating a new search...")
|
||||
dlg = dialogs.search.searchDialog(value)
|
||||
if dlg.get_response() == widgetUtils.OK and dlg.get("term") != "":
|
||||
term = dlg.get("term")
|
||||
searches_position =controller.view.search("searches", session.get_name())
|
||||
if dlg.get("tweets") == True:
|
||||
if term not in session.settings["other_buffers"]["tweet_searches"]:
|
||||
session.settings["other_buffers"]["tweet_searches"].append(term)
|
||||
session.settings.write()
|
||||
args = {"lang": dlg.get_language(), "result_type": dlg.get_result_type()}
|
||||
pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (term,), sessionObject=session, account=session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", q=term, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
else:
|
||||
log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,))
|
||||
return
|
||||
elif dlg.get("users") == True:
|
||||
pub.sendMessage("createBuffer", buffer_type="SearchPeopleBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, function="search_users", name="%s-searchUser" % (term,), sessionObject=session, account=session.get_name(), bufferType=None, sound="search_updated.ogg", q=term))
|
||||
dlg.Destroy()
|
@@ -10,9 +10,10 @@ from pubsub import pub
|
||||
log = logging.getLogger("controller.listsController")
|
||||
|
||||
class listsController(object):
|
||||
def __init__(self, session, user=None):
|
||||
def __init__(self, session, user=None, lists_buffer_position=0):
|
||||
super(listsController, self).__init__()
|
||||
self.session = session
|
||||
self.lists_buffer_position = lists_buffer_position
|
||||
if user == None:
|
||||
self.dialog = lists.listViewer()
|
||||
self.dialog.populate_list(self.get_all_lists())
|
||||
@@ -90,7 +91,9 @@ class listsController(object):
|
||||
def open_list_as_buffer(self, *args, **kwargs):
|
||||
if self.dialog.lista.get_count() == 0: return
|
||||
list = self.session.db["lists"][self.dialog.get_item()]
|
||||
pub.sendMessage("create-new-buffer", buffer="list", account=self.session.db["user_name"], create=list.name)
|
||||
pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=self.session.type, buffer_title=_("List for {}").format(list.name), parent_tab=self.lists_buffer_position, start=True, kwargs=dict(function="list_timeline", name="%s-list" % (list.name,), sessionObject=self.session, account=self.session.get_name(), bufferType=None, sound="list_tweet.ogg", list_id=list.id, include_ext_alt_text=True, tweet_mode="extended"))
|
||||
self.session.settings["other_buffers"]["lists"].append(list.name)
|
||||
self.session.settings.write()
|
||||
|
||||
def subscribe(self, *args, **kwargs):
|
||||
if self.dialog.lista.get_count() == 0: return
|
@@ -8,7 +8,7 @@ import output
|
||||
import sound
|
||||
import config
|
||||
from pubsub import pub
|
||||
from twitter_text import parse_tweet
|
||||
from twitter_text.parse_tweet import parse_tweet
|
||||
from wxUI.dialogs import twitterDialogs, urlList
|
||||
from wxUI import commonMessageDialogs
|
||||
from extra import translator, SpellChecker
|
||||
@@ -23,7 +23,7 @@ class basicTweet(object):
|
||||
self.max = max
|
||||
self.title = title
|
||||
self.session = session
|
||||
self.message = getattr(twitterDialogs, messageType)(title=title, caption=caption, message=text, *args, **kwargs)
|
||||
self.message = getattr(twitterDialogs, messageType)(title=title, caption=caption, message=text, max_length=max, *args, **kwargs)
|
||||
self.message.text.SetValue(text)
|
||||
self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
|
||||
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
|
||||
@@ -185,11 +185,12 @@ class tweet(basicTweet):
|
||||
def text_processor(self, *args, **kwargs):
|
||||
super(tweet, self).text_processor(*args, **kwargs)
|
||||
if len(self.thread) > 0:
|
||||
self.message.tweets.Enable(True)
|
||||
self.message.remove_tweet.Enable(True)
|
||||
else:
|
||||
self.message.tweets.Enable(False)
|
||||
self.message.remove_tweet.Enable(False)
|
||||
if hasattr(self.message, "tweets"):
|
||||
self.message.tweets.Enable(True)
|
||||
self.message.remove_tweet.Enable(True)
|
||||
else:
|
||||
self.message.tweets.Enable(False)
|
||||
self.message.remove_tweet.Enable(False)
|
||||
if len(self.attachments) > 0:
|
||||
self.message.attachments.Enable(True)
|
||||
self.message.remove_attachment.Enable(True)
|
247
src/controller/twitter/settings.py
Normal file
247
src/controller/twitter/settings.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import threading
|
||||
import logging
|
||||
import sound_lib
|
||||
import paths
|
||||
import widgetUtils
|
||||
import output
|
||||
from collections import OrderedDict
|
||||
from wxUI import commonMessageDialogs
|
||||
from extra.autocompletionUsers import scan, manage
|
||||
from extra.ocr import OCRSpace
|
||||
from controller.settings import globalSettingsController
|
||||
from . templateEditor import EditTemplate
|
||||
|
||||
log = logging.getLogger("Settings")
|
||||
|
||||
class accountSettingsController(globalSettingsController):
|
||||
def __init__(self, buffer, window):
|
||||
self.user = buffer.session.db["user_name"]
|
||||
self.buffer = buffer
|
||||
self.window = window
|
||||
self.config = buffer.session.settings
|
||||
super(accountSettingsController, self).__init__()
|
||||
|
||||
def create_config(self):
|
||||
self.dialog.create_general_account()
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
|
||||
widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
|
||||
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
|
||||
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
|
||||
self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"])
|
||||
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_tweets_per_call"])
|
||||
self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"])
|
||||
rt = self.config["general"]["retweet_mode"]
|
||||
if rt == "ask":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Ask"))
|
||||
elif rt == "direct":
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet without comments"))
|
||||
else:
|
||||
self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments"))
|
||||
self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"]))
|
||||
self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"])
|
||||
self.dialog.create_reporting()
|
||||
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"])
|
||||
self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
|
||||
tweet_template = self.config["templates"]["tweet"]
|
||||
dm_template = self.config["templates"]["dm"]
|
||||
sent_dm_template = self.config["templates"]["dm_sent"]
|
||||
person_template = self.config["templates"]["person"]
|
||||
self.dialog.create_templates(tweet_template=tweet_template, dm_template=dm_template, sent_dm_template=sent_dm_template, person_template=person_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.tweet, widgetUtils.BUTTON_PRESSED, self.edit_tweet_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.dm, widgetUtils.BUTTON_PRESSED, self.edit_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.sent_dm, widgetUtils.BUTTON_PRESSED, self.edit_sent_dm_template)
|
||||
widgetUtils.connect_event(self.dialog.templates.person, widgetUtils.BUTTON_PRESSED, self.edit_person_template)
|
||||
self.dialog.create_other_buffers()
|
||||
buffer_values = self.get_buffers_list()
|
||||
self.dialog.buffers.insert_buffers(buffer_values)
|
||||
self.dialog.buffers.connect_hook_func(self.toggle_buffer_active)
|
||||
widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state)
|
||||
widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up)
|
||||
widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down)
|
||||
|
||||
self.dialog.create_ignored_clients(self.config["twitter"]["ignored_clients"])
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.add, widgetUtils.BUTTON_PRESSED, self.add_ignored_client)
|
||||
widgetUtils.connect_event(self.dialog.ignored_clients.remove, widgetUtils.BUTTON_PRESSED, self.remove_ignored_client)
|
||||
self.input_devices = sound_lib.input.Input.get_device_names()
|
||||
self.output_devices = sound_lib.output.Output.get_device_names()
|
||||
self.soundpacks = []
|
||||
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ]
|
||||
self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks)
|
||||
self.dialog.set_value("sound", "volumeCtrl", int(self.config["sound"]["volume"]*100))
|
||||
self.dialog.set_value("sound", "input", self.config["sound"]["input_device"])
|
||||
self.dialog.set_value("sound", "output", self.config["sound"]["output_device"])
|
||||
self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"])
|
||||
self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"])
|
||||
self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"])
|
||||
self.dialog.set_value("sound", "indicate_geo", self.config["sound"]["indicate_geo"])
|
||||
self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"])
|
||||
self.dialog.create_extras(OCRSpace.translatable_langs)
|
||||
self.dialog.set_value("extras", "sndup_apiKey", self.config["sound"]["sndup_api_key"])
|
||||
language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"])
|
||||
self.dialog.extras.ocr_lang.SetSelection(language_index)
|
||||
self.dialog.realize()
|
||||
self.dialog.set_title(_(u"Account settings for %s") % (self.user,))
|
||||
self.response = self.dialog.get_response()
|
||||
|
||||
def edit_tweet_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["tweet"]
|
||||
control = EditTemplate(template=template, type="tweet")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["tweet"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.tweet.SetLabel(_("Edit template for tweets. Current template: {}").format(result))
|
||||
|
||||
def edit_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.dm.SetLabel(_("Edit template for direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_sent_dm_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["dm_sent"]
|
||||
control = EditTemplate(template=template, type="dm")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["dm_sent"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.sent_dm.SetLabel(_("Edit template for sent direct messages. Current template: {}").format(result))
|
||||
|
||||
def edit_person_template(self, *args, **kwargs):
|
||||
template = self.config["templates"]["person"]
|
||||
control = EditTemplate(template=template, type="person")
|
||||
result = control.run_dialog()
|
||||
if result != "": # Template has been saved.
|
||||
self.config["templates"]["person"] = result
|
||||
self.config.write()
|
||||
self.dialog.templates.person.SetLabel(_("Edit template for persons. Current template: {}").format(result))
|
||||
|
||||
def save_configuration(self):
|
||||
if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in relative times.")
|
||||
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
|
||||
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
|
||||
self.config["general"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
|
||||
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
|
||||
if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
|
||||
self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in database strategy management.")
|
||||
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
|
||||
if self.dialog.get_value("general", "persist_size") == '':
|
||||
self.config["general"]["persist_size"] =-1
|
||||
else:
|
||||
try:
|
||||
self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size"))
|
||||
except ValueError:
|
||||
output.speak("Invalid cache size, setting to default.",True)
|
||||
self.config["general"]["persist_size"] =1764
|
||||
|
||||
if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"):
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in timeline order.")
|
||||
self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines")
|
||||
rt = self.dialog.get_value("general", "retweet_mode")
|
||||
if rt == _(u"Ask"):
|
||||
self.config["general"]["retweet_mode"] = "ask"
|
||||
elif rt == _(u"Retweet without comments"):
|
||||
self.config["general"]["retweet_mode"] = "direct"
|
||||
else:
|
||||
self.config["general"]["retweet_mode"] = "comment"
|
||||
buffers_list = self.dialog.buffers.get_list()
|
||||
if buffers_list != self.config["general"]["buffer_order"]:
|
||||
self.needs_restart = True
|
||||
log.debug("Triggered app restart due to change in buffer ordering.")
|
||||
self.config["general"]["buffer_order"] = buffers_list
|
||||
self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting")
|
||||
self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting")
|
||||
self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()]
|
||||
if self.config["sound"]["input_device"] != self.dialog.sound.get("input"):
|
||||
self.config["sound"]["input_device"] = self.dialog.sound.get("input")
|
||||
try:
|
||||
self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"]))
|
||||
except:
|
||||
self.config["sound"]["input_device"] = "default"
|
||||
if self.config["sound"]["output_device"] != self.dialog.sound.get("output"):
|
||||
self.config["sound"]["output_device"] = self.dialog.sound.get("output")
|
||||
try:
|
||||
self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"]))
|
||||
except:
|
||||
self.config["sound"]["output_device"] = "default"
|
||||
self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0
|
||||
self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute")
|
||||
self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack")
|
||||
self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio")
|
||||
self.config["sound"]["indicate_geo"] = self.dialog.get_value("sound", "indicate_geo")
|
||||
self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img")
|
||||
self.config["sound"]["sndup_api_key"] = self.dialog.get_value("extras", "sndup_apiKey")
|
||||
self.buffer.session.sound.config = self.config["sound"]
|
||||
self.buffer.session.sound.check_soundpack()
|
||||
self.config.write()
|
||||
|
||||
def toggle_state(self,*args,**kwargs):
|
||||
return self.dialog.buffers.change_selected_item()
|
||||
|
||||
def on_autocompletion_scan(self, *args, **kwargs):
|
||||
configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window)
|
||||
to_scan = configuration.show_dialog()
|
||||
if to_scan == True:
|
||||
configuration.prepare_progress_dialog()
|
||||
t = threading.Thread(target=configuration.scan)
|
||||
t.start()
|
||||
|
||||
|
||||
|
||||
def on_autocompletion_manage(self, *args, **kwargs):
|
||||
configuration = manage.autocompletionManage(self.buffer.session)
|
||||
configuration.show_settings()
|
||||
|
||||
def add_ignored_client(self, *args, **kwargs):
|
||||
client = commonMessageDialogs.get_ignored_client()
|
||||
if client == None: return
|
||||
if client not in self.config["twitter"]["ignored_clients"]:
|
||||
self.config["twitter"]["ignored_clients"].append(client)
|
||||
self.dialog.ignored_clients.append(client)
|
||||
|
||||
def remove_ignored_client(self, *args, **kwargs):
|
||||
if self.dialog.ignored_clients.get_clients() == 0: return
|
||||
id = self.dialog.ignored_clients.get_client_id()
|
||||
self.config["twitter"]["ignored_clients"].pop(id)
|
||||
self.dialog.ignored_clients.remove_(id)
|
||||
|
||||
def get_buffers_list(self):
|
||||
all_buffers=OrderedDict()
|
||||
all_buffers['home']=_(u"Home")
|
||||
all_buffers['mentions']=_(u"Mentions")
|
||||
all_buffers['dm']=_(u"Direct Messages")
|
||||
all_buffers['sent_dm']=_(u"Sent direct messages")
|
||||
all_buffers['sent_tweets']=_(u"Sent tweets")
|
||||
all_buffers['favorites']=_(u"Likes")
|
||||
all_buffers['followers']=_(u"Followers")
|
||||
all_buffers['friends']=_(u"Friends")
|
||||
all_buffers['blocks']=_(u"Blocked users")
|
||||
all_buffers['muted']=_(u"Muted users")
|
||||
list_buffers = []
|
||||
hidden_buffers=[]
|
||||
all_buffers_keys = list(all_buffers.keys())
|
||||
# Check buffers shown first.
|
||||
for i in self.config["general"]["buffer_order"]:
|
||||
if i in all_buffers_keys:
|
||||
list_buffers.append((i, all_buffers[i], True))
|
||||
# This second pass will retrieve all hidden buffers.
|
||||
for i in all_buffers_keys:
|
||||
if i not in self.config["general"]["buffer_order"]:
|
||||
hidden_buffers.append((i, all_buffers[i], False))
|
||||
list_buffers.extend(hidden_buffers)
|
||||
return list_buffers
|
||||
|
||||
def toggle_buffer_active(self, ev):
|
||||
change = self.dialog.buffers.get_event(ev)
|
||||
if change == True:
|
||||
self.dialog.buffers.change_selected_item()
|
@@ -29,42 +29,42 @@ class userActionsController(object):
|
||||
def follow(self, user):
|
||||
try:
|
||||
self.session.twitter.create_friendship(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unfollow(self, user):
|
||||
try:
|
||||
id = self.session.twitter.destroy_friendship(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def mute(self, user):
|
||||
try:
|
||||
id = self.session.twitter.create_mute(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def unmute(self, user):
|
||||
try:
|
||||
id = self.session.twitter.destroy_mute(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def report(self, user):
|
||||
try:
|
||||
id = self.session.twitter.report_spam(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
||||
def block(self, user):
|
||||
try:
|
||||
id = self.session.twitter.create_block(screen_name=user )
|
||||
pub.sendMessage("restartStreaming", session=self.session.session_id)
|
||||
pub.sendMessage("twitter.restart_streaming", session=self.session.session_id)
|
||||
except TweepyException as err:
|
||||
output.speak("Error %s" % (str(err)), True)
|
||||
|
@@ -1,3 +1 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from .soundsTutorial import soundsTutorial
|
||||
|
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from gi.repository import Gtk
|
||||
import widgetUtils
|
||||
|
||||
class soundsTutorialDialog(Gtk.Dialog):
|
||||
def __init__(self, actions):
|
||||
super(soundsTutorialDialog, self).__init__("Sounds tutorial", None, 0, (Gtk.STOCK_CANCEL, widgetUtils.CANCEL))
|
||||
box = self.get_content_area()
|
||||
label = Gtk.Label("Press enter for listen the sound")
|
||||
self.list = widgetUtils.list("Action")
|
||||
self.populate_actions(actions)
|
||||
lBox = Gtk.Box(spacing=6)
|
||||
lBox.add(label)
|
||||
lBox.add(self.list.list)
|
||||
box.add(lBox)
|
||||
self.play = Gtk.Button("Play")
|
||||
box.add(self.play)
|
||||
self.show_all()
|
||||
|
||||
def populate_actions(self, actions):
|
||||
for i in actions:
|
||||
self.list.insert_item(i)
|
||||
|
||||
def get_selected(self):
|
||||
return self.list.get_selected()
|
@@ -1,4 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
#Reverse sort, by Bill Dengler <codeofdusk@gmail.com> for use in TWBlue http://twblue.es
|
||||
def invert_tuples(t):
|
||||
"Invert a list of tuples, so that the 0th element becomes the -1th, and the -1th becomes the 0th."
|
||||
|
@@ -1,7 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from builtins import object
|
||||
import platform
|
||||
import widgetUtils
|
||||
import os
|
||||
@@ -9,10 +6,7 @@ import paths
|
||||
import logging
|
||||
log = logging.getLogger("extra.SoundsTutorial.soundsTutorial")
|
||||
from . import soundsTutorial_constants
|
||||
if platform.system() == "Windows":
|
||||
from . import wx_ui as UI
|
||||
elif platform.system() == "Linux":
|
||||
from . import gtk_ui as UI
|
||||
from . import wx_ui as UI
|
||||
|
||||
class soundsTutorial(object):
|
||||
def __init__(self, sessionObject):
|
||||
|
@@ -1,7 +1,4 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
#-*- coding: utf-8 -*-
|
||||
from . import reverse_sort
|
||||
import application
|
||||
actions = reverse_sort.reverse_sort([ ("audio", _(u"Audio tweet.")),
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import wx
|
||||
import widgetUtils
|
||||
|
||||
|
@@ -1,8 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from builtins import next
|
||||
from builtins import object
|
||||
import os
|
||||
import logging
|
||||
from . import wx_ui
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
from enchant.tokenize import Filter
|
||||
|
||||
|
@@ -16,7 +16,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
############################################################
|
||||
from __future__ import unicode_literals
|
||||
import wx
|
||||
import application
|
||||
|
||||
|
@@ -2,7 +2,4 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from . import translator
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
from . import wx_ui as gui
|
||||
|
||||
from . import wx_ui as gui
|
@@ -21,7 +21,7 @@ def parse(s):
|
||||
lst.remove(item)
|
||||
#end if
|
||||
if len(lst) > 1: #more than one key, parse error
|
||||
raise ValueError, 'unknown modifier %s' % lst[0]
|
||||
raise ValueError('unknown modifier %s' % lst[0])
|
||||
return (m, lst[0].lower())
|
||||
class AtspiThread(threading.Thread):
|
||||
def run(self):
|
||||
|
@@ -6,27 +6,26 @@ from wxUI.dialogs import baseDialog
|
||||
class keystrokeEditorDialog(baseDialog.BaseWXDialog):
|
||||
def __init__(self):
|
||||
super(keystrokeEditorDialog, self).__init__(parent=None, id=-1, title=_(u"Keystroke editor"))
|
||||
panel = wx.Panel(self)
|
||||
self.actions = []
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
keysText = wx.StaticText(panel, -1, _(u"Select a keystroke to edit"))
|
||||
keysText = wx.StaticText(self, -1, _(u"Select a keystroke to edit"))
|
||||
self.keys = widgets.list(self, _(u"Action"), _(u"Keystroke"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL, size=(400, 450))
|
||||
self.keys.list.SetFocus()
|
||||
firstSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
firstSizer.Add(keysText, 0, wx.ALL, 5)
|
||||
firstSizer.Add(self.keys.list, 0, wx.ALL, 5)
|
||||
self.edit = wx.Button(panel, -1, _(u"Edit"))
|
||||
self.edit = wx.Button(self, -1, _(u"Edit"))
|
||||
self.edit.SetDefault()
|
||||
self.undefine = wx.Button(panel, -1, _("Undefine keystroke"))
|
||||
self.execute = wx.Button(panel, -1, _(u"Execute action"))
|
||||
close = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||
self.undefine = wx.Button(self, -1, _("Undefine keystroke"))
|
||||
self.execute = wx.Button(self, -1, _(u"Execute action"))
|
||||
close = wx.Button(self, wx.ID_CANCEL, _(u"Close"))
|
||||
secondSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
secondSizer.Add(self.edit, 0, wx.ALL, 5)
|
||||
secondSizer.Add(self.execute, 0, wx.ALL, 5)
|
||||
secondSizer.Add(close, 0, wx.ALL, 5)
|
||||
sizer.Add(firstSizer, 0, wx.ALL, 5)
|
||||
sizer.Add(secondSizer, 0, wx.ALL, 5)
|
||||
panel.SetSizer(sizer)
|
||||
self.SetSizer(sizer)
|
||||
self.SetClientSize(sizer.CalcMin())
|
||||
|
||||
def put_keystrokes(self, actions, keystrokes):
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,92 +1,82 @@
|
||||
# Steffen Schultz <steffenschultz@mailbox.org>, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TW Blue 0.80\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"Report-Msgid-Bugs-To: manuel@manuelcortez.net\n"
|
||||
"POT-Creation-Date: 2022-08-16 17:50-0500\n"
|
||||
"PO-Revision-Date: 2019-03-17 15:50+0100\n"
|
||||
"PO-Revision-Date: 2022-10-05 13:23+0000\n"
|
||||
"Last-Translator: Steffen Schultz <steffenschultz@mailbox.org>\n"
|
||||
"Language-Team: Steffen Schultz <schulle3o@yahoo.de>\n"
|
||||
"Language-Team: German <https://weblate.mcvsoftware.com/projects/twblue/"
|
||||
"twblue/de/>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.14\n"
|
||||
"Generated-By: Babel 2.10.3\n"
|
||||
|
||||
#: languageHandler.py:61
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Amharic"
|
||||
msgstr "Amharisch"
|
||||
|
||||
#: languageHandler.py:62
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Aragonese"
|
||||
msgstr "Japanisch"
|
||||
msgstr "Aragonesisch"
|
||||
|
||||
#: languageHandler.py:63
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Spanish"
|
||||
msgstr "Spanisch"
|
||||
|
||||
#: languageHandler.py:64
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugiesisch"
|
||||
|
||||
#: languageHandler.py:65
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Russian"
|
||||
msgstr "Russisch"
|
||||
|
||||
#: languageHandler.py:66
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "italian"
|
||||
msgstr "Italienisch"
|
||||
|
||||
#: languageHandler.py:67
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Turkey"
|
||||
msgstr "Funktion"
|
||||
msgstr "Türkisch"
|
||||
|
||||
#: languageHandler.py:68
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Galician"
|
||||
msgstr "Galizisch"
|
||||
|
||||
#: languageHandler.py:69
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Catala"
|
||||
msgstr "Katalanisch"
|
||||
|
||||
#: languageHandler.py:70
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Vasque"
|
||||
msgstr "Baskisch"
|
||||
|
||||
#: languageHandler.py:71
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "polish"
|
||||
msgstr "Polnisch"
|
||||
|
||||
#: languageHandler.py:72
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Arabic"
|
||||
msgstr "Arabisch"
|
||||
|
||||
#: languageHandler.py:73
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Nepali"
|
||||
msgstr "Nepalesisch"
|
||||
@@ -94,10 +84,9 @@ msgstr "Nepalesisch"
|
||||
#: languageHandler.py:74
|
||||
msgctxt "languageName"
|
||||
msgid "Serbian (Latin)"
|
||||
msgstr ""
|
||||
msgstr "Serbisch (Latein)"
|
||||
|
||||
#: languageHandler.py:75
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Japanese"
|
||||
msgstr "Japanisch"
|
||||
@@ -168,9 +157,8 @@ msgid "Followers"
|
||||
msgstr "Folger"
|
||||
|
||||
#: controller/mainController.py:351
|
||||
#, fuzzy
|
||||
msgid "Following"
|
||||
msgstr "E&ntfolgen"
|
||||
msgstr "Folge ich"
|
||||
|
||||
#: controller/buffers/twitter/base.py:70 controller/mainController.py:353
|
||||
#: controller/mainController.py:1399 controller/settings.py:346
|
||||
@@ -201,7 +189,6 @@ msgid "Likes for {}"
|
||||
msgstr "Likes für {}"
|
||||
|
||||
#: controller/mainController.py:364
|
||||
#, fuzzy
|
||||
msgid "Followers timelines"
|
||||
msgstr "Folger-Zeitleisten"
|
||||
|
||||
@@ -211,9 +198,8 @@ msgid "Followers for {}"
|
||||
msgstr "Folger von {}"
|
||||
|
||||
#: controller/mainController.py:368
|
||||
#, fuzzy
|
||||
msgid "Following timelines"
|
||||
msgstr "Folger-Zeitleisten"
|
||||
msgstr "Freunde-Zeitleisten"
|
||||
|
||||
#: controller/mainController.py:371 controller/mainController.py:934
|
||||
#: controller/mainController.py:1582
|
||||
@@ -267,11 +253,11 @@ msgstr "Filter können für diese Ansicht nicht angewendet werden"
|
||||
|
||||
#: controller/mainController.py:747
|
||||
msgid "Add an user alias"
|
||||
msgstr ""
|
||||
msgstr "Benutzer-Alias hinzufügen"
|
||||
|
||||
#: controller/mainController.py:755
|
||||
msgid "Alias has been set correctly for {}."
|
||||
msgstr ""
|
||||
msgstr "Alias korrekt festgelegt für {}."
|
||||
|
||||
#: controller/mainController.py:823 controller/messages.py:328
|
||||
msgid "MMM D, YYYY. H:m"
|
||||
@@ -292,7 +278,7 @@ msgstr ""
|
||||
|
||||
#: controller/mainController.py:998
|
||||
msgid "Unable to find address in OpenStreetMap."
|
||||
msgstr ""
|
||||
msgstr "Adresse kann nicht in OpenStreetMap gefunden werden."
|
||||
|
||||
#: controller/mainController.py:1011
|
||||
msgid "There are no results for the coordinates in this tweet"
|
||||
@@ -423,34 +409,32 @@ msgid "View item"
|
||||
msgstr "Eintrag ansehen"
|
||||
|
||||
#: controller/messages.py:381
|
||||
#, fuzzy
|
||||
msgid "Link copied to clipboard."
|
||||
msgstr "In Zwischenablage kopieren"
|
||||
msgstr "Link in Zwischenablage kopiert."
|
||||
|
||||
#: controller/settings.py:77
|
||||
#, fuzzy
|
||||
msgid "System default"
|
||||
msgstr "Benutzerstandard"
|
||||
msgstr "Systemstandard"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "HTTP"
|
||||
msgstr ""
|
||||
msgstr "HTTP"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v4"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v4"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v4 with DNS support"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v4 mit DNS-Unterstützung"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v5"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v5"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v5 with DNS support"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v5 mit DNS-Unterstützung"
|
||||
|
||||
#: controller/settings.py:155 controller/settings.py:269
|
||||
#: wxUI/dialogs/configuration.py:121
|
||||
@@ -473,19 +457,20 @@ msgstr "Account-Einstellungen für %s"
|
||||
|
||||
#: controller/settings.py:213 wxUI/dialogs/configuration.py:247
|
||||
msgid "Edit template for tweets. Current template: {}"
|
||||
msgstr ""
|
||||
msgstr "Tweet-Vorlage bearbeiten. Aktuelle Vorlage: {}"
|
||||
|
||||
#: controller/settings.py:222 wxUI/dialogs/configuration.py:249
|
||||
msgid "Edit template for direct messages. Current template: {}"
|
||||
msgstr ""
|
||||
msgstr "Direktnachrichten-Vorlage bearbeiten. Aktuelle Vorlage: {}"
|
||||
|
||||
#: controller/settings.py:231 wxUI/dialogs/configuration.py:251
|
||||
msgid "Edit template for sent direct messages. Current template: {}"
|
||||
msgstr ""
|
||||
"Vorlage für gesendete Direktnachrichten bearbeiten. Aktuelle Vorlage: {}"
|
||||
|
||||
#: controller/settings.py:240 wxUI/dialogs/configuration.py:253
|
||||
msgid "Edit template for persons. Current template: {}"
|
||||
msgstr ""
|
||||
msgstr "Vorlage für Personen bearbeiten. Aktuelle Vorlage: {}"
|
||||
|
||||
#: controller/settings.py:340
|
||||
msgid "Direct Messages"
|
||||
@@ -559,7 +544,7 @@ msgstr "Geschützt: %s\n"
|
||||
|
||||
#: controller/user.py:110
|
||||
msgid "Relationship: "
|
||||
msgstr ""
|
||||
msgstr "Beziehung "
|
||||
|
||||
#: controller/user.py:112
|
||||
msgid "You follow {0}. "
|
||||
@@ -598,14 +583,12 @@ msgid "You can't ignore direct messages"
|
||||
msgstr "Du kannst keine Direktnachrichten ignorieren."
|
||||
|
||||
#: controller/userAliasController.py:31
|
||||
#, fuzzy
|
||||
msgid "Edit alias for {}"
|
||||
msgstr "Liste für {}"
|
||||
msgstr "Alias für {} bearbeiten"
|
||||
|
||||
#: controller/userSelector.py:10
|
||||
#, fuzzy
|
||||
msgid "Select user"
|
||||
msgstr "Wähle den Benutzer"
|
||||
msgstr "Benutzer wählen"
|
||||
|
||||
#: controller/buffers/base/base.py:91 controller/buffers/mastodon/base.py:91
|
||||
msgid "This action is not supported for this buffer"
|
||||
@@ -677,9 +660,8 @@ msgid "New direct message"
|
||||
msgstr "Neue Direktnachricht"
|
||||
|
||||
#: controller/buffers/twitter/base.py:452
|
||||
#, fuzzy
|
||||
msgid "This action is not supported on protected accounts."
|
||||
msgstr "Diese Aktion ist in der Ansicht noch nicht verfügbar."
|
||||
msgstr "Diese Aktion wird für geschützte Accounts nicht unterstützt."
|
||||
|
||||
#: controller/buffers/twitter/base.py:469
|
||||
msgid "Quote"
|
||||
@@ -1107,19 +1089,16 @@ msgid "Autocomplete users' settings"
|
||||
msgstr "Auto-Vervollständigungs-Einstellungen"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:11
|
||||
#, fuzzy
|
||||
msgid "Add followers to database"
|
||||
msgstr "Benutzer zur Datenbank hinzufügen"
|
||||
msgstr "Folger zur Datenbank hinzufügen"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:12
|
||||
#, fuzzy
|
||||
msgid "Add friends to database"
|
||||
msgstr "Benutzer zur Datenbank hinzufügen"
|
||||
msgstr "Freunde zur Datenbank hinzufügen"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:26
|
||||
#, fuzzy
|
||||
msgid "Updating autocompletion database"
|
||||
msgstr "Auto-Vervollständigungsdatenbank verwalten"
|
||||
msgstr "Auto-Vervollständigungsdatenbank aktualisieren"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:37
|
||||
msgid ""
|
||||
@@ -1132,6 +1111,14 @@ msgid ""
|
||||
"with no error, you will be redirected back to the account settings dialog. "
|
||||
"Do you want to continue?"
|
||||
msgstr ""
|
||||
"Dieser Vorgang wird die von dir gewählten Benutzer auf Twitter abrufen und "
|
||||
"sie in die Autovervollständigungs-Datenbank aufnehmen. Bitte beachte, dass "
|
||||
"es hierbei zur Überschreitung von Twitters API-Begrenzungen kommen kann, "
|
||||
"etwa bei sehr vielen Nutzern oder wenn dieser Vorgang innerhalb der letzten "
|
||||
"15 Minuten bereits ausgeführt wurde. In diesem Fall wird eine Fehlermeldung "
|
||||
"angezeigt und du kannst es in einigen Minuten erneut versuchen. Bei "
|
||||
"erfolgreichem Abschluss wirst du in die Account-Einstellungen "
|
||||
"weitergeleitet. Möchtest du fortfahren?"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:37 wxUI/commonMessageDialogs.py:36
|
||||
#: wxUI/commonMessageDialogs.py:86
|
||||
@@ -1140,7 +1127,7 @@ msgstr "Achtung"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:43
|
||||
msgid "TWBlue has imported {} users successfully."
|
||||
msgstr ""
|
||||
msgstr "TWBlue hat die Benutzer erfolgreich importiert."
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:43
|
||||
msgid "Done"
|
||||
@@ -1149,6 +1136,8 @@ msgstr "Fertig"
|
||||
#: extra/autocompletionUsers/wx_scan.py:47
|
||||
msgid "Error adding users from Twitter. Please try again in about 15 minutes."
|
||||
msgstr ""
|
||||
"Fehler beim Hinzufügen der Twitter-Benutzer. Bitte warte 15 Minuten und "
|
||||
"versuche es erneut."
|
||||
|
||||
#: extra/ocr/OCRSpace.py:7
|
||||
msgid "Detect automatically"
|
||||
@@ -1755,9 +1744,8 @@ msgid "Extracts the text from a picture and displays the result in a dialog."
|
||||
msgstr "Extrahiert Text aus einem Bild und zeigt ihn in einem Dialog an."
|
||||
|
||||
#: keystrokeEditor/constants.py:59
|
||||
#, fuzzy
|
||||
msgid "Adds an alias to an user"
|
||||
msgstr "Wähle eine Liste zum Hinzufügen des Benutzers."
|
||||
msgstr "Erstellt einen Alias für einen Benutzer"
|
||||
|
||||
#: keystrokeEditor/wx_ui.py:8
|
||||
msgid "Keystroke editor"
|
||||
@@ -1782,9 +1770,8 @@ msgid "Edit"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: keystrokeEditor/wx_ui.py:20 keystrokeEditor/wx_ui.py:50
|
||||
#, fuzzy
|
||||
msgid "Undefine keystroke"
|
||||
msgstr "Bearbeite Tastenkombination"
|
||||
msgstr "Tastenkombination entfernen"
|
||||
|
||||
#: keystrokeEditor/wx_ui.py:21
|
||||
msgid "Execute action"
|
||||
@@ -1797,12 +1784,11 @@ msgstr "Schließen"
|
||||
|
||||
#: keystrokeEditor/wx_ui.py:42
|
||||
msgid "Undefined"
|
||||
msgstr ""
|
||||
msgstr "Nicht definiert"
|
||||
|
||||
#: keystrokeEditor/wx_ui.py:50
|
||||
#, fuzzy
|
||||
msgid "Are you sure you want to undefine this keystroke?"
|
||||
msgstr "Möchtest du diese Liste wirklich löschen?"
|
||||
msgstr "Möchtest du diese Tastenkombination wirklich entfernen?"
|
||||
|
||||
#: keystrokeEditor/wx_ui.py:54
|
||||
msgid "Editing keystroke"
|
||||
@@ -1918,10 +1904,15 @@ msgid ""
|
||||
"after an account reactivation. Please remove the account manually from your "
|
||||
"Twitter sessions in order to stop seeing this message."
|
||||
msgstr ""
|
||||
"TWBlue kann den Account für {} nicht in Twitter authentisieren. Dies kann "
|
||||
"durch einen ungültigen oder veralteten Token ausgelöst werden, widerrufener "
|
||||
"App-Zugriff oder nach einer Account-Reaktivierung. Bitte entferne den "
|
||||
"Account manuell aus der Sitzungsverwaltung, um diesen Fehler nicht mehr "
|
||||
"anzuzeigen."
|
||||
|
||||
#: sessionmanager/wxUI.py:81
|
||||
msgid "Authentication error for session {}"
|
||||
msgstr ""
|
||||
msgstr "Authentisierungsfehler für Sitzung {}"
|
||||
|
||||
#: sessions/base.py:113
|
||||
msgid ""
|
||||
@@ -1929,6 +1920,9 @@ msgid ""
|
||||
"and rebuilt automatically. If this error persists, send the error log to the "
|
||||
"{app} developers."
|
||||
msgstr ""
|
||||
"Beim Speichern der {app}-Datenbank ist ein Ausnahmefehler aufgetreten. Sie "
|
||||
"wird daher gelöscht und neu erstellt. Sollte das Problem weiterhin bestehen, "
|
||||
"sende das Fehlerprotokoll an die {app}-Entwickler."
|
||||
|
||||
#: sessions/base.py:153
|
||||
msgid ""
|
||||
@@ -1936,6 +1930,9 @@ msgid ""
|
||||
"and rebuilt automatically. If this error persists, send the error log to the "
|
||||
"{app} developers."
|
||||
msgstr ""
|
||||
"Beim Laden der {app}-Datenbank ist ein Ausnahmefehler aufgetreten. Sie wird "
|
||||
"daher gelöscht und neu erstellt. Sollte das Problem weiterhin bestehen, "
|
||||
"sende das Fehlerprotokoll an die {app}-Entwickler."
|
||||
|
||||
#: sessions/twitter/compose.py:25 sessions/twitter/compose.py:68
|
||||
#: sessions/twitter/compose.py:133 sessions/twitter/compose.py:142
|
||||
@@ -1988,30 +1985,30 @@ msgid "%s succeeded."
|
||||
msgstr "%s erfolgreich."
|
||||
|
||||
#: sessions/twitter/session.py:452 sessions/twitter/session.py:535
|
||||
#, fuzzy
|
||||
msgid "Deleted account"
|
||||
msgstr "Neuer Account"
|
||||
msgstr "Gelöschter Account"
|
||||
|
||||
#: sessions/twitter/templates.py:16
|
||||
msgid "$display_name, $text $image_descriptions $date. $source"
|
||||
msgstr ""
|
||||
msgstr "$display_name, $text $image_descriptions $date. $source"
|
||||
|
||||
#: sessions/twitter/templates.py:17
|
||||
msgid "$sender_display_name, $text $date"
|
||||
msgstr ""
|
||||
msgstr "$sender_display_name, $text $date"
|
||||
|
||||
#: sessions/twitter/templates.py:18
|
||||
msgid "Dm to $recipient_display_name, $text $date"
|
||||
msgstr ""
|
||||
msgstr "Direktnachricht an $recipient_display_name, $text $date"
|
||||
|
||||
#: sessions/twitter/templates.py:19
|
||||
msgid ""
|
||||
"$display_name (@$screen_name). $followers followers, $following following, "
|
||||
"$tweets tweets. Joined Twitter $created_at."
|
||||
msgstr ""
|
||||
"$display_name (@$screen_name). $followers Folger, folgt $following, $tweets "
|
||||
"Tweets. Trat Twitter $created_at bei."
|
||||
|
||||
#: sessions/twitter/templates.py:54
|
||||
#, fuzzy
|
||||
msgid "Image description: {}."
|
||||
msgstr "Bildbeschreibung"
|
||||
|
||||
@@ -2024,13 +2021,12 @@ msgid "No status found with that ID"
|
||||
msgstr "Kein Status mit dieser ID gefunden."
|
||||
|
||||
#: sessions/twitter/utils.py:247
|
||||
#, fuzzy
|
||||
msgid "Error {0}"
|
||||
msgstr "Fehlercode {0}"
|
||||
msgstr "Fehler {0}"
|
||||
|
||||
#: sessions/twitter/utils.py:274
|
||||
msgid "{user_1}, {user_2} and {all_users} more: {text}"
|
||||
msgstr ""
|
||||
msgstr "{user_1}, {user_2} und {all_users} weitere: {text}"
|
||||
|
||||
#: sessions/twitter/wxUI.py:7
|
||||
msgid "Authorising account..."
|
||||
@@ -2296,7 +2292,7 @@ msgstr ""
|
||||
|
||||
#: wxUI/commonMessageDialogs.py:95
|
||||
msgid "The configuration file is invalid."
|
||||
msgstr ""
|
||||
msgstr "Die Konfigurationsdatei ist ungültig."
|
||||
|
||||
#: wxUI/commonMessageDialogs.py:98
|
||||
msgid ""
|
||||
@@ -2438,7 +2434,7 @@ msgstr "&Listen verwalten"
|
||||
|
||||
#: wxUI/view.py:23
|
||||
msgid "Manage user aliases"
|
||||
msgstr ""
|
||||
msgstr "Benutzer-Aliase verwalten"
|
||||
|
||||
#: wxUI/view.py:24
|
||||
msgid "&Edit keystrokes"
|
||||
@@ -2478,7 +2474,7 @@ msgstr "&Direktnachricht"
|
||||
|
||||
#: wxUI/view.py:47
|
||||
msgid "Add a&lias"
|
||||
msgstr ""
|
||||
msgstr "A&lias hinzufügen"
|
||||
|
||||
#: wxUI/view.py:48
|
||||
msgid "&Add to list"
|
||||
@@ -2510,7 +2506,7 @@ msgstr "Filter &verwalten"
|
||||
|
||||
#: wxUI/view.py:60
|
||||
msgid "Find a string in the currently focused buffer..."
|
||||
msgstr "Eine Zeichenkette im der momentan fokussierten Ansicht suchen..."
|
||||
msgstr "Eine Zeichenkette in der momentan fokussierten Ansicht suchen..."
|
||||
|
||||
#: wxUI/view.py:61
|
||||
msgid "&Load previous items"
|
||||
@@ -2729,18 +2725,18 @@ msgid "Password: "
|
||||
msgstr "Passwort: "
|
||||
|
||||
#: wxUI/dialogs/configuration.py:102
|
||||
#, fuzzy
|
||||
msgid "User autocompletion settings"
|
||||
msgstr "Auto-Vervollständigungs-Einstellungen..."
|
||||
msgstr "Auto-Vervollständigungs-Einstellungen"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:103
|
||||
msgid ""
|
||||
"Scan account and add friends and followers to the user autocompletion "
|
||||
"database"
|
||||
msgstr ""
|
||||
"Account scannen und Freunde und Folger zur Autovervollständigungs-Datenbank "
|
||||
"hinzufügen"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:104
|
||||
#, fuzzy
|
||||
msgid "Manage autocompletion database"
|
||||
msgstr "Auto-Vervollständigungsdatenbank verwalten"
|
||||
|
||||
@@ -2770,7 +2766,7 @@ msgstr "Zeige Benutzernamen statt der vollständigen Namen"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:128
|
||||
msgid "hide emojis in usernames"
|
||||
msgstr ""
|
||||
msgstr "Emojis in Benutzernamen ausblenden"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:130
|
||||
msgid ""
|
||||
@@ -2785,6 +2781,8 @@ msgid ""
|
||||
"Load cache for tweets in memory (much faster in big datasets but requires "
|
||||
"more RAM)"
|
||||
msgstr ""
|
||||
"Tweets im Speicher zwischenlagern (sorgt für verbesserte Geschwindigkeit bei "
|
||||
"großen Datenmengen, erhöht jedoch die RAM-Nutzung)"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:141
|
||||
msgid "Enable automatic speech feedback"
|
||||
@@ -2910,7 +2908,7 @@ msgstr "Ansichten"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:406
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
msgstr "Vorlagen"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:410
|
||||
msgid "Sound"
|
||||
@@ -3004,11 +3002,11 @@ msgstr "Abbrechen"
|
||||
|
||||
#: wxUI/dialogs/filterDialogs.py:120
|
||||
msgid "You must define a name for the filter before creating it."
|
||||
msgstr ""
|
||||
msgstr "Du musst vor dem Erstellen eines Filters einen namen festlegen."
|
||||
|
||||
#: wxUI/dialogs/filterDialogs.py:120
|
||||
msgid "Missing filter name"
|
||||
msgstr ""
|
||||
msgstr "Fehlender Filtername"
|
||||
|
||||
#: wxUI/dialogs/filterDialogs.py:127
|
||||
msgid "Manage filters"
|
||||
@@ -3269,53 +3267,44 @@ msgid "&Ignore tweets from this client"
|
||||
msgstr "Tweets dieses Clients &ignorieren"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:18
|
||||
#, fuzzy
|
||||
msgid "Alias"
|
||||
msgstr "immer"
|
||||
msgstr "Alias"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:41
|
||||
#, fuzzy
|
||||
msgid "Edit user aliases"
|
||||
msgstr "Bearbeiten der {0} Benutzerdatenbank"
|
||||
msgstr "Benutzer-Aliase bearbeiten"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:48
|
||||
#, fuzzy
|
||||
msgid "Actions"
|
||||
msgstr "Aktion"
|
||||
msgstr "Aktionen"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:50
|
||||
#, fuzzy
|
||||
msgid "Add alias"
|
||||
msgstr "Zur Liste hinzufügen"
|
||||
msgstr "Alias hinzufügen"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:51
|
||||
msgid "Adds a new user alias"
|
||||
msgstr ""
|
||||
msgstr "Fügt einen neuen Benutzer-Alias hinzu"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:54
|
||||
#, fuzzy
|
||||
msgid "Edit the currently focused user Alias."
|
||||
msgstr "Interagiert mit dem momentan fokussierten Tweet."
|
||||
msgstr "Den momentan fokussierten Benutzer-Alias bearbeiten."
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:58
|
||||
#, fuzzy
|
||||
msgid "Remove the currently focused user alias."
|
||||
msgstr "Eine Zeichenkette im der momentan fokussierten Ansicht suchen..."
|
||||
msgstr "Den momentan gewählten Benutzer-Alias entfernen."
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:82
|
||||
#, fuzzy
|
||||
msgid "Are you sure you want to delete this user alias?"
|
||||
msgstr "Möchtest du diese Liste wirklich löschen?"
|
||||
msgstr "Möchtest du diesen Benutzer-Alias wirklich löschen?"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:82
|
||||
#, fuzzy
|
||||
msgid "Remove user alias"
|
||||
msgstr "Benutzer entfernen"
|
||||
msgstr "Benutzer-Alias entfernen"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:93
|
||||
#, fuzzy
|
||||
msgid "User alias"
|
||||
msgstr "Benutzerdetails"
|
||||
msgstr "Benutzer-Alias"
|
||||
|
||||
#: wxUI/dialogs/userSelection.py:10
|
||||
#, python-format
|
||||
@@ -3344,24 +3333,23 @@ msgstr "F&reunde"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/templateDialogs.py:8
|
||||
msgid "Edit Template"
|
||||
msgstr ""
|
||||
msgstr "Vorlage bearbeiten"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/templateDialogs.py:13
|
||||
msgid "Edit template"
|
||||
msgstr ""
|
||||
msgstr "Vorlage bearbeiten"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/templateDialogs.py:17
|
||||
#, fuzzy
|
||||
msgid "Available variables"
|
||||
msgstr "Nicht verfügbar"
|
||||
msgstr "Verfügbare Variablen"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/templateDialogs.py:29
|
||||
msgid "Restore template"
|
||||
msgstr ""
|
||||
msgstr "Vorlage wiederherstellen"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/templateDialogs.py:48
|
||||
msgid "Restored template to {}."
|
||||
msgstr ""
|
||||
msgstr "Vorlage zu {} wiederhergestellt."
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/templateDialogs.py:52
|
||||
msgid ""
|
||||
@@ -3370,11 +3358,14 @@ msgid ""
|
||||
"see a list of all available variables in the variables list while editing "
|
||||
"your template."
|
||||
msgstr ""
|
||||
"Die von dir spezifizierte Vorlage enthält Variablen, die für dieses Objekt "
|
||||
"nicht verfügbar sind. Bitte korrigiere die Vorlage und versuche es erneut. "
|
||||
"Eine Liste der verfügbaren Variablen wird im Bearbeitungsdialog für die "
|
||||
"Vorlage angezeigt."
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/templateDialogs.py:52
|
||||
#, fuzzy
|
||||
msgid "Invalid template"
|
||||
msgstr "Ungültige Tastenkombination"
|
||||
msgstr "Ungültige Vorlage"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:32
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:48
|
||||
@@ -3392,30 +3383,26 @@ msgstr "Typ"
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:39
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:175
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:259
|
||||
#, fuzzy
|
||||
msgid "Delete attachment"
|
||||
msgstr "Anhang entfernen"
|
||||
msgstr "Anhang löschen"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:44
|
||||
#, fuzzy
|
||||
msgid "Added Tweets"
|
||||
msgstr "Gesendete Tweets"
|
||||
msgstr "Hinzugefügte Tweets"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:51
|
||||
#, fuzzy
|
||||
msgid "Delete tweet"
|
||||
msgstr "Gesendete Tweets"
|
||||
msgstr "Tweet löschen"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:56
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:190
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:264
|
||||
msgid "A&dd..."
|
||||
msgstr ""
|
||||
msgstr "Hin&zufügen..."
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:58
|
||||
#, fuzzy
|
||||
msgid "Add t&weet"
|
||||
msgstr "Für einen Tweet \"gefällt mir\" abgeben"
|
||||
msgstr "T&weet hinzufügen"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:61
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:192
|
||||
@@ -3440,9 +3427,8 @@ msgstr "Rechtschreib&prüfung..."
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:69
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:200
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:272
|
||||
#, fuzzy
|
||||
msgid "&Translate"
|
||||
msgstr "Übersetzt"
|
||||
msgstr "Überse&tzen"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:73
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:204
|
||||
@@ -3454,31 +3440,29 @@ msgstr "Sen&den"
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:218
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:296
|
||||
msgid "Image"
|
||||
msgstr ""
|
||||
msgstr "Bild"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:119
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:220
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:298
|
||||
#, fuzzy
|
||||
msgid "Video"
|
||||
msgstr "Verbergen"
|
||||
msgstr "Video"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:121
|
||||
msgid "Poll"
|
||||
msgstr ""
|
||||
msgstr "Umfrage"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:126
|
||||
msgid "please provide a description"
|
||||
msgstr "Bitte gib eine Beschreibung ein"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:140
|
||||
#, fuzzy
|
||||
msgid "Select the video to be uploaded"
|
||||
msgstr "Wähle das hochzuladende Bild aus."
|
||||
msgstr "Wähle das hochzuladende Video aus."
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:140
|
||||
msgid "Video files (*.mp4)|*.mp4"
|
||||
msgstr ""
|
||||
msgstr "Videodateien (*.mp4)|*.mp4"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:146
|
||||
msgid ""
|
||||
@@ -3486,11 +3470,13 @@ msgid ""
|
||||
"complies with Twitter'S attachment rules. You can add only one video or GIF "
|
||||
"in every tweet, and a maximum of 4 photos."
|
||||
msgstr ""
|
||||
"Weitere Anhänge können nicht hinzugefügt werden. Bitte stelle sicher, dass "
|
||||
"dein Tweet den Twitter-Regeln für Anhänge entspricht. Du kannst nur ein "
|
||||
"Video oder Gif pro Tweet senden, und maximal 4 Fotos."
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:146
|
||||
#, fuzzy
|
||||
msgid "Error adding attachment"
|
||||
msgstr "Einen Anhang hinzufügen"
|
||||
msgstr "Fehler beim Hinzufügen des Anhangs"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:180
|
||||
msgid "&Mention to all"
|
||||
@@ -3528,9 +3514,8 @@ msgstr "Datum"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:362
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:435
|
||||
#, fuzzy
|
||||
msgid "Copy link to clipboard"
|
||||
msgstr "In Zwischenablage kopieren"
|
||||
msgstr "Link in Zwischenablage kopieren"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:365
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:440
|
||||
@@ -3556,37 +3541,38 @@ msgstr "URL &expandieren"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:477
|
||||
msgid "Add a poll"
|
||||
msgstr ""
|
||||
msgstr "Füge eine Umfrage hinzu"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:481
|
||||
msgid "Participation time (in days)"
|
||||
msgstr ""
|
||||
msgstr "Teilnahmezeitraum (in Tagen)"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:488
|
||||
msgid "Choices"
|
||||
msgstr ""
|
||||
msgstr "Wahlmöglichkeiten"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:492
|
||||
msgid "Option 1"
|
||||
msgstr ""
|
||||
msgstr "Option 1"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:499
|
||||
msgid "Option 2"
|
||||
msgstr ""
|
||||
msgstr "Option 2"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:506
|
||||
msgid "Option 3"
|
||||
msgstr ""
|
||||
msgstr "Option 3"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:513
|
||||
msgid "Option 4"
|
||||
msgstr ""
|
||||
msgstr "Option 4"
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:541
|
||||
msgid "Please make sure you have provided at least two options for the poll."
|
||||
msgstr ""
|
||||
"Bitte stelle sicher, dass mindestens zwei Optionen für die Umfrage angegeben "
|
||||
"wurden."
|
||||
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:541
|
||||
#, fuzzy
|
||||
msgid "Not enough information"
|
||||
msgstr "Information"
|
||||
msgstr "Nicht genügend Informationen"
|
||||
|
Binary file not shown.
@@ -1,107 +1,92 @@
|
||||
# Corentin Bacqué-Cazenave <corentin@progaccess.net>, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TW Blue 0.94\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"Report-Msgid-Bugs-To: manuel@manuelcortez.net\n"
|
||||
"POT-Creation-Date: 2022-08-16 17:50-0500\n"
|
||||
"PO-Revision-Date: 2022-08-18 15:32+0200\n"
|
||||
"PO-Revision-Date: 2022-08-29 17:03+0000\n"
|
||||
"Last-Translator: Corentin Bacqué-Cazenave <corentin@progaccess.net>\n"
|
||||
"Language-Team: Corentin Bacqué-Cazenave <corentin@progaccess.net>\n"
|
||||
"Language-Team: French <https://weblate.mcvsoftware.com/projects/twblue/"
|
||||
"twblue/fr/>\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Weblate 4.13.1\n"
|
||||
"Generated-By: Babel 2.10.3\n"
|
||||
"X-Generator: Poedit 3.1.1\n"
|
||||
|
||||
#: languageHandler.py:61
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Amharic"
|
||||
msgstr "Amharique"
|
||||
|
||||
#: languageHandler.py:62
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Aragonese"
|
||||
msgstr "Japonais"
|
||||
|
||||
#: languageHandler.py:63
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Spanish"
|
||||
msgstr "Espagnol"
|
||||
|
||||
#: languageHandler.py:64
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugais"
|
||||
|
||||
#: languageHandler.py:65
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Russian"
|
||||
msgstr "Russe"
|
||||
|
||||
#: languageHandler.py:66
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "italian"
|
||||
msgstr "Italien"
|
||||
|
||||
#: languageHandler.py:67
|
||||
#, fuzzy
|
||||
#| msgid "Turkish"
|
||||
msgctxt "languageName"
|
||||
msgid "Turkey"
|
||||
msgstr "Turc"
|
||||
|
||||
#: languageHandler.py:68
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Galician"
|
||||
msgstr "Galicien"
|
||||
|
||||
#: languageHandler.py:69
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Catala"
|
||||
msgstr "Catalan"
|
||||
|
||||
#: languageHandler.py:70
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Vasque"
|
||||
msgstr "Basque"
|
||||
|
||||
#: languageHandler.py:71
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "polish"
|
||||
msgstr "Polonais"
|
||||
|
||||
#: languageHandler.py:72
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Arabic"
|
||||
msgstr "Arabe"
|
||||
|
||||
#: languageHandler.py:73
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Nepali"
|
||||
msgstr "Népali"
|
||||
msgstr "Népalais"
|
||||
|
||||
#: languageHandler.py:74
|
||||
#, fuzzy
|
||||
#| msgid "Serbian"
|
||||
msgctxt "languageName"
|
||||
msgid "Serbian (Latin)"
|
||||
msgstr "Serbe"
|
||||
|
||||
#: languageHandler.py:75
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Japanese"
|
||||
msgstr "Japonais"
|
||||
@@ -205,7 +190,6 @@ msgstr "Favoris de {}"
|
||||
|
||||
# | msgid "Followers timelines"
|
||||
#: controller/mainController.py:364
|
||||
#, fuzzy
|
||||
msgid "Followers timelines"
|
||||
msgstr "Chronologies des abonnés"
|
||||
|
||||
@@ -602,7 +586,6 @@ msgid "Edit alias for {}"
|
||||
msgstr "Éditer l'alias pour {}"
|
||||
|
||||
#: controller/userSelector.py:10
|
||||
#, fuzzy
|
||||
msgid "Select user"
|
||||
msgstr "Sélectionnez l'utilisateur"
|
||||
|
||||
@@ -1106,19 +1089,16 @@ msgid "Autocomplete users' settings"
|
||||
msgstr "Paramètres pour la saisie automatique des utilisateurs"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:11
|
||||
#, fuzzy
|
||||
msgid "Add followers to database"
|
||||
msgstr "Ajouter l'utilisateur à la base de données"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:12
|
||||
#, fuzzy
|
||||
msgid "Add friends to database"
|
||||
msgstr "Ajouter l'utilisateur à la base de données"
|
||||
msgstr "Ajouter les abonnements à la base de données"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:26
|
||||
#, fuzzy
|
||||
msgid "Updating autocompletion database"
|
||||
msgstr "Gérer la base de données pour la saisie automatique"
|
||||
msgstr "Mise à jour de la base de données d'autocomplétion"
|
||||
|
||||
#: extra/autocompletionUsers/wx_scan.py:37
|
||||
msgid ""
|
||||
@@ -2044,7 +2024,6 @@ msgstr "Aucun Tweet trouvée avec cet ID"
|
||||
|
||||
# | msgid "Error {0}"
|
||||
#: sessions/twitter/utils.py:247
|
||||
#, fuzzy
|
||||
msgid "Error {0}"
|
||||
msgstr "Erreur {0}"
|
||||
|
||||
@@ -2757,9 +2736,8 @@ msgid "Password: "
|
||||
msgstr "Mot de passe: "
|
||||
|
||||
#: wxUI/dialogs/configuration.py:102
|
||||
#, fuzzy
|
||||
msgid "User autocompletion settings"
|
||||
msgstr "Paramètres pour la saisie automatique..."
|
||||
msgstr "Paramètres d'autocomplétion utilisateur"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:103
|
||||
msgid ""
|
||||
@@ -2770,9 +2748,8 @@ msgstr ""
|
||||
"données d'autocomplétion des utilisateurs."
|
||||
|
||||
#: wxUI/dialogs/configuration.py:104
|
||||
#, fuzzy
|
||||
msgid "Manage autocompletion database"
|
||||
msgstr "Gérer la base de données pour la saisie automatique"
|
||||
msgstr "Gérer la base de données d'autocomplétion"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:109
|
||||
msgid "Relative timestamps"
|
||||
@@ -2799,7 +2776,6 @@ msgid "Show screen names instead of full names"
|
||||
msgstr "Afficher les noms d'écran au lieu des noms complets"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:128
|
||||
#, fuzzy
|
||||
msgid "hide emojis in usernames"
|
||||
msgstr "Masquer les emojis dans les noms d'utilisateur"
|
||||
|
||||
@@ -3419,7 +3395,6 @@ msgstr "Type"
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:39
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:175
|
||||
#: wxUI/dialogs/twitterDialogs/tweetDialogs.py:259
|
||||
#, fuzzy
|
||||
msgid "Delete attachment"
|
||||
msgstr "Supprimer la pièce jointe"
|
||||
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -7,7 +7,7 @@ msgstr ""
|
||||
"Project-Id-Version: TWBlue 0.80\n"
|
||||
"Report-Msgid-Bugs-To: manuel@manuelcortez.net\n"
|
||||
"POT-Creation-Date: 2022-08-16 17:50-0500\n"
|
||||
"PO-Revision-Date: 2022-08-25 18:09+0000\n"
|
||||
"PO-Revision-Date: 2022-08-29 17:03+0000\n"
|
||||
"Last-Translator: zvonimir stanecic <zvonimirek222@yandex.com>\n"
|
||||
"Language-Team: Croatian <https://weblate.mcvsoftware.com/projects/twblue/"
|
||||
"twblue/hr/>\n"
|
||||
@@ -419,12 +419,10 @@ msgid "System default"
|
||||
msgstr "Korisnićki zadan"
|
||||
|
||||
#: controller/settings.py:77
|
||||
#, fuzzy
|
||||
msgid "HTTP"
|
||||
msgstr "HTTP"
|
||||
|
||||
#: controller/settings.py:77
|
||||
#, fuzzy
|
||||
msgid "SOCKS v4"
|
||||
msgstr "SOCKS v4"
|
||||
|
||||
@@ -433,7 +431,6 @@ msgid "SOCKS v4 with DNS support"
|
||||
msgstr "SOCKS v4 sa DNS podrškom"
|
||||
|
||||
#: controller/settings.py:77
|
||||
#, fuzzy
|
||||
msgid "SOCKS v5"
|
||||
msgstr "SOCKS v5"
|
||||
|
||||
@@ -1930,6 +1927,9 @@ msgid ""
|
||||
"and rebuilt automatically. If this error persists, send the error log to the "
|
||||
"{app} developers."
|
||||
msgstr ""
|
||||
"Dogogila se pogreška prilikom izgradnje {app} baze podataka. Ista će biti "
|
||||
"izbrisana i ponovno izgrađena. Ako se ova pogreška ponavlja, pošaljite "
|
||||
"zapisnik o pogrešci {app} programerima."
|
||||
|
||||
#: sessions/twitter/compose.py:25 sessions/twitter/compose.py:68
|
||||
#: sessions/twitter/compose.py:133 sessions/twitter/compose.py:142
|
||||
@@ -1982,32 +1982,34 @@ msgid "%s succeeded."
|
||||
msgstr "%s uspjelo."
|
||||
|
||||
#: sessions/twitter/session.py:452 sessions/twitter/session.py:535
|
||||
#, fuzzy
|
||||
msgid "Deleted account"
|
||||
msgstr "Novi nalog"
|
||||
msgstr "Izbrisani račun"
|
||||
|
||||
#: sessions/twitter/templates.py:16
|
||||
#, fuzzy
|
||||
msgid "$display_name, $text $image_descriptions $date. $source"
|
||||
msgstr ""
|
||||
msgstr "$display_name, $text $image_descriptions $date. $source"
|
||||
|
||||
#: sessions/twitter/templates.py:17
|
||||
#, fuzzy
|
||||
msgid "$sender_display_name, $text $date"
|
||||
msgstr ""
|
||||
msgstr "$sender_display_name, $text $date"
|
||||
|
||||
#: sessions/twitter/templates.py:18
|
||||
msgid "Dm to $recipient_display_name, $text $date"
|
||||
msgstr ""
|
||||
msgstr "Dm do $recipient_display_name, $text $date"
|
||||
|
||||
#: sessions/twitter/templates.py:19
|
||||
msgid ""
|
||||
"$display_name (@$screen_name). $followers followers, $following following, "
|
||||
"$tweets tweets. Joined Twitter $created_at."
|
||||
msgstr ""
|
||||
"$display_name (@$screen_name). $followers pratitelja, $following praćenih, $"
|
||||
"tweets tweetova. Pridružio se twitteru $created_at."
|
||||
|
||||
#: sessions/twitter/templates.py:54
|
||||
#, fuzzy
|
||||
msgid "Image description: {}."
|
||||
msgstr "opis slike"
|
||||
msgstr "Opis slike: {}."
|
||||
|
||||
#: sessions/twitter/utils.py:243
|
||||
msgid "Sorry, you are not authorised to see this status."
|
||||
@@ -2018,13 +2020,12 @@ msgid "No status found with that ID"
|
||||
msgstr "Nema pronađenog statusa sa tim identifikatorom"
|
||||
|
||||
#: sessions/twitter/utils.py:247
|
||||
#, fuzzy
|
||||
msgid "Error {0}"
|
||||
msgstr "Kod pogreške {0}"
|
||||
msgstr "pogreška {0}"
|
||||
|
||||
#: sessions/twitter/utils.py:274
|
||||
msgid "{user_1}, {user_2} and {all_users} more: {text}"
|
||||
msgstr ""
|
||||
msgstr "{user_1}, {user_2} i {all_users} više: {text}"
|
||||
|
||||
#: sessions/twitter/wxUI.py:7
|
||||
msgid "Authorising account..."
|
||||
@@ -2280,7 +2281,7 @@ msgstr "Ovaj filter već postoji. Koristite drugi naslov"
|
||||
|
||||
#: wxUI/commonMessageDialogs.py:95
|
||||
msgid "The configuration file is invalid."
|
||||
msgstr ""
|
||||
msgstr "Konfiguracijska datoteka je neispravna."
|
||||
|
||||
#: wxUI/commonMessageDialogs.py:98
|
||||
msgid ""
|
||||
@@ -2422,7 +2423,7 @@ msgstr "&Upravitelj listi"
|
||||
|
||||
#: wxUI/view.py:23
|
||||
msgid "Manage user aliases"
|
||||
msgstr ""
|
||||
msgstr "Upravljanje korisničkim aliasima"
|
||||
|
||||
#: wxUI/view.py:24
|
||||
msgid "&Edit keystrokes"
|
||||
@@ -2462,7 +2463,7 @@ msgstr "Izravna po&ruka"
|
||||
|
||||
#: wxUI/view.py:47
|
||||
msgid "Add a&lias"
|
||||
msgstr ""
|
||||
msgstr "dodaj a&lias"
|
||||
|
||||
#: wxUI/view.py:48
|
||||
msgid "&Add to list"
|
||||
@@ -2711,20 +2712,20 @@ msgid "Password: "
|
||||
msgstr "Lozinka"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:102
|
||||
#, fuzzy
|
||||
msgid "User autocompletion settings"
|
||||
msgstr "Postavke korisnika koji se samodovršuju"
|
||||
msgstr "Postavke korisnika koji se samodovršavaju"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:103
|
||||
msgid ""
|
||||
"Scan account and add friends and followers to the user autocompletion "
|
||||
"database"
|
||||
msgstr ""
|
||||
"Skeniraj račun i dodaj prijatelje i pratitelje u bazu podataka "
|
||||
"samodovršavanja"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:104
|
||||
#, fuzzy
|
||||
msgid "Manage autocompletion database"
|
||||
msgstr "upravljaj bazom podataka korisnika koji se samodovršavaju"
|
||||
msgstr "Upravljaj bazom podataka samodovršavanja"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:109
|
||||
msgid "Relative timestamps"
|
||||
@@ -2752,7 +2753,7 @@ msgstr "Prikazuj prikazana imena umjesto punih"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:128
|
||||
msgid "hide emojis in usernames"
|
||||
msgstr ""
|
||||
msgstr "Skrivaj emojie u korisničkim imenima"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:130
|
||||
msgid ""
|
||||
@@ -2767,6 +2768,8 @@ msgid ""
|
||||
"Load cache for tweets in memory (much faster in big datasets but requires "
|
||||
"more RAM)"
|
||||
msgstr ""
|
||||
"Učitaj predmemoriju tweetowa u memoriju (brže u velikim setovima podataka "
|
||||
"ali zahtjeva više radne memorije)"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:141
|
||||
msgid "Enable automatic speech feedback"
|
||||
@@ -2892,7 +2895,7 @@ msgstr "&Spremnik"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:406
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
msgstr "Predlošci"
|
||||
|
||||
#: wxUI/dialogs/configuration.py:410
|
||||
msgid "Sound"
|
||||
@@ -2986,11 +2989,11 @@ msgstr "Odustani"
|
||||
|
||||
#: wxUI/dialogs/filterDialogs.py:120
|
||||
msgid "You must define a name for the filter before creating it."
|
||||
msgstr ""
|
||||
msgstr "Prije stvaranja filtra, morate definirati njegov naziv."
|
||||
|
||||
#: wxUI/dialogs/filterDialogs.py:120
|
||||
msgid "Missing filter name"
|
||||
msgstr ""
|
||||
msgstr "Naziv filtra koji nedostaje"
|
||||
|
||||
#: wxUI/dialogs/filterDialogs.py:127
|
||||
msgid "Manage filters"
|
||||
@@ -3251,38 +3254,32 @@ msgid "&Ignore tweets from this client"
|
||||
msgstr "&Zanemari tweetove od ovog klijenta"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:18
|
||||
#, fuzzy
|
||||
msgid "Alias"
|
||||
msgstr "uvijek"
|
||||
msgstr "Alias"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:41
|
||||
#, fuzzy
|
||||
msgid "Edit user aliases"
|
||||
msgstr "Uređujem {0} bazu podataka korisnika"
|
||||
msgstr "Uredi aliase korisnika"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:48
|
||||
#, fuzzy
|
||||
msgid "Actions"
|
||||
msgstr "Akcija"
|
||||
msgstr "Radnje"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:50
|
||||
#, fuzzy
|
||||
msgid "Add alias"
|
||||
msgstr "Dodaj u popis"
|
||||
msgstr "Dodaj alias"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:51
|
||||
msgid "Adds a new user alias"
|
||||
msgstr ""
|
||||
msgstr "Dodaje novi korisnički alias"
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:54
|
||||
#, fuzzy
|
||||
msgid "Edit the currently focused user Alias."
|
||||
msgstr "Uđi u interakciju sa trenutnim tweetom"
|
||||
msgstr "Uredi trenutno fokusiran korisnički alias."
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:58
|
||||
#, fuzzy
|
||||
msgid "Remove the currently focused user alias."
|
||||
msgstr "Potraži niz znakova u trenutno fokusiranom spremniku..."
|
||||
msgstr "Ukloni trenutno fokusiran korisnički alias."
|
||||
|
||||
#: wxUI/dialogs/userAliasDialogs.py:82
|
||||
#, fuzzy
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,90 +1,80 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) 2022 ORGANIZATION
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
|
||||
#
|
||||
# Jonas S. Marques <jonasivle@gmail.com>, 2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TWBlue 0.80\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"Report-Msgid-Bugs-To: manuel@manuelcortez.net\n"
|
||||
"POT-Creation-Date: 2022-08-16 17:50-0500\n"
|
||||
"PO-Revision-Date: 2022-02-25 07:19-0300\n"
|
||||
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
|
||||
"Language-Team: Odenilton Júnior Santos <odeniltonjunior@gmail.com>\n"
|
||||
"Language: pt_BR\n"
|
||||
"PO-Revision-Date: 2022-09-04 00:02+0000\n"
|
||||
"Last-Translator: Jonas S. Marques <jonasivle@gmail.com>\n"
|
||||
"Language-Team: Portuguese <https://weblate.mcvsoftware.com/projects/twblue/"
|
||||
"twblue/pt/>\n"
|
||||
"Language: pt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 4.13.1\n"
|
||||
"Generated-By: Babel 2.10.3\n"
|
||||
|
||||
#: languageHandler.py:61
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Amharic"
|
||||
msgstr "Dari"
|
||||
|
||||
#: languageHandler.py:62
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Aragonese"
|
||||
msgstr "Japonês"
|
||||
|
||||
#: languageHandler.py:63
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Spanish"
|
||||
msgstr "Espanhol"
|
||||
|
||||
#: languageHandler.py:64
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Portuguese"
|
||||
msgstr "Português"
|
||||
|
||||
#: languageHandler.py:65
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Russian"
|
||||
msgstr "Russo"
|
||||
|
||||
#: languageHandler.py:66
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "italian"
|
||||
msgstr "Italiano"
|
||||
|
||||
#: languageHandler.py:67
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Turkey"
|
||||
msgstr "recurso"
|
||||
msgstr "turco"
|
||||
|
||||
#: languageHandler.py:68
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Galician"
|
||||
msgstr "Galego"
|
||||
|
||||
#: languageHandler.py:69
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Catala"
|
||||
msgstr "Catalão"
|
||||
|
||||
#: languageHandler.py:70
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Vasque"
|
||||
msgstr "Basco"
|
||||
|
||||
#: languageHandler.py:71
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "polish"
|
||||
msgstr "Polonês"
|
||||
|
||||
#: languageHandler.py:72
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Arabic"
|
||||
msgstr "Árabe"
|
||||
@@ -93,15 +83,14 @@ msgstr "Árabe"
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Nepali"
|
||||
msgstr "Nepali"
|
||||
msgstr "Nepalês"
|
||||
|
||||
#: languageHandler.py:74
|
||||
msgctxt "languageName"
|
||||
msgid "Serbian (Latin)"
|
||||
msgstr ""
|
||||
msgstr "Sérvio (Latin)"
|
||||
|
||||
#: languageHandler.py:75
|
||||
#, fuzzy
|
||||
msgctxt "languageName"
|
||||
msgid "Japanese"
|
||||
msgstr "Japonês"
|
||||
@@ -172,9 +161,8 @@ msgid "Followers"
|
||||
msgstr "Seguidores"
|
||||
|
||||
#: controller/mainController.py:351
|
||||
#, fuzzy
|
||||
msgid "Following"
|
||||
msgstr "&Deixar de seguir"
|
||||
msgstr "Seguindo"
|
||||
|
||||
#: controller/buffers/twitter/base.py:70 controller/mainController.py:353
|
||||
#: controller/mainController.py:1399 controller/settings.py:346
|
||||
@@ -205,7 +193,6 @@ msgid "Likes for {}"
|
||||
msgstr "Curtidas de {}"
|
||||
|
||||
#: controller/mainController.py:364
|
||||
#, fuzzy
|
||||
msgid "Followers timelines"
|
||||
msgstr "Linhas do tempo de Seguidores"
|
||||
|
||||
@@ -215,14 +202,13 @@ msgid "Followers for {}"
|
||||
msgstr "Seguidores de {}"
|
||||
|
||||
#: controller/mainController.py:368
|
||||
#, fuzzy
|
||||
msgid "Following timelines"
|
||||
msgstr "Linhas do tempo de Seguidores"
|
||||
msgstr "Linhas do tempo de amigos"
|
||||
|
||||
#: controller/mainController.py:371 controller/mainController.py:934
|
||||
#: controller/mainController.py:1582
|
||||
msgid "Friends for {}"
|
||||
msgstr "Seguidos por {}"
|
||||
msgstr "Pessoas que {} segue"
|
||||
|
||||
#: controller/mainController.py:372 wxUI/dialogs/lists.py:13
|
||||
msgid "Lists"
|
||||
@@ -271,15 +257,15 @@ msgstr "Filtros não são suportados neste exibidor"
|
||||
|
||||
#: controller/mainController.py:747
|
||||
msgid "Add an user alias"
|
||||
msgstr ""
|
||||
msgstr "Adicionar um atalho de usuário"
|
||||
|
||||
#: controller/mainController.py:755
|
||||
msgid "Alias has been set correctly for {}."
|
||||
msgstr ""
|
||||
msgstr "Sucesso ao definir um atalho para {}"
|
||||
|
||||
#: controller/mainController.py:823 controller/messages.py:328
|
||||
msgid "MMM D, YYYY. H:m"
|
||||
msgstr "dddd, D MMMM, YYYY. H:m:s"
|
||||
msgstr "MMM D, YYYY. H:m"
|
||||
|
||||
#: controller/mainController.py:951
|
||||
msgid "Conversation with {0}"
|
||||
@@ -295,7 +281,7 @@ msgstr "Erro ao decodificar coordenadas. Tente novamente mais tarde."
|
||||
|
||||
#: controller/mainController.py:998
|
||||
msgid "Unable to find address in OpenStreetMap."
|
||||
msgstr ""
|
||||
msgstr "Não foi possível encontrar este endereço nos mapas do OpenStreet."
|
||||
|
||||
#: controller/mainController.py:1011
|
||||
msgid "There are no results for the coordinates in this tweet"
|
||||
@@ -422,37 +408,35 @@ msgstr "Tweet"
|
||||
|
||||
#: controller/messages.py:355
|
||||
msgid "View item"
|
||||
msgstr "Ver item"
|
||||
msgstr "Visualizar item"
|
||||
|
||||
#: controller/messages.py:381
|
||||
#, fuzzy
|
||||
msgid "Link copied to clipboard."
|
||||
msgstr "Copiar para área de transferência"
|
||||
|
||||
#: controller/settings.py:77
|
||||
#, fuzzy
|
||||
msgid "System default"
|
||||
msgstr "Padrão do usuário"
|
||||
msgstr "Padrão do sistema"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "HTTP"
|
||||
msgstr ""
|
||||
msgstr "http"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v4"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v4"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v4 with DNS support"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v4 Com suporte à dns)"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v5"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v5"
|
||||
|
||||
#: controller/settings.py:77
|
||||
msgid "SOCKS v5 with DNS support"
|
||||
msgstr ""
|
||||
msgstr "SOCKS v5 (Com suporte à dns)"
|
||||
|
||||
#: controller/settings.py:155 controller/settings.py:269
|
||||
#: wxUI/dialogs/configuration.py:121
|
||||
@@ -475,19 +459,19 @@ msgstr "Configurações de conta para %s"
|
||||
|
||||
#: controller/settings.py:213 wxUI/dialogs/configuration.py:247
|
||||
msgid "Edit template for tweets. Current template: {}"
|
||||
msgstr ""
|
||||
msgstr "Editar modelo de tweets: Modelo atual {}"
|
||||
|
||||
#: controller/settings.py:222 wxUI/dialogs/configuration.py:249
|
||||
msgid "Edit template for direct messages. Current template: {}"
|
||||
msgstr ""
|
||||
msgstr "Editar modelo de mensagens diretas. Modelo atual {}"
|
||||
|
||||
#: controller/settings.py:231 wxUI/dialogs/configuration.py:251
|
||||
msgid "Edit template for sent direct messages. Current template: {}"
|
||||
msgstr ""
|
||||
msgstr "Editar modelo para mensagens diretas enviadas. Modelo atual {}"
|
||||
|
||||
#: controller/settings.py:240 wxUI/dialogs/configuration.py:253
|
||||
msgid "Edit template for persons. Current template: {}"
|
||||
msgstr ""
|
||||
msgstr "Editar modelos para pessoas. Modelo atual {}"
|
||||
|
||||
#: controller/settings.py:340
|
||||
msgid "Direct Messages"
|
||||
@@ -561,7 +545,7 @@ msgstr "Protegido: %s\n"
|
||||
|
||||
#: controller/user.py:110
|
||||
msgid "Relationship: "
|
||||
msgstr ""
|
||||
msgstr "Relação entre vocês:· "
|
||||
|
||||
#: controller/user.py:112
|
||||
msgid "You follow {0}. "
|
||||
@@ -600,12 +584,10 @@ msgid "You can't ignore direct messages"
|
||||
msgstr "Você não pode ignorar mensagens diretas"
|
||||
|
||||
#: controller/userAliasController.py:31
|
||||
#, fuzzy
|
||||
msgid "Edit alias for {}"
|
||||
msgstr "Lista de {}"
|
||||
msgstr "Editar atalho de {}"
|
||||
|
||||
#: controller/userSelector.py:10
|
||||
#, fuzzy
|
||||
msgid "Select user"
|
||||
msgstr "Selecione o usuário"
|
||||
|
||||
@@ -615,11 +597,11 @@ msgstr "Esta ação não é suportada neste exibidor"
|
||||
|
||||
#: controller/buffers/twitter/base.py:76
|
||||
msgid "{username}'s timeline"
|
||||
msgstr "Abrir linha do tempo do usuário"
|
||||
msgstr "Linha do tempo de {username}'s"
|
||||
|
||||
#: controller/buffers/twitter/base.py:78
|
||||
msgid "{username}'s likes"
|
||||
msgstr "Curtidas dos {username}'s "
|
||||
msgstr "curtidas de {username}'s"
|
||||
|
||||
#: controller/buffers/twitter/base.py:80
|
||||
msgid "{username}'s followers"
|
||||
@@ -627,7 +609,7 @@ msgstr "{username}'s seguidores"
|
||||
|
||||
#: controller/buffers/twitter/base.py:82
|
||||
msgid "{username}'s friends"
|
||||
msgstr "{nome de usuário dos amigos}"
|
||||
msgstr "Amigos de {username}'s"
|
||||
|
||||
#: controller/buffers/twitter/base.py:84
|
||||
msgid "Unknown buffer"
|
||||
|
89
src/main.py
89
src/main.py
@@ -1,22 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import platform
|
||||
from win32com.client import GetObject
|
||||
|
||||
""" there are lots of things not implemented for Gtk+ yet.
|
||||
We've started this effort 1 Apr 2015 so it isn't fully functional. We will remove the ifs statements when are no needed"""
|
||||
|
||||
system = platform.system()
|
||||
if system == "Windows":
|
||||
import sys
|
||||
import os
|
||||
#redirect the original stdout and stderr
|
||||
stdout=sys.stdout
|
||||
stderr=sys.stderr
|
||||
sys.stdout = open(os.path.join(os.getenv("temp"), "stdout.log"), "w")
|
||||
sys.stderr = open(os.path.join(os.getenv("temp"), "stderr.log"), "w")
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
#redirect the original stdout and stderr
|
||||
stdout=sys.stdout
|
||||
stderr=sys.stderr
|
||||
sys.stdout = open(os.path.join(os.getenv("temp"), "stdout.log"), "w")
|
||||
sys.stderr = open(os.path.join(os.getenv("temp"), "stderr.log"), "w")
|
||||
import languageHandler
|
||||
import paths
|
||||
#check if TWBlue is installed (Windows only)
|
||||
#check if TWBlue is installed
|
||||
# ToDo: Remove this soon as this is done already when importing the paths module.
|
||||
if os.path.exists(os.path.join(paths.app_path(), "Uninstall.exe")):
|
||||
paths.mode="installed"
|
||||
@@ -31,34 +25,30 @@ import fixes
|
||||
import widgetUtils
|
||||
import webbrowser
|
||||
from wxUI import commonMessageDialogs
|
||||
if system == "Windows":
|
||||
from logger import logger
|
||||
from update import updater
|
||||
stdout_temp=sys.stdout
|
||||
stderr_temp=sys.stderr
|
||||
from logger import logger
|
||||
from update import updater
|
||||
stdout_temp=sys.stdout
|
||||
stderr_temp=sys.stderr
|
||||
#if it's a binary version
|
||||
if hasattr(sys, 'frozen'):
|
||||
sys.stderr = open(os.path.join(paths.logs_path(), "stderr.log"), 'w')
|
||||
sys.stdout = open(os.path.join(paths.logs_path(), "stdout.log"), 'w')
|
||||
else:
|
||||
sys.stdout=stdout
|
||||
sys.stderr=stderr
|
||||
# We are running from source, let's prepare vlc module for that situation
|
||||
if system=="Windows":
|
||||
arch="x86"
|
||||
if platform.architecture()[0][:2] == "64":
|
||||
arch="x64"
|
||||
os.environ['PYTHON_VLC_MODULE_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", arch))
|
||||
os.environ['PYTHON_VLC_LIB_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", arch, "libvlc.dll"))
|
||||
#the final log files have been opened succesfully, let's close the temporary files
|
||||
stdout_temp.close()
|
||||
stderr_temp.close()
|
||||
#finally, remove the temporary files. TW Blue doesn't need them anymore, and we will get more free space on the harddrive
|
||||
os.remove(stdout_temp.name)
|
||||
os.remove(stderr_temp.name)
|
||||
if hasattr(sys, 'frozen'):
|
||||
sys.stderr = open(os.path.join(paths.logs_path(), "stderr.log"), 'w')
|
||||
sys.stdout = open(os.path.join(paths.logs_path(), "stdout.log"), 'w')
|
||||
else:
|
||||
sys.stdout=stdout
|
||||
sys.stderr=stderr
|
||||
# We are running from source, let's prepare vlc module for that situation
|
||||
arch="x86"
|
||||
if platform.architecture()[0][:2] == "64":
|
||||
arch="x64"
|
||||
os.environ['PYTHON_VLC_MODULE_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", arch))
|
||||
os.environ['PYTHON_VLC_LIB_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", arch, "libvlc.dll"))
|
||||
#the final log files have been opened succesfully, let's close the temporary files
|
||||
stdout_temp.close()
|
||||
stderr_temp.close()
|
||||
#finally, remove the temporary files. TW Blue doesn't need them anymore, and we will get more free space on the harddrive
|
||||
os.remove(stdout_temp.name)
|
||||
os.remove(stderr_temp.name)
|
||||
import sound
|
||||
if system == "Linux":
|
||||
from gi.repository import Gdk, GObject, GLib
|
||||
|
||||
log = logging.getLogger("main")
|
||||
|
||||
@@ -79,14 +69,14 @@ def setup():
|
||||
from sessionmanager import sessionManager
|
||||
app = widgetUtils.mainLoopObject()
|
||||
check_pid()
|
||||
if system == "Windows":
|
||||
if config.app["app-settings"]["donation_dialog_displayed"] == False:
|
||||
donation()
|
||||
if config.app['app-settings']['check_for_updates']:
|
||||
updater.do_update()
|
||||
if config.app["app-settings"]["donation_dialog_displayed"] == False:
|
||||
donation()
|
||||
if config.app['app-settings']['check_for_updates']:
|
||||
updater.do_update()
|
||||
sm = sessionManager.sessionManagerController()
|
||||
sm.fill_list()
|
||||
if len(sm.sessions) == 0: sm.show()
|
||||
if len(sm.sessions) == 0:
|
||||
sm.show()
|
||||
else:
|
||||
sm.do_ok()
|
||||
if hasattr(sm.view, "destroy"):
|
||||
@@ -96,10 +86,7 @@ def setup():
|
||||
r.view.show()
|
||||
r.do_work()
|
||||
r.check_invisible_at_startup()
|
||||
if system == "Windows":
|
||||
call_threaded(r.start)
|
||||
elif system == "Linux":
|
||||
GLib.idle_add(r.start)
|
||||
call_threaded(r.start)
|
||||
app.run()
|
||||
|
||||
def proxy_setup():
|
||||
|
53
src/mastodon.defaults
Normal file
53
src/mastodon.defaults
Normal file
@@ -0,0 +1,53 @@
|
||||
[mastodon]
|
||||
access_token = string(default="")
|
||||
instance = string(default="")
|
||||
user_name = string(default="")
|
||||
ignored_clients = list(default=list())
|
||||
|
||||
[general]
|
||||
relative_times = boolean(default=True)
|
||||
max_posts_per_call = integer(default=40)
|
||||
reverse_timelines = boolean(default=False)
|
||||
persist_size = integer(default=0)
|
||||
load_cache_in_memory=boolean(default=True)
|
||||
show_screen_names = boolean(default=False)
|
||||
buffer_order = list(default=list('home', 'local', 'mentions', 'direct_messages', 'sent', 'favorites', 'bookmarks', 'followers', 'following', 'blocked', 'muted', 'notifications'))
|
||||
boost_mode = string(default="ask")
|
||||
|
||||
[sound]
|
||||
volume = float(default=1.0)
|
||||
input_device = string(default="Default")
|
||||
output_device = string(default="Default")
|
||||
session_mute = boolean(default=False)
|
||||
current_soundpack = string(default="FreakyBlue")
|
||||
indicate_audio = boolean(default=True)
|
||||
indicate_img = boolean(default=True)
|
||||
|
||||
[other_buffers]
|
||||
timelines = list(default=list())
|
||||
searches = list(default=list())
|
||||
lists = list(default=list())
|
||||
followers_timelines = list(default=list())
|
||||
following_timelines = list(default=list())
|
||||
trending_topic_buffers = list(default=list())
|
||||
muted_buffers = list(default=list())
|
||||
autoread_buffers = list(default=list(mentions, direct_messages, events))
|
||||
|
||||
[mysc]
|
||||
spelling_language = string(default="")
|
||||
save_followers_in_autocompletion_db = boolean(default=False)
|
||||
save_friends_in_autocompletion_db = boolean(default=False)
|
||||
ocr_language = string(default="")
|
||||
|
||||
[reporting]
|
||||
braille_reporting = boolean(default=True)
|
||||
speech_reporting = boolean(default=True)
|
||||
|
||||
[templates]
|
||||
post = string(default="$display_name, $safe_text $image_descriptions $date. $visibility. $source")
|
||||
person = string(default="$display_name (@$screen_name). $followers followers, $following following, $posts posts. Joined $created_at.")
|
||||
conversation = string(default="Conversation with $users. Last message: $last_post")
|
||||
|
||||
[filters]
|
||||
|
||||
[user-aliases]
|
@@ -1,18 +0,0 @@
|
||||
# -*- 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)
|
@@ -7,4 +7,3 @@ Contents of this package:
|
||||
manager: Handles multiple sessions, setting the configuration files and check if the session is valid. Part of the model.
|
||||
session: Creates a twitter session for an user. The other part of the model.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
@@ -1,57 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
from gi.repository import Gtk
|
||||
import widgetUtils
|
||||
|
||||
class sessionManagerWindow(widgetUtils.baseDialog):
|
||||
def __init__(self):
|
||||
super(sessionManagerWindow, self).__init__("Session Manager", None, 0, (Gtk.STOCK_OK, widgetUtils.OK, Gtk.STOCK_CANCEL, widgetUtils.CANCEL))
|
||||
self.list = widgetUtils.list("Session")
|
||||
self.box.add(self.list.list)
|
||||
btnBox = Gtk.Box(spacing=6)
|
||||
self.new = Gtk.Button("New account")
|
||||
self.remove = Gtk.Button("Remove account")
|
||||
self.configuration = Gtk.Button("Configuration")
|
||||
btnBox.add(self.new)
|
||||
btnBox.add(self.remove)
|
||||
btnBox.add(self.configuration)
|
||||
self.box.add(btnBox)
|
||||
self.show_all()
|
||||
|
||||
def fill_list(self, sessionsList):
|
||||
for i in sessionsList:
|
||||
self.list.insert_item(False, i)
|
||||
if self.list.get_count() > 0:
|
||||
self.list.select_item(0)
|
||||
|
||||
def new_account_dialog(self):
|
||||
dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Authorization")
|
||||
dialog.format_secondary_text("The request to authorize your Twitter account will be opened in your browser. You only need to do this once. Would you like to continue?")
|
||||
answer = dialog.run()
|
||||
dialog.destroy()
|
||||
return answer
|
||||
|
||||
def add_new_session_to_list(self):
|
||||
total = self.list.get_count()
|
||||
name = "Authorized account %d" % (total+1)
|
||||
self.list.insert_item(name)
|
||||
if self.list.get_count() == 1:
|
||||
self.list.select_item(0)
|
||||
|
||||
def show_unauthorised_error(self):
|
||||
dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CANCEL, "Invalid user token")
|
||||
dialog.format_secondary_text("Your access token is invalid or the authorization has failed. Please try again.")
|
||||
answer = dialog.run()
|
||||
return answer
|
||||
|
||||
def remove_account_dialog(self):
|
||||
dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Remove account")
|
||||
dialog.format_secondary_text("Do you really want delete this account?")
|
||||
answer = dialog.run()
|
||||
return answer
|
||||
|
||||
def get_selected(self):
|
||||
return self.list.get_selected()
|
||||
|
||||
def remove_session(self, sessionID):
|
||||
self.list.remove_item(sessionID)
|
||||
|
@@ -1,42 +1,25 @@
|
||||
# -*- coding: cp1252 -*-
|
||||
#from config_utils import Configuration, ConfigurationResetException
|
||||
from __future__ import unicode_literals
|
||||
from builtins import object
|
||||
import config
|
||||
import paths
|
||||
""" Lightweigth module that saves session position across global config and performs validation of config files. """
|
||||
import os
|
||||
import logging
|
||||
import config
|
||||
import paths
|
||||
log = logging.getLogger("sessionmanager.manager")
|
||||
from sessions import session_exceptions
|
||||
|
||||
manager = None
|
||||
def setup():
|
||||
""" Creates the singleton instance used within TWBlue to access this object. """
|
||||
global manager
|
||||
if not manager:
|
||||
manager = sessionManager()
|
||||
|
||||
class sessionManager(object):
|
||||
# def __init__(self):
|
||||
# FILE = "sessions.conf"
|
||||
# SPEC = "app-configuration.defaults"
|
||||
# try:
|
||||
# self.main = Configuration(paths.config_path(FILE), paths.app_path(SPEC))
|
||||
# except ConfigurationResetException:
|
||||
# pass
|
||||
|
||||
def get_current_session(self):
|
||||
""" Returns the currently focused session, if valid. """
|
||||
if self.is_valid(config.app["sessions"]["current_session"]):
|
||||
return config.app["sessions"]["current_session"]
|
||||
else:
|
||||
return False
|
||||
|
||||
def add_session(self, id):
|
||||
log.debug("Adding a new session: %s" % (id,))
|
||||
path = os.path.join(paths.config_path(), id)
|
||||
if not os.path.exists(path):
|
||||
log.debug("Creating %s path" % (os.path.join(paths.config_path(), path),))
|
||||
os.mkdir(path)
|
||||
config.app["sessions"]["sessions"].append(id)
|
||||
|
||||
def set_current_session(self, sessionID):
|
||||
config.app["sessions"]["current_session"] = sessionID
|
||||
|
@@ -1,42 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import shutil
|
||||
import widgetUtils
|
||||
import platform
|
||||
import output
|
||||
if platform.system() == "Windows":
|
||||
from . import wxUI as view
|
||||
from controller import settings
|
||||
elif platform.system() == "Linux":
|
||||
from . import gtkUI as view
|
||||
import paths
|
||||
""" Module to perform session actions such as addition, removal or display of the global settings dialogue. """
|
||||
import time
|
||||
import os
|
||||
import logging
|
||||
import widgetUtils
|
||||
import sessions
|
||||
from sessions.twitter import session
|
||||
from . import manager
|
||||
import output
|
||||
import paths
|
||||
import config_utils
|
||||
import config
|
||||
from pubsub import pub
|
||||
from tweepy.errors import TweepyException
|
||||
from controller import settings
|
||||
from sessions.twitter import session as TwitterSession
|
||||
from sessions.mastodon import session as MastodonSession
|
||||
from . import manager
|
||||
from . import wxUI as view
|
||||
|
||||
log = logging.getLogger("sessionmanager.sessionManager")
|
||||
|
||||
class sessionManagerController(object):
|
||||
def __init__(self, started=False):
|
||||
def __init__(self, started: bool = False):
|
||||
""" Class constructor.
|
||||
|
||||
Creates the SessionManager class controller, responsible for the accounts within TWBlue. From this dialog, users can add/Remove accounts, or load the global settings dialog.
|
||||
|
||||
:param started: Indicates whether this object is being created during application startup (when no other controller has been instantiated) or not. It is important for us to know this, as we won't show the button to open global settings dialog if the application has been started. Users must choose the corresponding option in the menu bar.
|
||||
:type started: bool
|
||||
"""
|
||||
super(sessionManagerController, self).__init__()
|
||||
log.debug("Setting up the session manager.")
|
||||
self.started = started
|
||||
# Initialize the manager, responsible for storing session objects.
|
||||
manager.setup()
|
||||
self.view = view.sessionManagerWindow()
|
||||
widgetUtils.connect_event(self.view.new, widgetUtils.BUTTON_PRESSED, self.manage_new_account)
|
||||
widgetUtils.connect_event(self.view.remove, widgetUtils.BUTTON_PRESSED, self.remove)
|
||||
pub.subscribe(self.manage_new_account, "sessionmanager.new_account")
|
||||
pub.subscribe(self.remove_account, "sessionmanager.remove_account")
|
||||
if self.started == False:
|
||||
widgetUtils.connect_event(self.view.configuration, widgetUtils.BUTTON_PRESSED, self.configuration)
|
||||
pub.subscribe(self.configuration, "sessionmanager.configuration")
|
||||
else:
|
||||
self.view.hide_configuration()
|
||||
# Store a temporary copy of new and removed sessions, so we will perform actions on them during call to on_ok.
|
||||
self.new_sessions = {}
|
||||
self.removed_sessions = []
|
||||
|
||||
def fill_list(self):
|
||||
""" Fills the session list with all valid sessions that could be found in config path. """
|
||||
sessionsList = []
|
||||
reserved_dirs = ["dicts"]
|
||||
log.debug("Filling the sessions list.")
|
||||
@@ -55,10 +64,16 @@ class sessionManagerController(object):
|
||||
output.speak("An exception was raised while attempting to clean malformed session data. See the error log for details. If this message persists, contact the developers.",True)
|
||||
os.exception("Exception thrown while removing malformed session")
|
||||
continue
|
||||
name = config_test["twitter"]["user_name"]
|
||||
if config_test["twitter"]["user_key"] != "" and config_test["twitter"]["user_secret"] != "":
|
||||
sessionsList.append(name)
|
||||
self.sessions.append(i)
|
||||
if config_test.get("twitter") != None:
|
||||
name = _("{account_name} (Twitter)").format(account_name=config_test["twitter"]["user_name"])
|
||||
if config_test["twitter"]["user_key"] != "" and config_test["twitter"]["user_secret"] != "":
|
||||
sessionsList.append(name)
|
||||
self.sessions.append(dict(type="twitter", id=i))
|
||||
elif config_test.get("mastodon") != None:
|
||||
name = _("{account_name} (Mastodon)").format(account_name=config_test["mastodon"]["user_name"])
|
||||
if config_test["mastodon"]["instance"] != "" and config_test["mastodon"]["access_token"] != "":
|
||||
sessionsList.append(name)
|
||||
self.sessions.append(dict(type="mastodon", id=i))
|
||||
else:
|
||||
try:
|
||||
log.debug("Deleting session %s" % (i,))
|
||||
@@ -69,6 +84,7 @@ class sessionManagerController(object):
|
||||
self.view.fill_list(sessionsList)
|
||||
|
||||
def show(self):
|
||||
""" Displays the session manager dialog. """
|
||||
if self.view.get_response() == widgetUtils.OK:
|
||||
self.do_ok()
|
||||
# else:
|
||||
@@ -77,49 +93,49 @@ class sessionManagerController(object):
|
||||
def do_ok(self):
|
||||
log.debug("Starting sessions...")
|
||||
for i in self.sessions:
|
||||
if (i in sessions.sessions) == True: continue
|
||||
s = session.Session(i)
|
||||
# Skip already created sessions. Useful when session manager controller is not created during startup.
|
||||
if sessions.sessions.get(i.get("id")) != None:
|
||||
continue
|
||||
# Create the session object based in session type.
|
||||
if i.get("type") == "twitter":
|
||||
s = TwitterSession.Session(i.get("id"))
|
||||
elif i.get("type") == "mastodon":
|
||||
s = MastodonSession.Session(i.get("id"))
|
||||
s.get_configuration()
|
||||
if i not in config.app["sessions"]["ignored_sessions"]:
|
||||
if i.get("id") not in config.app["sessions"]["ignored_sessions"]:
|
||||
try:
|
||||
s.login()
|
||||
except TweepyException:
|
||||
self.show_auth_error(s.settings["twitter"]["user_name"])
|
||||
continue
|
||||
sessions.sessions[i] = s
|
||||
self.new_sessions[i] = s
|
||||
sessions.sessions[i.get("id")] = s
|
||||
self.new_sessions[i.get("id")] = s
|
||||
# self.view.destroy()
|
||||
|
||||
def show_auth_error(self, user_name):
|
||||
error = view.auth_error(user_name)
|
||||
|
||||
def manage_new_account(self, *args, **kwargs):
|
||||
if self.view.new_account_dialog() == widgetUtils.YES:
|
||||
location = (str(time.time())[-6:])
|
||||
log.debug("Creating session in the %s path" % (location,))
|
||||
s = session.Session(location)
|
||||
manager.manager.add_session(location)
|
||||
s.get_configuration()
|
||||
# try:
|
||||
s.authorise()
|
||||
self.sessions.append(location)
|
||||
def manage_new_account(self, type):
|
||||
# Generic settings for all account types.
|
||||
location = (str(time.time())[-6:])
|
||||
log.debug("Creating %s session in the %s path" % (type, location))
|
||||
if type == "twitter":
|
||||
s = TwitterSession.Session(location)
|
||||
elif type == "mastodon":
|
||||
s = MastodonSession.Session(location)
|
||||
result = s.authorise()
|
||||
if result == True:
|
||||
self.sessions.append(dict(id=location, type=type))
|
||||
self.view.add_new_session_to_list()
|
||||
s.settings.write()
|
||||
# except:
|
||||
# log.exception("Error authorising the session")
|
||||
# self.view.show_unauthorised_error()
|
||||
# return
|
||||
|
||||
def remove(self, *args, **kwargs):
|
||||
if self.view.remove_account_dialog() == widgetUtils.YES:
|
||||
selected_account = self.sessions[self.view.get_selected()]
|
||||
self.view.remove_session(self.view.get_selected())
|
||||
self.removed_sessions.append(selected_account)
|
||||
self.sessions.remove(selected_account)
|
||||
shutil.rmtree(path=os.path.join(paths.config_path(), selected_account), ignore_errors=True)
|
||||
def remove_account(self, index):
|
||||
selected_account = self.sessions[index]
|
||||
self.view.remove_session(index)
|
||||
self.removed_sessions.append(selected_account.get("id"))
|
||||
self.sessions.remove(selected_account)
|
||||
shutil.rmtree(path=os.path.join(paths.config_path(), selected_account.get("id")), ignore_errors=True)
|
||||
|
||||
|
||||
def configuration(self, *args, **kwargs):
|
||||
def configuration(self):
|
||||
""" Opens the global settings dialogue."""
|
||||
d = settings.globalSettingsController()
|
||||
if d.response == widgetUtils.OK:
|
||||
|
@@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
""" Base GUI (Wx) class for the Session manager module."""
|
||||
import wx
|
||||
from pubsub import pub
|
||||
from multiplatform_widgets import widgets
|
||||
import application
|
||||
|
||||
class sessionManagerWindow(wx.Dialog):
|
||||
""" Dialog that displays all session managing capabilities to users. """
|
||||
def __init__(self):
|
||||
super(sessionManagerWindow, self).__init__(parent=None, title=_(u"Session manager"), size=wx.DefaultSize)
|
||||
panel = wx.Panel(self)
|
||||
@@ -16,8 +17,11 @@ class sessionManagerWindow(wx.Dialog):
|
||||
listSizer.Add(self.list.list, 0, wx.ALL, 5)
|
||||
sizer.Add(listSizer, 0, wx.ALL, 5)
|
||||
self.new = wx.Button(panel, -1, _(u"New account"), size=wx.DefaultSize)
|
||||
self.new.Bind(wx.EVT_BUTTON, self.on_new_account)
|
||||
self.remove = wx.Button(panel, -1, _(u"Remove account"))
|
||||
self.remove.Bind(wx.EVT_BUTTON, self.on_remove)
|
||||
self.configuration = wx.Button(panel, -1, _(u"Global Settings"))
|
||||
self.configuration.Bind(wx.EVT_BUTTON, self.on_configuration)
|
||||
ok = wx.Button(panel, wx.ID_OK, size=wx.DefaultSize)
|
||||
ok.SetDefault()
|
||||
cancel = wx.Button(panel, wx.ID_CANCEL, size=wx.DefaultSize)
|
||||
@@ -42,11 +46,29 @@ class sessionManagerWindow(wx.Dialog):
|
||||
if self.list.get_count() == 0:
|
||||
wx.MessageDialog(None, _(u"You need to configure an account."), _(u"Account Error"), wx.ICON_ERROR).ShowModal()
|
||||
return
|
||||
self.controller.do_ok()
|
||||
self.EndModal(wx.ID_OK)
|
||||
|
||||
def new_account_dialog(self):
|
||||
return wx.MessageDialog(self, _(u"The request to authorize your Twitter account will be opened in your browser. You only need to do this once. Would you like to continue?"), _(u"Authorization"), wx.YES_NO).ShowModal()
|
||||
def on_new_account(self, *args, **kwargs):
|
||||
menu = wx.Menu()
|
||||
twitter = menu.Append(wx.ID_ANY, _("Twitter"))
|
||||
mastodon = menu.Append(wx.ID_ANY, _("Mastodon"))
|
||||
menu.Bind(wx.EVT_MENU, self.on_new_twitter_account, twitter)
|
||||
menu.Bind(wx.EVT_MENU, self.on_new_mastodon_account, mastodon)
|
||||
self.PopupMenu(menu, self.new.GetPosition())
|
||||
|
||||
def on_new_mastodon_account(self, *args, **kwargs):
|
||||
dlg = wx.MessageDialog(self, _("You will be prompted for your Mastodon data (instance URL, email address and password) so we can authorise TWBlue in your instance. Would you like to authorise your account now?"), _(u"Authorization"), wx.YES_NO)
|
||||
response = dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
if response == wx.ID_YES:
|
||||
pub.sendMessage("sessionmanager.new_account", type="mastodon")
|
||||
|
||||
def on_new_twitter_account(self, *args, **kwargs):
|
||||
dlg = wx.MessageDialog(self, _(u"The request to authorize your Twitter account will be opened in your browser. You only need to do this once. Would you like to continue?"), _(u"Authorization"), wx.YES_NO)
|
||||
response = dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
if response == wx.ID_YES:
|
||||
pub.sendMessage("sessionmanager.new_account", type="twitter")
|
||||
|
||||
def add_new_session_to_list(self):
|
||||
total = self.list.get_count()
|
||||
@@ -61,8 +83,16 @@ class sessionManagerWindow(wx.Dialog):
|
||||
def get_response(self):
|
||||
return self.ShowModal()
|
||||
|
||||
def remove_account_dialog(self):
|
||||
return wx.MessageDialog(self, _(u"Do you really want to delete this account?"), _(u"Remove account"), wx.YES_NO).ShowModal()
|
||||
def on_remove(self, *args, **kwargs):
|
||||
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this account?"), _(u"Remove account"), wx.YES_NO)
|
||||
response = dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
if response == wx.ID_YES:
|
||||
selected = self.list.get_selected()
|
||||
pub.sendMessage("sessionmanager.remove_account", index=selected)
|
||||
|
||||
def on_configuration(self, *args, **kwargs):
|
||||
pub.sendMessage("sessionmanager.configuration")
|
||||
|
||||
def get_selected(self):
|
||||
return self.list.get_selected()
|
||||
|
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" this package contains code related to Sessions.
|
||||
In TWBlue, a session module defines everything a social network needs to be used in the program."""
|
||||
from __future__ import unicode_literals
|
||||
# let's define a global object for storing sessions across the program.
|
||||
sessions = {}
|
||||
|
@@ -6,10 +6,12 @@ import output
|
||||
import time
|
||||
import sound
|
||||
import logging
|
||||
import config
|
||||
import config_utils
|
||||
import sqlitedict
|
||||
import application
|
||||
from . import session_exceptions as Exceptions
|
||||
|
||||
log = logging.getLogger("sessionmanager.session")
|
||||
|
||||
class baseSession(object):
|
||||
@@ -52,6 +54,13 @@ class baseSession(object):
|
||||
def is_logged(self):
|
||||
return self.logged
|
||||
|
||||
def create_session_folder(self):
|
||||
path = os.path.join(paths.config_path(), self.session_id)
|
||||
if not os.path.exists(path):
|
||||
log.debug("Creating %s path" % (os.path.join(paths.config_path(), path),))
|
||||
os.mkdir(path)
|
||||
config.app["sessions"]["sessions"].append(id)
|
||||
|
||||
def get_configuration(self):
|
||||
""" Get settings for a session."""
|
||||
file_ = "%s/session.conf" % (self.session_id,)
|
||||
@@ -60,6 +69,9 @@ class baseSession(object):
|
||||
self.init_sound()
|
||||
self.load_persistent_data()
|
||||
|
||||
def get_name(self):
|
||||
pass
|
||||
|
||||
def init_sound(self):
|
||||
try: self.sound = sound.soundSystem(self.settings["sound"])
|
||||
except: pass
|
||||
|
1
src/sessions/mastodon/__init__.py
Normal file
1
src/sessions/mastodon/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
51
src/sessions/mastodon/compose.py
Normal file
51
src/sessions/mastodon/compose.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import arrow
|
||||
import languageHandler
|
||||
from . import utils, templates
|
||||
|
||||
def compose_post(post, db, relative_times, show_screen_names):
|
||||
if show_screen_names == False:
|
||||
user = post.account.get("display_name")
|
||||
if user == "":
|
||||
user = post.account.get("username")
|
||||
else:
|
||||
user = post.account.get("acct")
|
||||
original_date = arrow.get(post.created_at)
|
||||
if relative_times:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = original_date.shift(hours=db["utc_offset"]).format(_("dddd, MMMM D, YYYY H:m"), locale=languageHandler.curLang[:2])
|
||||
if post.reblog != None:
|
||||
text = _("Boosted from @{}: {}").format(post.reblog.account.acct, templates.process_text(post.reblog))
|
||||
else:
|
||||
text = templates.process_text(post)
|
||||
source = post.get("application", "")
|
||||
# "" means remote user, None for legacy apps so we should cover both sides.
|
||||
if source != None and source != "":
|
||||
source = source.get("name", "")
|
||||
else:
|
||||
source = ""
|
||||
return [user+", ", text, ts+", ", source]
|
||||
|
||||
def compose_user(user, db, relative_times=True, show_screen_names=False):
|
||||
original_date = arrow.get(user.created_at)
|
||||
if relative_times:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = original_date.shift(hours=db["utc_offset"]).format(_("dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
name = user.display_name
|
||||
if name == "":
|
||||
name = user.get("username")
|
||||
return [_("%s (@%s). %s followers, %s following, %s posts. Joined %s") % (name, user.acct, user.followers_count, user.following_count, user.statuses_count, ts)]
|
||||
|
||||
def compose_conversation(conversation, db, relative_times, show_screen_names):
|
||||
users = []
|
||||
for account in conversation.accounts:
|
||||
if account.display_name != "":
|
||||
users.append(account.display_name)
|
||||
else:
|
||||
users.append(account.username)
|
||||
users = ", ".join(users)
|
||||
last_post = compose_post(conversation.last_status, db, relative_times, show_screen_names)
|
||||
text = _("Last message from {}: {}").format(last_post[0], last_post[1])
|
||||
return [users, text, last_post[-2], last_post[-1]]
|
290
src/sessions/mastodon/session.py
Normal file
290
src/sessions/mastodon/session.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import paths
|
||||
import time
|
||||
import logging
|
||||
import webbrowser
|
||||
import wx
|
||||
import mastodon
|
||||
import config
|
||||
import config_utils
|
||||
import output
|
||||
import application
|
||||
from mastodon import MastodonError, MastodonNotFoundError, MastodonUnauthorizedError
|
||||
from pubsub import pub
|
||||
from mysc.thread_utils import call_threaded
|
||||
from sessions import base
|
||||
from sessions.mastodon import utils, streaming
|
||||
from .wxUI import authorisationDialog
|
||||
|
||||
log = logging.getLogger("sessions.mastodonSession")
|
||||
|
||||
MASTODON_VERSION = "4.0.1"
|
||||
|
||||
class Session(base.baseSession):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Session, self).__init__(*args, **kwargs)
|
||||
self.config_spec = "mastodon.defaults"
|
||||
self.supported_languages = []
|
||||
self.type = "mastodon"
|
||||
self.db["pagination_info"] = dict()
|
||||
self.char_limit = 500
|
||||
pub.subscribe(self.on_status, "mastodon.status_received")
|
||||
pub.subscribe(self.on_status_updated, "mastodon.status_updated")
|
||||
pub.subscribe(self.on_notification, "mastodon.notification_received")
|
||||
|
||||
def login(self, verify_credentials=True):
|
||||
if self.settings["mastodon"]["access_token"] != None and self.settings["mastodon"]["instance"] != None:
|
||||
try:
|
||||
log.debug("Logging in to Mastodon instance {}...".format(self.settings["mastodon"]["instance"]))
|
||||
self.api = mastodon.Mastodon(access_token=self.settings["mastodon"]["access_token"], api_base_url=self.settings["mastodon"]["instance"], mastodon_version=MASTODON_VERSION)
|
||||
if verify_credentials == True:
|
||||
credentials = self.api.account_verify_credentials()
|
||||
self.db["user_name"] = credentials["username"]
|
||||
self.db["user_id"] = credentials["id"]
|
||||
self.settings["mastodon"]["user_name"] = credentials["username"]
|
||||
self.logged = True
|
||||
log.debug("Logged.")
|
||||
self.counter = 0
|
||||
except IOError:
|
||||
log.error("The login attempt failed.")
|
||||
self.logged = False
|
||||
else:
|
||||
self.logged = False
|
||||
raise Exceptions.RequireCredentialsSessionError
|
||||
|
||||
def authorise(self):
|
||||
if self.logged == True:
|
||||
raise Exceptions.AlreadyAuthorisedError("The authorisation process is not needed at this time.")
|
||||
authorisation_dialog = wx.TextEntryDialog(None, _("Please enter your instance URL."), _("Mastodon instance"))
|
||||
answer = authorisation_dialog.ShowModal()
|
||||
instance = authorisation_dialog.GetValue()
|
||||
authorisation_dialog.Destroy()
|
||||
if answer != wx.ID_OK:
|
||||
return
|
||||
try:
|
||||
client_id, client_secret = mastodon.Mastodon.create_app("TWBlue", api_base_url=authorisation_dialog.GetValue(), website="https://twblue.es")
|
||||
temporary_api = mastodon.Mastodon(client_id=client_id, client_secret=client_secret, api_base_url=instance, mastodon_version=MASTODON_VERSION)
|
||||
auth_url = temporary_api.auth_request_url()
|
||||
except MastodonError:
|
||||
dlg = wx.MessageDialog(None, _("We could not connect to your mastodon instance. Please verify that the domain exists and the instance is accessible via a web browser."), _("Instance error"), wx.ICON_ERROR)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
return
|
||||
webbrowser.open_new_tab(auth_url)
|
||||
verification_dialog = wx.TextEntryDialog(None, _("Enter the verification code"), _("PIN code authorization"))
|
||||
answer = verification_dialog.ShowModal()
|
||||
code = verification_dialog.GetValue()
|
||||
verification_dialog.Destroy()
|
||||
if answer != wx.ID_OK:
|
||||
return
|
||||
try:
|
||||
access_token = temporary_api.log_in(code=verification_dialog.GetValue())
|
||||
except MastodonError:
|
||||
dlg = wx.MessageDialog(None, _("We could not authorice your mastodon account to be used in TWBlue. This might be caused due to an incorrect verification code. Please try to add the session again."), _("Authorization error"), wx.ICON_ERROR)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
return
|
||||
self.create_session_folder()
|
||||
self.get_configuration()
|
||||
self.settings["mastodon"]["access_token"] = access_token
|
||||
self.settings["mastodon"]["instance"] = instance
|
||||
self.settings.write()
|
||||
return True
|
||||
|
||||
def get_user_info(self):
|
||||
""" Retrieves some information required by TWBlue for setup."""
|
||||
# retrieve the current user's UTC offset so we can calculate dates properly.
|
||||
offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
|
||||
offset = offset / 60 / 60 * -1
|
||||
self.db["utc_offset"] = offset
|
||||
if len(self.supported_languages) == 0:
|
||||
self.supported_languages = self.api.instance().languages
|
||||
self.get_lists()
|
||||
self.get_muted_users()
|
||||
# determine instance custom characters limit.
|
||||
instance = self.api.instance()
|
||||
if hasattr(instance, "configuration") and hasattr(instance.configuration, "statuses") and hasattr(instance.configuration.statuses, "max_characters"):
|
||||
self.char_limit = instance.configuration.statuses.max_characters
|
||||
self.settings.write()
|
||||
|
||||
def get_lists(self):
|
||||
""" Gets the lists that the user is subscribed to and stores them in the database. Returns None."""
|
||||
self.db["lists"] = self.api.lists()
|
||||
|
||||
def get_muted_users(self):
|
||||
### ToDo: Use a function to retrieve all muted users.
|
||||
self.db["muted_users"] = self.api.mutes()
|
||||
|
||||
def get_user_alias(self, user):
|
||||
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 order_buffer(self, name, data, ignore_older=False):
|
||||
num = 0
|
||||
last_id = None
|
||||
if self.db.get(name) == None:
|
||||
self.db[name] = []
|
||||
objects = self.db[name]
|
||||
if ignore_older and len(self.db[name]) > 0:
|
||||
if self.settings["general"]["reverse_timelines"] == False:
|
||||
last_id = self.db[name][0].id
|
||||
else:
|
||||
last_id = self.db[name][-1].id
|
||||
for i in data:
|
||||
if ignore_older and last_id != None:
|
||||
if i.id < last_id:
|
||||
log.error("Ignoring an older tweet... Last id: {0}, tweet id: {1}".format(last_id, i.id))
|
||||
continue
|
||||
if utils.find_item(i, self.db[name]) == None:
|
||||
if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
|
||||
else: objects.insert(0, i)
|
||||
num = num+1
|
||||
self.db[name] = objects
|
||||
return num
|
||||
|
||||
def update_item(self, name, item):
|
||||
if name not in self.db:
|
||||
return False
|
||||
items = self.db[name]
|
||||
if type(items) != list:
|
||||
return False
|
||||
# determine item position in buffer.
|
||||
item_position = next((x for x in range(len(items)) if items[x].id == item.id), None)
|
||||
if item_position != None:
|
||||
self.db[name][item_position] = item
|
||||
return item_position
|
||||
return False
|
||||
|
||||
def api_call(self, call_name, action="", _sound=None, report_success=False, report_failure=True, preexec_message="", *args, **kwargs):
|
||||
finished = False
|
||||
tries = 0
|
||||
if preexec_message:
|
||||
output.speak(preexec_message, True)
|
||||
while finished==False and tries < 25:
|
||||
try:
|
||||
val = getattr(self.api, call_name)(*args, **kwargs)
|
||||
finished = True
|
||||
except MastodonError as e:
|
||||
output.speak(str(e))
|
||||
val = None
|
||||
if type(e) != MastodonNotFoundError and type(e) != MastodonUnauthorizedError :
|
||||
tries = tries+1
|
||||
time.sleep(5)
|
||||
elif report_failure:
|
||||
output.speak(_("%s failed. Reason: %s") % (action, str(e)))
|
||||
finished = True
|
||||
# except:
|
||||
# tries = tries + 1
|
||||
# time.sleep(5)
|
||||
if report_success:
|
||||
output.speak(_("%s succeeded.") % action)
|
||||
if _sound != None: self.sound.play(_sound)
|
||||
return val
|
||||
|
||||
def send_post(self, reply_to=None, users=None, visibility=None, posts=[]):
|
||||
""" Convenience function to send a thread. """
|
||||
in_reply_to_id = reply_to
|
||||
for obj in posts:
|
||||
text = obj.get("text")
|
||||
if len(obj["attachments"]) == 0:
|
||||
item = self.api_call(call_name="status_post", status=text, _sound="tweet_send.ogg", in_reply_to_id=in_reply_to_id, visibility=visibility, sensitive=obj["sensitive"], spoiler_text=obj["spoiler_text"])
|
||||
if item != None:
|
||||
in_reply_to_id = item["id"]
|
||||
else:
|
||||
media_ids = []
|
||||
poll = None
|
||||
if len(obj["attachments"]) == 1 and obj["attachments"][0]["type"] == "poll":
|
||||
poll = self.api.make_poll(options=obj["attachments"][0]["options"], expires_in=obj["attachments"][0]["expires_in"], multiple=obj["attachments"][0]["multiple"], hide_totals=obj["attachments"][0]["hide_totals"])
|
||||
else:
|
||||
for i in obj["attachments"]:
|
||||
img = self.api_call("media_post", media_file=i["file"], description=i["description"])
|
||||
media_ids.append(img.id)
|
||||
item = self.api_call(call_name="status_post", status=text, _sound="tweet_send.ogg", in_reply_to_id=in_reply_to_id, media_ids=media_ids, visibility=visibility, poll=poll, sensitive=obj["sensitive"], spoiler_text=obj["spoiler_text"])
|
||||
if item != None:
|
||||
in_reply_to_id = item["id"]
|
||||
|
||||
def get_name(self):
|
||||
instance = self.settings["mastodon"]["instance"]
|
||||
instance = instance.replace("https://", "")
|
||||
user = self.settings["mastodon"]["user_name"]
|
||||
return "Mastodon: {}@{}".format(user, instance)
|
||||
|
||||
def start_streaming(self):
|
||||
if config.app["app-settings"]["no_streaming"]:
|
||||
return
|
||||
listener = streaming.StreamListener(session_name=self.get_name(), user_id=self.db["user_id"])
|
||||
self.user_stream = self.api.stream_user(listener, run_async=True)
|
||||
self.direct_stream = self.api.stream_direct(listener, run_async=True)
|
||||
|
||||
def stop_streaming(self):
|
||||
if config.app["app-settings"]["no_streaming"]:
|
||||
return
|
||||
# if hasattr(self, "user_stream"):
|
||||
# self.user_stream.close()
|
||||
# log.debug("Stream stopped for accounr {}".format(self.db["user_name"]))
|
||||
# if hasattr(self, "direct_stream"):
|
||||
# self.direct_stream.close()
|
||||
# log.debug("Stream stopped for accounr {}".format(self.db["user_name"]))
|
||||
|
||||
def check_streams(self):
|
||||
if config.app["app-settings"]["no_streaming"]:
|
||||
return
|
||||
if not hasattr(self, "user_stream"):
|
||||
return
|
||||
if self.user_stream.is_alive() == False or self.user_stream.is_receiving() == False or self.direct_stream.is_alive() == False or self.direct_stream.is_receiving() == False:
|
||||
self.start_streaming()
|
||||
|
||||
def check_buffers(self, status):
|
||||
buffers = []
|
||||
buffers.append("home_timeline")
|
||||
if status.account.id == self.db["user_id"]:
|
||||
buffers.append("sent")
|
||||
return buffers
|
||||
|
||||
def on_status(self, status, session_name):
|
||||
# Discard processing the status if the streaming sends a tweet for another account.
|
||||
if self.get_name() != session_name:
|
||||
return
|
||||
buffers = self.check_buffers(status)
|
||||
for b in buffers[::]:
|
||||
num = self.order_buffer(b, [status])
|
||||
if num == 0:
|
||||
buffers.remove(b)
|
||||
pub.sendMessage("mastodon.new_item", session_name=self.get_name(), item=status, _buffers=buffers)
|
||||
|
||||
def on_status_updated(self, status, session_name):
|
||||
# Discard processing the status if the streaming sends a tweet for another account.
|
||||
if self.get_name() != session_name:
|
||||
return
|
||||
buffers = {}
|
||||
for b in list(self.db.keys()):
|
||||
updated = self.update_item(b, status)
|
||||
if updated != False:
|
||||
buffers[b] = updated
|
||||
pub.sendMessage("mastodon.updated_item", session_name=self.get_name(), item=status, _buffers=buffers)
|
||||
|
||||
def on_notification(self, notification, session_name):
|
||||
# Discard processing the notification if the streaming sends a tweet for another account.
|
||||
if self.get_name() != session_name:
|
||||
return
|
||||
buffers = []
|
||||
obj = None
|
||||
if notification.type == "mention":
|
||||
buffers = ["mentions"]
|
||||
obj = notification.status
|
||||
elif notification.type == "follow":
|
||||
buffers = ["followers"]
|
||||
obj = notification.account
|
||||
for b in buffers[::]:
|
||||
num = self.order_buffer(b, [obj])
|
||||
if num == 0:
|
||||
buffers.remove(b)
|
||||
pub.sendMessage("mastodon.new_item", session_name=self.get_name(), item=obj, _buffers=buffers)
|
25
src/sessions/mastodon/streaming.py
Normal file
25
src/sessions/mastodon/streaming.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import mastodon
|
||||
from pubsub import pub
|
||||
|
||||
class StreamListener(mastodon.StreamListener):
|
||||
|
||||
def __init__(self, session_name, user_id):
|
||||
self.session_name = session_name
|
||||
self.user_id = user_id
|
||||
super(StreamListener, self).__init__()
|
||||
|
||||
def on_update(self, status):
|
||||
pub.sendMessage("mastodon.status_received", status=status, session_name=self.session_name)
|
||||
|
||||
def on_status_update(self, status):
|
||||
pub.sendMessage("mastodon.status_updated", status=status, session_name=self.session_name)
|
||||
|
||||
def on_conversation(self, conversation):
|
||||
pub.sendMessage("mastodon.conversation_received", conversation=conversation, session_name=self.session_name)
|
||||
|
||||
def on_notification(self, notification):
|
||||
pub.sendMessage("mastodon.notification_received", notification=notification, session_name=self.session_name)
|
||||
|
||||
def on_unknown_event(self, event, payload):
|
||||
log.error("Unknown event: {} with payload as {}".format(event, payload))
|
137
src/sessions/mastodon/templates.py
Normal file
137
src/sessions/mastodon/templates.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import arrow
|
||||
import languageHandler
|
||||
from string import Template
|
||||
from . import utils, compose
|
||||
|
||||
# Define variables that would be available for all template objects.
|
||||
# This will be used for the edit template dialog.
|
||||
# Available variables for post objects.
|
||||
# safe_text will be the content warning in case a post contains one, text will always be the full text, no matter if has a content warning or not.
|
||||
post_variables = ["date", "display_name", "screen_name", "source", "lang", "safe_text", "text", "image_descriptions", "visibility"]
|
||||
person_variables = ["display_name", "screen_name", "description", "followers", "following", "favorites", "posts", "created_at"]
|
||||
conversation_variables = ["users", "last_post"]
|
||||
|
||||
# Default, translatable templates.
|
||||
post_default_template = _("$display_name, $text $image_descriptions $date. $source")
|
||||
dm_sent_default_template = _("Dm to $recipient_display_name, $text $date")
|
||||
person_default_template = _("$display_name (@$screen_name). $followers followers, $following following, $posts posts. Joined $created_at.")
|
||||
|
||||
def process_date(field, relative_times=True, offset_hours=0):
|
||||
original_date = arrow.get(field)
|
||||
if relative_times == True:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = original_date.shift(hours=offset_hours).format(_("dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
return ts
|
||||
|
||||
def process_text(post, safe=True):
|
||||
# text = utils.clean_mentions(utils.StripChars(text))
|
||||
if safe == True and post.sensitive == True and post.spoiler_text != "":
|
||||
return _("Content warning: {}").format(post.spoiler_text)
|
||||
return utils.html_filter(post.content)
|
||||
|
||||
def process_image_descriptions(media_attachments):
|
||||
""" Attempt to extract information for image descriptions. """
|
||||
image_descriptions = []
|
||||
for media in media_attachments:
|
||||
if media.get("description") != None and media.get("description") != "":
|
||||
image_descriptions.append(media.get("description"))
|
||||
idescriptions = ""
|
||||
for image in image_descriptions:
|
||||
idescriptions = idescriptions + _("Image description: {}").format(image) + "\n"
|
||||
return idescriptions
|
||||
|
||||
def remove_unneeded_variables(template, variables):
|
||||
for variable in variables:
|
||||
template = re.sub("\$"+variable, "", template)
|
||||
return template
|
||||
|
||||
def render_post(post, template, relative_times=False, offset_hours=0):
|
||||
""" Renders any given post according to the passed template.
|
||||
Available data for posts will be stored in the following variables:
|
||||
$date: Creation date.
|
||||
$display_name: User profile name.
|
||||
$screen_name: User screen name, this is the same name used to reference the user in Twitter.
|
||||
$ source: Source client from where the current tweet was sent.
|
||||
$lang: Two letter code for the automatically detected language for the tweet. This detection is performed by Twitter.
|
||||
$safe_text: Safe text to display. If a content warning is applied in posts, display those instead of the whole post.
|
||||
$text: Toot text. This always displays the full text, even if there is a content warning present.
|
||||
$image_descriptions: Information regarding image descriptions added by twitter users.
|
||||
$visibility: post's visibility: public, not listed, followers only or direct.
|
||||
"""
|
||||
global post_variables
|
||||
available_data = dict()
|
||||
created_at = process_date(post.created_at, relative_times, offset_hours)
|
||||
available_data.update(date=created_at)
|
||||
# user.
|
||||
display_name = post.account.display_name
|
||||
if display_name == "":
|
||||
display_name = post.account.username
|
||||
available_data.update(display_name=display_name, screen_name=post.account.acct)
|
||||
# Source client from where tweet was originated.
|
||||
source = ""
|
||||
if hasattr(post, "application") and post.application != None:
|
||||
available_data.update(source=post.application.get("name"))
|
||||
if post.reblog != None:
|
||||
text = _("Boosted from @{}: {}").format(post.reblog.account.acct, process_text(post.reblog, safe=False), )
|
||||
safe_text = _("Boosted from @{}: {}").format(post.reblog.account.acct, process_text(post.reblog), )
|
||||
else:
|
||||
text = process_text(post, safe=False)
|
||||
safe_text = process_text(post)
|
||||
visibility_settings = dict(public=_("Public"), unlisted=_("Not listed"), private=_("Followers only"), direct=_("Direct"))
|
||||
visibility = visibility_settings.get(post.visibility)
|
||||
available_data.update(lang=post.language, text=text, safe_text=safe_text, visibility=visibility)
|
||||
# process image descriptions
|
||||
image_descriptions = ""
|
||||
if post.reblog != None:
|
||||
image_descriptions = process_image_descriptions(post.reblog.media_attachments)
|
||||
else:
|
||||
image_descriptions = process_image_descriptions(post.media_attachments)
|
||||
if image_descriptions != "":
|
||||
available_data.update(image_descriptions=image_descriptions)
|
||||
result = Template(_(template)).safe_substitute(**available_data)
|
||||
result = remove_unneeded_variables(result, post_variables)
|
||||
return result
|
||||
|
||||
def render_user(user, template, relative_times=True, offset_hours=0):
|
||||
""" Renders persons by using the provided template.
|
||||
Available data will be stored in the following variables:
|
||||
$display_name: The name of the user, as they’ve defined it. Not necessarily a person’s name. Typically capped at 50 characters, but subject to change.
|
||||
$screen_name: The screen name, handle, or alias that this user identifies themselves with.
|
||||
$description: The user-defined UTF-8 string describing their account.
|
||||
$followers: The number of followers this account currently has. This value might be inaccurate.
|
||||
$following: The number of users this account is following (AKA their “followings”). This value might be inaccurate.
|
||||
$posts: The number of Tweets (including retweets) issued by the user. This value might be inaccurate.
|
||||
$created_at: The date and time that the user account was created on Twitter.
|
||||
"""
|
||||
global person_variables
|
||||
display_name = user.display_name
|
||||
if display_name == "":
|
||||
display_name = user.username
|
||||
available_data = dict(display_name=display_name, screen_name=user.acct, followers=user.followers_count, following=user.following_count, posts=user.statuses_count)
|
||||
# Nullable values.
|
||||
nullables = ["description"]
|
||||
for nullable in nullables:
|
||||
if hasattr(user, nullable) and getattr(user, nullable) != None:
|
||||
available_data[nullable] = getattr(user, nullable)
|
||||
created_at = process_date(user.created_at, relative_times=relative_times, offset_hours=offset_hours)
|
||||
available_data.update(created_at=created_at)
|
||||
result = Template(_(template)).safe_substitute(**available_data)
|
||||
result = remove_unneeded_variables(result, person_variables)
|
||||
return result
|
||||
|
||||
def render_conversation(conversation, template, post_template, relative_times=False, offset_hours=0):
|
||||
users = []
|
||||
for account in conversation.accounts:
|
||||
if account.display_name != "":
|
||||
users.append(account.display_name)
|
||||
else:
|
||||
users.append(account.username)
|
||||
users = ", ".join(users)
|
||||
last_post = render_post(conversation.last_status, post_template, relative_times=relative_times, offset_hours=offset_hours)
|
||||
available_data = dict(users=users, last_post=last_post)
|
||||
result = Template(_(template)).safe_substitute(**available_data)
|
||||
result = remove_unneeded_variables(result, conversation_variables)
|
||||
return result
|
60
src/sessions/mastodon/utils.py
Normal file
60
src/sessions/mastodon/utils.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import re
|
||||
from html.parser import HTMLParser
|
||||
|
||||
url_re = re.compile('<a\s*href=[\'|"](.*?)[\'"].*?>')
|
||||
|
||||
class HTMLFilter(HTMLParser):
|
||||
text = ""
|
||||
def handle_data(self, data):
|
||||
self.text += data
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "br":
|
||||
self.text = self.text+"\n"
|
||||
|
||||
def html_filter(data):
|
||||
f = HTMLFilter()
|
||||
f.feed(data)
|
||||
return f.text
|
||||
|
||||
def find_item(item, listItems):
|
||||
for i in range(0, len(listItems)):
|
||||
if listItems[i].id == item.id:
|
||||
return i
|
||||
if hasattr(item, "reblog") and item.reblog != None and item.reblog.id == listItems[i].id:
|
||||
return i
|
||||
return None
|
||||
|
||||
def is_audio_or_video(post):
|
||||
if post.reblog != None:
|
||||
return is_audio_or_video(post.reblog)
|
||||
# Checks firstly for Mastodon native videos and audios.
|
||||
for media in post.media_attachments:
|
||||
if media["type"] == "video" or media["type"] == "audio":
|
||||
return True
|
||||
|
||||
def is_image(post):
|
||||
if post.reblog != None:
|
||||
return is_image(post.reblog)
|
||||
# Checks firstly for Mastodon native videos and audios.
|
||||
for media in post.media_attachments:
|
||||
if media["type"] == "gifv" or media["type"] == "image":
|
||||
return True
|
||||
|
||||
def get_media_urls(post):
|
||||
if hasattr(post, "reblog") and post.reblog != None:
|
||||
return get_media_urls(post.reblog)
|
||||
urls = []
|
||||
for media in post.media_attachments:
|
||||
if media.get("type") == "audio" or media.get("type") == "video":
|
||||
urls.append(media.get("url"))
|
||||
return urls
|
||||
|
||||
def find_urls(post, include_tags=False):
|
||||
urls = url_re.findall(post.content)
|
||||
if include_tags == False:
|
||||
for tag in post.tags:
|
||||
for url in urls[::]:
|
||||
if url.lower().endswith("/tags/"+tag["name"]):
|
||||
urls.remove(url)
|
||||
return urls
|
35
src/sessions/mastodon/wxUI.py
Normal file
35
src/sessions/mastodon/wxUI.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import wx
|
||||
|
||||
class authorisationDialog(wx.Dialog):
|
||||
def __init__(self):
|
||||
super(authorisationDialog, self).__init__(parent=None, title=_(u"Authorising account..."))
|
||||
panel = wx.Panel(self)
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
static1 = wx.StaticText(panel, wx.NewId(), _("URL of mastodon instance:"))
|
||||
self.instance = wx.TextCtrl(panel, -1)
|
||||
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sizer1.Add(static1, 0, wx.ALL, 5)
|
||||
sizer1.Add(self.instance, 0, wx.ALL, 5)
|
||||
sizer.Add(sizer1, 0, wx.ALL, 5)
|
||||
static2 = wx.StaticText(panel, wx.NewId(), _("Email address:"))
|
||||
self.email = wx.TextCtrl(panel, -1)
|
||||
sizer2 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sizer2.Add(static2, 0, wx.ALL, 5)
|
||||
sizer2.Add(self.email, 0, wx.ALL, 5)
|
||||
sizer.Add(sizer2, 0, wx.ALL, 5)
|
||||
static3 = wx.StaticText(panel, wx.NewId(), _("Password:"))
|
||||
self.password = wx.TextCtrl(panel, -1)
|
||||
sizer3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sizer3.Add(static3, 0, wx.ALL, 5)
|
||||
sizer3.Add(self.password, 0, wx.ALL, 5)
|
||||
sizer.Add(sizer3, 0, wx.ALL, 5)
|
||||
self.ok = wx.Button(panel, wx.ID_OK)
|
||||
self.cancel = wx.Button(panel, wx.ID_CANCEL)
|
||||
sizer4 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sizer4.Add(self.ok, 0, wx.ALL, 5)
|
||||
sizer4.Add(self.cancel, 0, wx.ALL, 5)
|
||||
sizer.Add(sizer4, 0, wx.ALL, 5)
|
||||
panel.SetSizer(sizer)
|
||||
min = sizer.CalcMin()
|
||||
self.SetClientSize(min)
|
@@ -1,6 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import platform
|
||||
system = platform.system()
|
||||
from . import utils
|
||||
import re
|
||||
import time
|
||||
@@ -17,14 +15,11 @@ chars = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
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."""
|
||||
if system == "Windows":
|
||||
original_date = arrow.get(tweet.created_at, locale="en")
|
||||
if relative_times == True:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
original_date = arrow.get(tweet.created_at, locale="en")
|
||||
if relative_times == True:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = tweet.created_at
|
||||
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
if hasattr(tweet, "message"):
|
||||
value = "message"
|
||||
elif hasattr(tweet, "full_text"):
|
||||
@@ -58,16 +53,13 @@ def compose_tweet(tweet, db, relative_times, show_screen_names=False, session=No
|
||||
return [user+", ", text, ts+", ", source]
|
||||
|
||||
def compose_direct_message(item, db, relative_times, show_screen_names=False, session=None):
|
||||
if system == "Windows":
|
||||
# Let's remove the last 3 digits in the timestamp string.
|
||||
# Twitter sends their "epoch" timestamp with 3 digits for milliseconds and arrow doesn't like it.
|
||||
original_date = arrow.get(int(item.created_timestamp))
|
||||
if relative_times == True:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
# Let's remove the last 3 digits in the timestamp string.
|
||||
# Twitter sends their "epoch" timestamp with 3 digits for milliseconds and arrow doesn't like it.
|
||||
original_date = arrow.get(int(item.created_timestamp))
|
||||
if relative_times == True:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = item.created_timestamp
|
||||
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
text = StripChars(item.message_create["message_data"]["text"])
|
||||
source = "DM"
|
||||
sender = session.get_user(item.message_create["sender_id"])
|
||||
@@ -125,23 +117,17 @@ def compose_quoted_tweet(quoted_tweet, original_tweet, show_screen_names=False,
|
||||
return quoted_tweet
|
||||
|
||||
def compose_followers_list(tweet, db, relative_times=True, show_screen_names=False, session=None):
|
||||
if system == "Windows":
|
||||
original_date = arrow.get(tweet.created_at, locale="en")
|
||||
if relative_times == True:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
original_date = arrow.get(tweet.created_at, locale="en")
|
||||
if relative_times == True:
|
||||
ts = original_date.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts = tweet.created_at
|
||||
ts = original_date.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
if hasattr(tweet, "status"):
|
||||
if system == "Windows":
|
||||
original_date2 = arrow.get(tweet.status.created_at, locale="en")
|
||||
if relative_times:
|
||||
ts2 = original_date2.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts2 = original_date2.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
original_date2 = arrow.get(tweet.status.created_at, locale="en")
|
||||
if relative_times:
|
||||
ts2 = original_date2.humanize(locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
ts2 = _("Unavailable")
|
||||
ts2 = original_date2.shift(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.curLang[:2])
|
||||
else:
|
||||
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)]
|
||||
|
@@ -1,3 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" this package holds different modules to extract information regarding long tweets. A long tweet contains more than one tweet (such a quoted tweet), or is made via services like twishort."""
|
||||
from __future__ import unicode_literals
|
||||
|
@@ -5,13 +5,13 @@ import time
|
||||
import logging
|
||||
import webbrowser
|
||||
import wx
|
||||
import tweepy
|
||||
import demoji
|
||||
import config
|
||||
import output
|
||||
import application
|
||||
import appkeys
|
||||
from pubsub import pub
|
||||
import tweepy
|
||||
from tweepy.errors import TweepyException, Forbidden, NotFound
|
||||
from tweepy.models import User as UserModel
|
||||
from mysc.thread_utils import call_threaded
|
||||
@@ -106,10 +106,8 @@ class Session(base.baseSession):
|
||||
else: objects.insert(0, i)
|
||||
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("twitter.sent_dms_updated", total=sent, session_name=self.get_name())
|
||||
return incoming
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -127,8 +125,8 @@ class Session(base.baseSession):
|
||||
# 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")
|
||||
pub.subscribe(self.handle_new_status, "twitter.new_status")
|
||||
pub.subscribe(self.handle_connected, "twitter.stream_connected")
|
||||
|
||||
# @_require_configuration
|
||||
def login(self, verify_credentials=True):
|
||||
@@ -137,12 +135,14 @@ class Session(base.baseSession):
|
||||
if self.settings["twitter"]["user_key"] != None and self.settings["twitter"]["user_secret"] != None:
|
||||
try:
|
||||
log.debug("Logging in to twitter...")
|
||||
self.auth = tweepy.OAuth1UserHandler(appkeys.twitter_api_key, appkeys.twitter_api_secret)
|
||||
self.auth.set_access_token(self.settings["twitter"]["user_key"], self.settings["twitter"]["user_secret"])
|
||||
self.auth = tweepy.OAuth1UserHandler(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"])
|
||||
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:
|
||||
self.credentials = self.twitter.verify_credentials()
|
||||
self.settings["twitter"]["user_name"] = self.credentials.screen_name
|
||||
self.db["user_name"] = self.credentials.screen_name
|
||||
self.db["user_id"] = self.credentials.id_str
|
||||
self.logged = True
|
||||
log.debug("Logged.")
|
||||
self.counter = 0
|
||||
@@ -153,37 +153,32 @@ class Session(base.baseSession):
|
||||
self.logged = False
|
||||
raise Exceptions.RequireCredentialsSessionError
|
||||
|
||||
# @_require_configuration
|
||||
def authorise(self):
|
||||
""" Authorises a Twitter account. This function needs to be called for each new session, after self.get_configuration() and before self.login()"""
|
||||
if self.logged == True:
|
||||
raise Exceptions.AlreadyAuthorisedError("The authorisation process is not needed at this time.")
|
||||
else:
|
||||
self.auth = tweepy.OAuth1UserHandler(appkeys.twitter_api_key, appkeys.twitter_api_secret)
|
||||
redirect_url = self.auth.get_authorization_url()
|
||||
webbrowser.open_new_tab(redirect_url)
|
||||
self.authorisation_dialog = authorisationDialog()
|
||||
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.ShowModal()
|
||||
|
||||
def verify_authorisation(self, pincode):
|
||||
self.auth.get_access_token(pincode)
|
||||
self.settings["twitter"]["user_key"] = self.auth.access_token
|
||||
self.settings["twitter"]["user_secret"] = self.auth.access_token_secret
|
||||
auth = tweepy.OAuth1UserHandler(appkeys.twitter_api_key, appkeys.twitter_api_secret)
|
||||
redirect_url = auth.get_authorization_url()
|
||||
webbrowser.open_new_tab(redirect_url)
|
||||
verification_dialog = wx.TextEntryDialog(None, _("Enter your PIN code here"), _("Authorising account..."))
|
||||
answer = verification_dialog.ShowModal()
|
||||
code = verification_dialog.GetValue()
|
||||
verification_dialog.Destroy()
|
||||
if answer != wx.ID_OK:
|
||||
return
|
||||
try:
|
||||
auth.get_access_token(code)
|
||||
except TweepyException:
|
||||
dlg = wx.MessageDialog(None, _("We could not authorice your Twitter account to be used in TWBlue. This might be caused due to an incorrect verification code. Please try to add the session again."), _("Authorization error"), wx.ICON_ERROR)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
return False
|
||||
self.create_session_folder()
|
||||
self.get_configuration()
|
||||
self.settings["twitter"]["user_key"] = auth.access_token
|
||||
self.settings["twitter"]["user_secret"] = auth.access_token_secret
|
||||
self.settings.write()
|
||||
del self.auth
|
||||
|
||||
def authorisation_cancelled(self, *args, **kwargs):
|
||||
""" Destroy the authorization dialog. """
|
||||
self.authorisation_dialog.Destroy()
|
||||
del self.authorisation_dialog
|
||||
|
||||
def authorisation_accepted(self, *args, **kwargs):
|
||||
""" Gets the PIN code entered by user and validate it through Twitter."""
|
||||
pincode = self.authorisation_dialog.text.GetValue()
|
||||
self.verify_authorisation(pincode)
|
||||
self.authorisation_dialog.Destroy()
|
||||
return True
|
||||
|
||||
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.
|
||||
@@ -279,15 +274,9 @@ class Session(base.baseSession):
|
||||
# @_require_login
|
||||
def get_user_info(self):
|
||||
""" Retrieves some information required by TWBlue for setup."""
|
||||
f = self.twitter.get_settings()
|
||||
sn = f["screen_name"]
|
||||
self.settings["twitter"]["user_name"] = sn
|
||||
self.db["user_name"] = sn
|
||||
self.db["user_id"] = self.twitter.get_user(screen_name=sn).id
|
||||
try:
|
||||
self.db["utc_offset"] = f["time_zone"]["utc_offset"]
|
||||
except KeyError:
|
||||
self.db["utc_offset"] = -time.timezone
|
||||
offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
|
||||
offset = offset*-1
|
||||
self.db["utc_offset"] = offset
|
||||
# Get twitter's supported languages and save them in a global variable
|
||||
#so we won't call to this method once per session.
|
||||
if len(application.supported_languages) == 0:
|
||||
@@ -556,7 +545,7 @@ class Session(base.baseSession):
|
||||
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 = streaming.Stream(twitter_api=self.twitter, session_name=self.get_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):
|
||||
@@ -566,12 +555,12 @@ class Session(base.baseSession):
|
||||
self.stream.running = False
|
||||
log.debug("Stream stopped for accounr {}".format(self.db["user_name"]))
|
||||
|
||||
def handle_new_status(self, status, user):
|
||||
def handle_new_status(self, status, session_name):
|
||||
""" 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:
|
||||
if self.get_name() != session_name:
|
||||
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.
|
||||
@@ -608,7 +597,7 @@ class Session(base.baseSession):
|
||||
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)
|
||||
pub.sendMessage("twitter.new_tweet", data=status, session_name=self.get_name(), _buffers=buffers_to_send)
|
||||
|
||||
def check_streams(self):
|
||||
if config.app["app-settings"]["no_streaming"]:
|
||||
@@ -619,11 +608,11 @@ class Session(base.baseSession):
|
||||
if self.stream.running == False:
|
||||
self.start_streaming()
|
||||
|
||||
def handle_connected(self, user):
|
||||
def handle_connected(self, session_name):
|
||||
if self.logged == False:
|
||||
return
|
||||
if user != self.db["user_name"]:
|
||||
log.debug("Connected streaming endpoint on account {}".format(user))
|
||||
if session_name != self.get_name():
|
||||
log.debug("Connected streaming endpoint on session {}".format(session_name))
|
||||
|
||||
def send_tweet(self, *tweets):
|
||||
""" Convenience function to send a thread. """
|
||||
@@ -674,4 +663,10 @@ class Session(base.baseSession):
|
||||
else:
|
||||
sent_dms.insert(0, item)
|
||||
self.db["sent_direct_messages"] = sent_dms
|
||||
pub.sendMessage("sent-dm", data=item, user=self.db["user_name"])
|
||||
pub.sendMessage("twitter.sent_dm", data=item, session_name=self.get_name())
|
||||
|
||||
def get_name(self):
|
||||
if self.logged:
|
||||
return "Twitter: {}".format(self.db["user_name"])
|
||||
else:
|
||||
return "Twitter: {}".format(self.settings["twitter"]["user_name"])
|
@@ -14,13 +14,13 @@ log = logging.getLogger("sessions.twitter.streaming")
|
||||
|
||||
class Stream(tweepy.Stream):
|
||||
|
||||
def __init__(self, twitter_api, user, user_id, muted_users=[], *args, **kwargs):
|
||||
def __init__(self, twitter_api, session_name, user_id, muted_users=[], *args, **kwargs):
|
||||
super(Stream, self).__init__(*args, **kwargs)
|
||||
log.debug("Starting streaming listener for account {}".format(user))
|
||||
log.debug("Starting streaming listener for session {}".format(session_name))
|
||||
self.started = False
|
||||
self.users = []
|
||||
self.api = twitter_api
|
||||
self.user = user
|
||||
self.session_name = session_name
|
||||
self.user_id = user_id
|
||||
friends = self.api.get_friend_ids()
|
||||
log.debug("Retrieved {} friends to add to the streaming listener.".format(len(friends)))
|
||||
@@ -33,15 +33,15 @@ class Stream(tweepy.Stream):
|
||||
log.debug("Streaming listener started with {} users to follow.".format(len(self.users)))
|
||||
|
||||
def on_connect(self):
|
||||
pub.sendMessage("streamConnected", user=self.user)
|
||||
pub.sendMessage("twitter.stream_connected", session_name=self.session_name)
|
||||
|
||||
def on_exception(self, ex):
|
||||
log.exception("Exception received on streaming endpoint for user {}".format(self.user))
|
||||
log.exception("Exception received on streaming endpoint for session {}".format(self.session_name))
|
||||
|
||||
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:
|
||||
if status.in_reply_to_user_id_str != None and status.in_reply_to_user_id_str not in self.users and status.user.id != self.user_id:
|
||||
return
|
||||
if status.user.id_str in self.users:
|
||||
pub.sendMessage("newStatus", status=status, user=self.user)
|
||||
pub.sendMessage("twitter.new_status", status=status, session_name=self.session_name)
|
||||
|
@@ -231,7 +231,7 @@ def filter_tweet(tweet, tweet_data, settings, buffer_name):
|
||||
if word not in getattr(tweet, value):
|
||||
return False
|
||||
if settings["filters"][i]["in_lang"] == "True":
|
||||
if getattr(tweet, lang) not in settings["filters"][i]["languages"]:
|
||||
if getattr(tweet, "lang") not in settings["filters"][i]["languages"]:
|
||||
return False
|
||||
elif settings["filters"][i]["in_lang"] == "False":
|
||||
if tweet.lang in settings["filters"][i]["languages"]:
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import wx
|
||||
|
||||
class authorisationDialog(wx.Dialog):
|
||||
|
@@ -111,7 +111,8 @@ class URLStream(object):
|
||||
self.prepared = False
|
||||
log.debug("URL Player initialized")
|
||||
# LibVLC controls.
|
||||
self.instance = vlc.Instance()
|
||||
self.instance = vlc.Instance("--quiet")
|
||||
self.instance.log_unset()
|
||||
self.player = self.instance.media_player_new()
|
||||
self.event_manager = self.player.event_manager()
|
||||
self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.end_callback)
|
||||
|
1
src/test/sessions/__init__.py
Normal file
1
src/test/sessions/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
201
src/test/sessions/test_base_session.py
Normal file
201
src/test/sessions/test_base_session.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import types
|
||||
import pytest
|
||||
import os
|
||||
import sqlitedict
|
||||
import shutil
|
||||
from unittest import mock
|
||||
|
||||
# Mock sound module, so LibVLc won't complain.
|
||||
sound_module = types.ModuleType("sound")
|
||||
sys.modules["sound"] = sound_module
|
||||
sound_module.soundManager = mock.MagicMock(name="sound.soundManager")
|
||||
from sessions import base
|
||||
|
||||
# path where we will save our test config, as we can't rely on paths module due to pytest's paths being different.
|
||||
session_path = os.path.join(os.getcwd(), "config", "testing")
|
||||
|
||||
@pytest.fixture
|
||||
def session():
|
||||
""" Configures a fake base session from where we can test things. """
|
||||
global session_path
|
||||
s = base.baseSession("testing")
|
||||
if os.path.exists(session_path) == False:
|
||||
os.mkdir(session_path)
|
||||
# Patches paths.app_path and paths.config_path, so we will not have issues during session configuration.
|
||||
with mock.patch("paths.app_path", return_value=os.getcwd()) as app_path:
|
||||
with mock.patch("paths.config_path", return_value=os.path.join(os.getcwd(), "config")) as config_path:
|
||||
s.get_configuration()
|
||||
yield s
|
||||
# Session's cleanup code.
|
||||
if os.path.exists(session_path):
|
||||
shutil.rmtree(session_path)
|
||||
del s
|
||||
|
||||
@pytest.fixture
|
||||
def dataset():
|
||||
""" Generates a sample dataset"""
|
||||
dataset = dict(home_timeline=["message" for i in range(10000)], mentions_timeline=["mention" for i in range(20000)])
|
||||
yield dataset
|
||||
|
||||
### Testing database being read from disk.
|
||||
def test_cache_in_disk_unlimited_size(session, dataset):
|
||||
""" Tests cache database being read from disk, storing the whole datasets. """
|
||||
session.settings["general"]["load_cache_in_memory"] = False
|
||||
session.settings["general"]["persist_size"] = -1
|
||||
session.load_persistent_data()
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
session.save_persistent_data()
|
||||
assert isinstance(session.db, sqlitedict.SqliteDict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert len(session.db.get("home_timeline")) == 10000
|
||||
assert len(session.db.get("mentions_timeline")) == 20000
|
||||
session.db.close()
|
||||
|
||||
def test_cache_in_disk_limited_dataset(session, dataset):
|
||||
""" Tests wether the cache stores only the amount of items we ask it to store. """
|
||||
session.settings["general"]["load_cache_in_memory"] = False
|
||||
session.settings["general"]["persist_size"] = 100
|
||||
session.load_persistent_data()
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
|
||||
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
|
||||
# Might cause an out of sync error between the GUI lists and the database.
|
||||
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
|
||||
session.save_persistent_data()
|
||||
session.db = dict()
|
||||
session.load_persistent_data()
|
||||
assert isinstance(session.db, sqlitedict.SqliteDict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert len(session.db.get("home_timeline")) == 100
|
||||
assert len(session.db.get("mentions_timeline")) == 100
|
||||
session.db.close()
|
||||
|
||||
def test_cache_in_disk_limited_dataset_unreversed(session):
|
||||
"""Test if the cache is saved properly in unreversed buffers, when newest items are at the end of the list. """
|
||||
dataset = dict(home_timeline=[i for i in range(20)], mentions_timeline=[i for i in range(20)])
|
||||
session.settings["general"]["load_cache_in_memory"] = False
|
||||
session.settings["general"]["persist_size"] = 10
|
||||
session.load_persistent_data()
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
|
||||
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
|
||||
# Might cause an out of sync error between the GUI lists and the database.
|
||||
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
|
||||
session.save_persistent_data()
|
||||
session.db = dict()
|
||||
session.load_persistent_data()
|
||||
assert isinstance(session.db, sqlitedict.SqliteDict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert session.db.get("home_timeline")[0] == 10
|
||||
assert session.db.get("mentions_timeline")[0] == 10
|
||||
assert session.db.get("home_timeline")[-1] == 19
|
||||
assert session.db.get("mentions_timeline")[-1] == 19
|
||||
session.db.close()
|
||||
|
||||
def test_cache_in_disk_limited_dataset_reversed(session):
|
||||
"""Test if the cache is saved properly in reversed buffers, when newest items are at the start of the list. """
|
||||
dataset = dict(home_timeline=[i for i in range(19, -1, -1)], mentions_timeline=[i for i in range(19, -1, -1)])
|
||||
session.settings["general"]["load_cache_in_memory"] = False
|
||||
session.settings["general"]["persist_size"] = 10
|
||||
session.settings["general"]["reverse_timelines"] = True
|
||||
session.load_persistent_data()
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
|
||||
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
|
||||
# Might cause an out of sync error between the GUI lists and the database.
|
||||
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
|
||||
session.save_persistent_data()
|
||||
session.db = dict()
|
||||
session.load_persistent_data()
|
||||
assert isinstance(session.db, sqlitedict.SqliteDict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert session.db.get("home_timeline")[0] == 19
|
||||
assert session.db.get("mentions_timeline")[0] == 19
|
||||
assert session.db.get("home_timeline")[-1] == 10
|
||||
assert session.db.get("mentions_timeline")[-1] == 10
|
||||
session.db.close()
|
||||
|
||||
### Testing database being loaded into memory. Those tests should give the same results than before
|
||||
### but as we have different code depending whether we load db into memory or read it from disk,
|
||||
### We need to test this anyways.
|
||||
def test_cache_in_memory_unlimited_size(session, dataset):
|
||||
""" Tests cache database being loaded in memory, storing the whole datasets. """
|
||||
session.settings["general"]["load_cache_in_memory"] = True
|
||||
session.settings["general"]["persist_size"] = -1
|
||||
session.load_persistent_data()
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
session.save_persistent_data()
|
||||
session.db = dict()
|
||||
session.load_persistent_data()
|
||||
assert isinstance(session.db, dict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert len(session.db.get("home_timeline")) == 10000
|
||||
assert len(session.db.get("mentions_timeline")) == 20000
|
||||
|
||||
def test_cache_in_memory_limited_dataset(session, dataset):
|
||||
""" Tests wether the cache stores only the amount of items we ask it to store, when loaded in memory. """
|
||||
session.settings["general"]["load_cache_in_memory"] = True
|
||||
session.settings["general"]["persist_size"] = 100
|
||||
session.load_persistent_data()
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
session.save_persistent_data()
|
||||
session.db = dict()
|
||||
session.load_persistent_data()
|
||||
assert isinstance(session.db, dict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert len(session.db.get("home_timeline")) == 100
|
||||
assert len(session.db.get("mentions_timeline")) == 100
|
||||
|
||||
def test_cache_in_memory_limited_dataset_unreversed(session):
|
||||
"""Test if the cache is saved properly when loaded in memory in unreversed buffers, when newest items are at the end of the list. """
|
||||
dataset = dict(home_timeline=[i for i in range(20)], mentions_timeline=[i for i in range(20)])
|
||||
session.settings["general"]["load_cache_in_memory"] = True
|
||||
session.settings["general"]["persist_size"] = 10
|
||||
session.load_persistent_data()
|
||||
assert len(session.db)==1
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
session.save_persistent_data()
|
||||
session.db = dict()
|
||||
session.load_persistent_data()
|
||||
assert isinstance(session.db, dict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert session.db.get("home_timeline")[0] == 10
|
||||
assert session.db.get("mentions_timeline")[0] == 10
|
||||
assert session.db.get("home_timeline")[-1] == 19
|
||||
assert session.db.get("mentions_timeline")[-1] == 19
|
||||
|
||||
def test_cache_in_memory_limited_dataset_reversed(session):
|
||||
"""Test if the cache is saved properly in reversed buffers, when newest items are at the start of the list. This test if for db read into memory. """
|
||||
dataset = dict(home_timeline=[i for i in range(19, -1, -1)], mentions_timeline=[i for i in range(19, -1, -1)])
|
||||
session.settings["general"]["load_cache_in_memory"] = True
|
||||
session.settings["general"]["persist_size"] = 10
|
||||
session.settings["general"]["reverse_timelines"] = True
|
||||
session.load_persistent_data()
|
||||
session.db["home_timeline"] = dataset["home_timeline"]
|
||||
session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
session.save_persistent_data()
|
||||
session.db = dict()
|
||||
session.load_persistent_data()
|
||||
assert isinstance(session.db, dict)
|
||||
assert session.db.get("home_timeline") != None
|
||||
assert session.db.get("mentions_timeline") != None
|
||||
assert session.db.get("home_timeline")[0] == 19
|
||||
assert session.db.get("mentions_timeline")[0] == 19
|
||||
assert session.db.get("home_timeline")[-1] == 10
|
||||
assert session.db.get("mentions_timeline")[-1] == 10
|
1
src/test/sessions/twitter/__init__.py
Normal file
1
src/test/sessions/twitter/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
13
src/test/sessions/twitter/conftest.py
Normal file
13
src/test/sessions/twitter/conftest.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
from tweepy.models import Status
|
||||
|
||||
@pytest.fixture
|
||||
def basic_tweet():
|
||||
data = {'created_at': 'Mon Jan 03 15:03:36 +0000 2022', 'id': 1478019218884857856, 'id_str': '1478019218884857856', 'full_text': 'Changes in projects for next year https://t.co/nW3GS9RmHd', 'truncated': False, 'display_text_range': [0, 57], 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': [{'url': 'https://t.co/nW3GS9RmHd', 'expanded_url': 'https://manuelcortez.net/blog/changes-in-projects-for-next-year/#.YdMQQU6t1FI.twitter', 'display_url': 'manuelcortez.net/blog/changes-i…', 'indices': [34, 57]}]}, 'source': '<a href="https://mobile.twitter.com" rel="nofollow">Twitter Web App</a>', 'in_reply_to_status_id': None, 'in_reply_to_status_id_str': None, 'in_reply_to_user_id': None, 'in_reply_to_user_id_str': None, 'in_reply_to_screen_name': None, 'user': {'id': 258677951, 'id_str': '258677951', 'name': 'Manuel Cortez', 'screen_name': 'manuelcortez00', 'location': 'Nuevo León, México', 'description': 'Python developer, , interested in reading, accessibility, astronomy, physics and science. Я учу русский.', 'url': 'https://t.co/JFRKRA73ZV', 'entities': {'url': {'urls': [{'url': 'https://t.co/JFRKRA73ZV', 'expanded_url': 'https://manuelcortez.net', 'display_url': 'manuelcortez.net', 'indices': [0, 23]}]}, 'description': {'urls': []}}, 'protected': False, 'followers_count': 1453, 'friends_count': 568, 'listed_count': 45, 'created_at': 'Mon Feb 28 06:52:48 +0000 2011', 'favourites_count': 283, 'utc_offset': None, 'time_zone': None, 'geo_enabled': True, 'verified': False, 'statuses_count': 43371, 'lang': None, 'contributors_enabled': False, 'is_translator': False, 'is_translation_enabled': False, 'profile_background_color': 'C0DEED', 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'profile_background_tile': False, 'profile_image_url': 'http://pbs.twimg.com/profile_images/442466677645508608/3EBBC-OX_normal.jpeg', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/442466677645508608/3EBBC-OX_normal.jpeg', 'profile_image_extensions_alt_text': None, 'profile_link_color': '1DA1F2', 'profile_sidebar_border_color': 'C0DEED', 'profile_sidebar_fill_color': 'DDEEF6', 'profile_text_color': '333333', 'profile_use_background_image': True, 'has_extended_profile': False, 'default_profile': True, 'default_profile_image': False, 'following': False, 'follow_request_sent': False, 'notifications': False, 'translator_type': 'regular', 'withheld_in_countries': []}, 'geo': None, 'coordinates': None, 'place': None, 'contributors': None, 'is_quote_status': False, 'retweet_count': 6, 'favorite_count': 2, 'favorited': False, 'retweeted': False, 'possibly_sensitive': False, 'possibly_sensitive_appealable': False, 'lang': 'en'}
|
||||
yield Status().parse(api=None, json=data)
|
||||
|
||||
@pytest.fixture
|
||||
def basic_tweet_multiple_mentions():
|
||||
data = {'created_at': 'Mon Dec 27 21:21:25 +0000 2021', 'id': 1475577584947707909, 'id_str': '1475577584947707909', 'full_text': '@tamaranatalia9 @Darkstrings @Chris88171572 @manuelcortez00 Well done, thanks Tamara', 'truncated': False, 'display_text_range': [60, 84], 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'screen_name': 'tamaranatalia9', 'name': 'Tamara', 'id': 914114584591597568, 'id_str': '914114584591597568', 'indices': [0, 15]}, {'screen_name': 'Darkstrings', 'name': 'Luc', 'id': 1374154151115042823, 'id_str': '1374154151115042823', 'indices': [16, 28]}, {'screen_name': 'Chris88171572', 'name': 'Chris', 'id': 1323980014799495168, 'id_str': '1323980014799495168', 'indices': [29, 43]}, {'screen_name': 'manuelcortez00', 'name': 'Manuel Cortez', 'id': 258677951, 'id_str': '258677951', 'indices': [44, 59]}], 'urls': []}, 'source': '<a href="http://twitter.com/download/android" rel="nofollow">Twitter for Android</a>', 'in_reply_to_status_id': 1475550502083563526, 'in_reply_to_status_id_str': '1475550502083563526', 'in_reply_to_user_id': 914114584591597568, 'in_reply_to_user_id_str': '914114584591597568', 'in_reply_to_screen_name': 'tamaranatalia9', 'user': {'id': 784837522157436929, 'id_str': '784837522157436929', 'name': 'Paulus', 'screen_name': 'PauloPer01', 'location': '', 'description': '', 'url': None, 'entities': {'description': {'urls': []}}, 'protected': False, 'followers_count': 1082, 'friends_count': 3029, 'listed_count': 2, 'created_at': 'Sat Oct 08 19:27:01 +0000 2016', 'favourites_count': 78862, 'utc_offset': None, 'time_zone': None, 'geo_enabled': False, 'verified': False, 'statuses_count': 4976, 'lang': None, 'contributors_enabled': False, 'is_translator': False, 'is_translation_enabled': False, 'profile_background_color': 'F5F8FA', 'profile_background_image_url': None, 'profile_background_image_url_https': None, 'profile_background_tile': False, 'profile_image_url': 'http://pbs.twimg.com/profile_images/1464572633014587395/246oPPLa_normal.jpg', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1464572633014587395/246oPPLa_normal.jpg', 'profile_image_extensions_alt_text': None, 'profile_link_color': '1DA1F2', 'profile_sidebar_border_color': 'C0DEED', 'profile_sidebar_fill_color': 'DDEEF6', 'profile_text_color': '333333', 'profile_use_background_image': True, 'has_extended_profile': True, 'default_profile': True, 'default_profile_image': False, 'following': False, 'follow_request_sent': False, 'notifications': False, 'translator_type': 'none', 'withheld_in_countries': []}, 'geo': None, 'coordinates': None, 'place': None, 'contributors': None, 'is_quote_status': False, 'retweet_count': 1, 'favorite_count': 2, 'favorited': False, 'retweeted': False, 'lang': 'en'}
|
||||
yield Status().parse(api=None, json=data)
|
52
src/test/sessions/twitter/test_twitter_templates.py
Normal file
52
src/test/sessions/twitter/test_twitter_templates.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
import gettext
|
||||
import datetime
|
||||
gettext.install("test")
|
||||
from unittest import mock
|
||||
from sessions.twitter import templates
|
||||
|
||||
def test_default_values():
|
||||
""" Tests wheter default values are the expected ones.
|
||||
This might be useful so we will have this failing when we update anything from those values.
|
||||
As TWBlue might be using those from other dialogs.
|
||||
"""
|
||||
assert templates.tweet_variables == ["date", "display_name", "screen_name", "source", "lang", "text", "image_descriptions"]
|
||||
assert templates.dm_variables == ["date", "sender_display_name", "sender_screen_name", "recipient_display_name", "recipient_display_name", "text"]
|
||||
assert templates.person_variables == ["display_name", "screen_name", "location", "description", "followers", "following", "listed", "likes", "tweets", "created_at"]
|
||||
|
||||
@pytest.mark.parametrize("offset, language, expected_result", [
|
||||
(0, "en_US", "Wednesday, October 10, 2018 20:19:24"),
|
||||
(-21600, "en_US", "Wednesday, October 10, 2018 14:19:24"),
|
||||
(7200, "en_US", "Wednesday, October 10, 2018 22:19:24"),
|
||||
(0, "es_ES", "miércoles, octubre 10, 2018 20:19:24"),
|
||||
(-21600, "es_ES", "miércoles, octubre 10, 2018 14:19:24"),
|
||||
(7200, "es_ES", "miércoles, octubre 10, 2018 22:19:24"),
|
||||
(18000, "es_ES", "jueves, octubre 11, 2018 1:19:24"),
|
||||
])
|
||||
def test_process_date_absolute_time(offset, language, expected_result):
|
||||
""" Tests date processing function for tweets, when relative_times is set to False. """
|
||||
# Date representation used by twitter, converted to datetime object, as tweepy already does this.
|
||||
# Original date was Wed Oct 10 20:19:24 +0000 2018
|
||||
date_field = datetime.datetime(2018, 10, 10, 20, 19, 24)
|
||||
with mock.patch("languageHandler.curLang", new=language):
|
||||
processed_date = templates.process_date(date_field, relative_times=False, offset_seconds=offset)
|
||||
assert processed_date == expected_result
|
||||
|
||||
def test_process_date_relative_time():
|
||||
date_field = datetime.datetime(2018, 10, 10, 20, 19, 24)
|
||||
with mock.patch("languageHandler.curLang", new="es_ES"):
|
||||
processed_date = templates.process_date(date_field, relative_times=True, offset_seconds=7200)
|
||||
# As this depends in relative times and this is subject to change, let's do some light checks here and hope the string is going to be valid.
|
||||
assert isinstance(processed_date, str)
|
||||
assert "hace" in processed_date and "años" in processed_date
|
||||
|
||||
def test_process_text_basic_tweet(basic_tweet):
|
||||
expected_result = "Changes in projects for next year https://manuelcortez.net/blog/changes-in-projects-for-next-year/#.YdMQQU6t1FI.twitter"
|
||||
text = templates.process_text(basic_tweet)
|
||||
assert text == expected_result
|
||||
|
||||
def test_process_text_basic_tweet_multiple_mentions(basic_tweet_multiple_mentions):
|
||||
expected_result = "@tamaranatalia9, @Darkstrings and 2 more: Well done, thanks Tamara"
|
||||
text = templates.process_text(basic_tweet_multiple_mentions)
|
||||
assert text == expected_result
|
@@ -1,200 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Test case to check some of the scenarios we might face when storing tweets in cache, both loading into memory or rreading from disk. """
|
||||
import unittest
|
||||
import os
|
||||
import paths
|
||||
import sqlitedict
|
||||
import shutil
|
||||
# The base session module requires sound as a dependency, and this needs libVLC to be locatable.
|
||||
os.environ['PYTHON_VLC_MODULE_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", "x86"))
|
||||
os.environ['PYTHON_VLC_LIB_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", "x86", "libvlc.dll"))
|
||||
from sessions import base
|
||||
|
||||
class cacheTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
""" Configures a fake session to check caching objects here. """
|
||||
self.session = base.baseSession("testing")
|
||||
if os.path.exists(os.path.join(paths.config_path(), "testing")) == False:
|
||||
os.mkdir(os.path.join(paths.config_path(), "testing"))
|
||||
self.session.get_configuration()
|
||||
|
||||
def tearDown(self):
|
||||
""" Removes the previously configured session. """
|
||||
session_folder = os.path.join(paths.config_path(), "testing")
|
||||
if os.path.exists(session_folder):
|
||||
shutil.rmtree(session_folder)
|
||||
|
||||
def generate_dataset(self):
|
||||
""" Generates a sample dataset"""
|
||||
dataset = dict(home_timeline=["message" for i in range(10000)], mentions_timeline=["mention" for i in range(20000)])
|
||||
return dataset
|
||||
|
||||
### Testing database being read from disk.
|
||||
|
||||
def test_cache_in_disk_unlimited_size(self):
|
||||
""" Tests cache database being read from disk, storing the whole datasets. """
|
||||
dataset = self.generate_dataset()
|
||||
self.session.settings["general"]["load_cache_in_memory"] = False
|
||||
self.session.settings["general"]["persist_size"] = -1
|
||||
self.session.load_persistent_data()
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
self.session.save_persistent_data()
|
||||
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(len(self.session.db.get("home_timeline")), 10000)
|
||||
self.assertEquals(len(self.session.db.get("mentions_timeline")), 20000)
|
||||
self.session.db.close()
|
||||
|
||||
def test_cache_in_disk_limited_dataset(self):
|
||||
""" Tests wether the cache stores only the amount of items we ask it to store. """
|
||||
dataset = self.generate_dataset()
|
||||
self.session.settings["general"]["load_cache_in_memory"] = False
|
||||
self.session.settings["general"]["persist_size"] = 100
|
||||
self.session.load_persistent_data()
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
|
||||
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
|
||||
# Might cause an out of sync error between the GUI lists and the database.
|
||||
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
|
||||
self.session.save_persistent_data()
|
||||
self.session.db = dict()
|
||||
self.session.load_persistent_data()
|
||||
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(len(self.session.db.get("home_timeline")), 100)
|
||||
self.assertEquals(len(self.session.db.get("mentions_timeline")), 100)
|
||||
self.session.db.close()
|
||||
|
||||
def test_cache_in_disk_limited_dataset_unreversed(self):
|
||||
"""Test if the cache is saved properly in unreversed buffers, when newest items are at the end of the list. """
|
||||
dataset = dict(home_timeline=[i for i in range(20)], mentions_timeline=[i for i in range(20)])
|
||||
self.session.settings["general"]["load_cache_in_memory"] = False
|
||||
self.session.settings["general"]["persist_size"] = 10
|
||||
self.session.load_persistent_data()
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
|
||||
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
|
||||
# Might cause an out of sync error between the GUI lists and the database.
|
||||
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
|
||||
self.session.save_persistent_data()
|
||||
self.session.db = dict()
|
||||
self.session.load_persistent_data()
|
||||
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[0], 10)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[0], 10)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[-1], 19)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 19)
|
||||
self.session.db.close()
|
||||
|
||||
def test_cache_in_disk_limited_dataset_reversed(self):
|
||||
"""Test if the cache is saved properly in reversed buffers, when newest items are at the start of the list. """
|
||||
dataset = dict(home_timeline=[i for i in range(19, -1, -1)], mentions_timeline=[i for i in range(19, -1, -1)])
|
||||
self.session.settings["general"]["load_cache_in_memory"] = False
|
||||
self.session.settings["general"]["persist_size"] = 10
|
||||
self.session.settings["general"]["reverse_timelines"] = True
|
||||
self.session.load_persistent_data()
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
|
||||
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
|
||||
# Might cause an out of sync error between the GUI lists and the database.
|
||||
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
|
||||
self.session.save_persistent_data()
|
||||
self.session.db = dict()
|
||||
self.session.load_persistent_data()
|
||||
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[0], 19)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[0], 19)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[-1], 10)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 10)
|
||||
self.session.db.close()
|
||||
|
||||
### Testing database being loaded into memory. Those tests should give the same results than before
|
||||
### but as we have different code depending whether we load db into memory or read it from disk,
|
||||
### We need to test this anyways.
|
||||
def test_cache_in_memory_unlimited_size(self):
|
||||
""" Tests cache database being loaded in memory, storing the whole datasets. """
|
||||
dataset = self.generate_dataset()
|
||||
self.session.settings["general"]["load_cache_in_memory"] = True
|
||||
self.session.settings["general"]["persist_size"] = -1
|
||||
self.session.load_persistent_data()
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
self.session.save_persistent_data()
|
||||
self.session.db = dict()
|
||||
self.session.load_persistent_data()
|
||||
self.assertIsInstance(self.session.db, dict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(len(self.session.db.get("home_timeline")), 10000)
|
||||
self.assertEquals(len(self.session.db.get("mentions_timeline")), 20000)
|
||||
|
||||
def test_cache_in_memory_limited_dataset(self):
|
||||
""" Tests wether the cache stores only the amount of items we ask it to store, when loaded in memory. """
|
||||
dataset = self.generate_dataset()
|
||||
self.session.settings["general"]["load_cache_in_memory"] = True
|
||||
self.session.settings["general"]["persist_size"] = 100
|
||||
self.session.load_persistent_data()
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
self.session.save_persistent_data()
|
||||
self.session.db = dict()
|
||||
self.session.load_persistent_data()
|
||||
self.assertIsInstance(self.session.db, dict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(len(self.session.db.get("home_timeline")), 100)
|
||||
self.assertEquals(len(self.session.db.get("mentions_timeline")), 100)
|
||||
|
||||
def test_cache_in_memory_limited_dataset_unreversed(self):
|
||||
"""Test if the cache is saved properly when loaded in memory in unreversed buffers, when newest items are at the end of the list. """
|
||||
dataset = dict(home_timeline=[i for i in range(20)], mentions_timeline=[i for i in range(20)])
|
||||
self.session.settings["general"]["load_cache_in_memory"] = True
|
||||
self.session.settings["general"]["persist_size"] = 10
|
||||
self.session.load_persistent_data()
|
||||
self.assertTrue(len(self.session.db)==1)
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
self.session.save_persistent_data()
|
||||
self.session.db = dict()
|
||||
self.session.load_persistent_data()
|
||||
self.assertIsInstance(self.session.db, dict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[0], 10)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[0], 10)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[-1], 19)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 19)
|
||||
|
||||
def test_cache_in_memory_limited_dataset_reversed(self):
|
||||
"""Test if the cache is saved properly in reversed buffers, when newest items are at the start of the list. This test if for db read into memory. """
|
||||
dataset = dict(home_timeline=[i for i in range(19, -1, -1)], mentions_timeline=[i for i in range(19, -1, -1)])
|
||||
self.session.settings["general"]["load_cache_in_memory"] = True
|
||||
self.session.settings["general"]["persist_size"] = 10
|
||||
self.session.settings["general"]["reverse_timelines"] = True
|
||||
self.session.load_persistent_data()
|
||||
self.session.db["home_timeline"] = dataset["home_timeline"]
|
||||
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
|
||||
self.session.save_persistent_data()
|
||||
self.session.db = dict()
|
||||
self.session.load_persistent_data()
|
||||
self.assertIsInstance(self.session.db, dict)
|
||||
self.assertTrue(self.session.db.get("home_timeline") != None)
|
||||
self.assertTrue(self.session.db.get("mentions_timeline") != None)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[0], 19)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[0], 19)
|
||||
self.assertEquals(self.session.db.get("home_timeline")[-1], 10)
|
||||
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 10)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -1,13 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import glob
|
||||
import os.path
|
||||
import platform
|
||||
|
||||
def find_datafiles():
|
||||
system = platform.system()
|
||||
if system == 'Windows':
|
||||
file_ext = '*.exe'
|
||||
else:
|
||||
file_ext = '*.sh'
|
||||
path = os.path.abspath(os.path.join(__path__[0], 'bootstrappers', file_ext))
|
||||
return [('', glob.glob(path))]
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import application
|
||||
from . import update
|
||||
import platform
|
||||
import logging
|
||||
import output
|
||||
from requests.exceptions import ConnectionError
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user