Compare commits

...

60 Commits
v0.81 ... 0.83

Author SHA1 Message Date
Jose Manuel Delicado
990ddf64f5 Updated version to 0.83. Added romanian documentation 2016-07-24 10:59:41 +02:00
ff529eacb4 Updated russian and turkish updates 2016-07-24 02:51:30 -05:00
Jose Manuel Delicado
238607bbe7 Updated pa.c format specification to version 3.3. Now setup.py collects all documentation, including license.txt 2016-07-24 00:31:45 +02:00
0aa51d9529 Updated contributors list 2016-07-23 16:54:53 -05:00
3815ce0a67 Enter will create a new tweet in trends buffers 2016-07-23 16:40:40 -05:00
5e91808b73 Added russian to documentation translations 2016-07-23 16:39:59 -05:00
08259cdf16 Updated program translations 2016-07-23 16:38:53 -05:00
0bbb3afde0 Now is possible to update a conversation 2016-07-23 16:24:06 -05:00
3c0110528f The documentation optino in the sys tray icon is enabled and working 2016-07-23 15:43:38 -05:00
18b52e8909 Fixed load of new items in the visible timeline with inverted buffers 2016-07-20 04:47:39 -05:00
037cfec91a Fixed some shorcuts for dialogs 2016-07-19 09:30:43 -05:00
695b35031e Fixed a bug with long tweets when posting 2016-07-19 09:06:43 -05:00
f9d869e824 Fixed a few issues with long tweets 2016-07-18 09:56:22 -05:00
c1c001ad96 Merge branch 'next-gen' of http://manuelcortez.net:1337/twblue/twblue into next-gen 2016-07-15 12:41:07 -05:00
d768afc329 Image uploader has been rewritten. Images can have description when uploading 2016-07-15 12:29:48 -05:00
Jose Manuel Delicado
4186f1a3e6 Merge branch 'next-gen' of manuelcortez.net:twblue/twblue into next-gen 2016-07-03 20:58:15 +02:00
Jose Manuel Delicado
2eac158a39 Updated Windows dependencies. 2016-07-03 20:57:24 +02:00
1ee629d731 Improving quoted retweets 2016-06-28 10:41:13 -05:00
5590ab47ee Possibly fixes errors with duplicates and focus change in the list 2016-06-28 10:11:56 -05:00
b555ed736c Modified .gitignore 2016-06-27 20:52:51 -05:00
da39f40048 Changed way of detecting quoted statuses. Parsing improvements in tweet displayer 2016-06-27 09:44:36 -05:00
b1cf1c5590 Fixed some things related to twishort's tweets 2016-06-25 19:48:21 -05:00
48232a6cf8 Added set_description to twython API 2016-06-25 17:42:25 -05:00
3bc92af55f Removed sessions when configuration is malformed 2016-05-13 12:09:38 -05:00
Jose Manuel Delicado
5e9218b072 Updated dependencies. Updated documentation about how to build the pa.c version 2016-05-09 10:17:32 +02:00
Jose Manuel Delicado
65f860ceef Installer: upgraded all version strings to 0.82 2016-05-08 12:34:06 +02:00
Jose Manuel Delicado
b9b8145bca Added function com_path() to paths.py. This function is now used in libloader/com.py. Added winpaths to the easy_install example in readme.md. Updated application.py in doc folder. 2016-05-08 11:54:10 +02:00
Jose Manuel Delicado
7fab6bcf54 setup.py: retrieve cacert.pem from requests package. Create application compressed archive. Added a fix for requests to support cacert.pem in different locations. 2016-05-08 00:08:40 +02:00
Jose Manuel Delicado
7ad5e6fa37 Goodbye, Pycurl. Now TWBlue uses requests and requests-based packages for all http connections. Remember to run git submodule update before submiting any commits 2016-05-07 19:26:40 +02:00
Jose Manuel Delicado
f6fec67d52 Small fix in audio uploader 2016-05-06 18:23:26 +02:00
Jose Manuel Delicado
b3cac85c4e AudioUploader: some code cleaning, optimizations and bug fixes 2016-05-06 17:38:17 +02:00
Jose Manuel Delicado
ff49bd2488 extras/audioUploader/transfer.py: use pycurl.XFERINFOFUNCTION; PROGRESSFUNCTION is deprecated since libcurl 7.32. Only convert_bytes is imported from utils module. 2016-05-06 10:24:01 +02:00
Jose Manuel Delicado
45f23a4c8a To protect users privacy, TwUp has been placed after SNDUp in audio services list 2016-05-05 19:15:13 +02:00
Jose Manuel Delicado
a67a5e6264 Updated Windows dependencies. Remember to run git submodule update before submiting new commits. 2016-05-01 10:35:05 +02:00
8988d63f33 Fixed audio upload dialog 2016-04-30 06:16:16 -05:00
2268619101 some improvements in long tweets 2016-04-28 13:54:06 -05:00
a312b7f63c Integrate check for updates at startup in the general tab 2016-04-27 16:33:51 -05:00
92d803717f Added Tweet deletion based in streaming events 2016-04-16 13:31:37 -05:00
2124f6c60b Merge pull request #70 from TWBlueQS/next-gen
Added version indication on the issue-reporting system
2016-04-16 11:31:23 -05:00
29c87dbd3f Merge pull request #79 from codeofdusk/postabandon1
post-abandonment : stage I
2016-04-16 11:29:05 -05:00
1ea3c5d23b Merge pull request #78 from codeofdusk/next-gen
English cleanup
2016-04-16 11:25:02 -05:00
Bill Dengler
547f9393b9 Post-abandonment stage I (add tab to settings, add option to check for updates on app launch). Fixed #76 2016-04-15 18:08:50 -04:00
Bill Dengler
16b34c827b Update readme.
Clean up readme.

change favorite to like in README.
2016-04-15 16:57:50 -04:00
Bill Dengler
56f0f37f39 Fix soundnotes.
Fix soundnotes.

Fix soundnotes.

Add information to soundnotes.

Fixed soundnotes.
2016-04-15 16:57:40 -04:00
3e9143d607 Display direct message dialog properly. Fixed #75 2016-04-11 08:21:29 -05:00
da07859138 When session mute is enabled, you shouldn't hear tl's and lists' updates 2016-04-02 06:57:50 -06:00
dfc2b605f5 Added audio playback from soundcloud 2016-04-02 03:10:29 -06:00
8badd3987a Storage: Removed clean the local database before being deleted 2016-04-02 02:59:59 -06:00
7139a2bcb3 Conversations: Ignores deleted tweets and 404 errors 2016-04-01 08:55:30 -06:00
c4e2c3b57a Fixed photo upload when posting tweets 2016-03-29 16:09:44 -06:00
ed95270d3b Updated twython 2016-03-29 16:09:09 -06:00
edd45a1adf Image description is shown in the view tweets dialogue if is possible 2016-03-29 15:12:16 -06:00
f24d5fec4e Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2016-03-28 17:35:57 -06:00
a9b47bb1a4 Tweets made from Rossiyskaya Gazeta don't break the tweets' viewer anymore 2016-03-28 17:35:00 -06:00
8849ce9039 Removed pocket from configuration 2016-03-28 17:03:36 -06:00
Jose Manuel Delicado
8b4f16ef84 Updated version to 0.82. Stable.json: updated version to 0.81 2016-03-28 13:16:25 +02:00
Iván Novegil
70169a2a4a Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2016-01-31 12:18:19 +01:00
Iván Novegil
0875b7ef92 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2016-01-22 18:08:11 +01:00
Iván Novegil
e3e4fa42db Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2016-01-20 21:11:33 +01:00
Iván Novegil
6e0c6de0af Now the application indicates the user's version and if it's snapshot or not when reporting an issue trough the issue-reporter 2016-01-20 21:04:33 +01:00
77 changed files with 10167 additions and 11925 deletions

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@ src/Microsoft.VC90.CRT
src/Microsoft.VC90.MFC src/Microsoft.VC90.MFC
src/launcher.bat src/launcher.bat
src/sounds/iOs src/sounds/iOs
release-snapshot/ release-snapshot/
src/com_cache/

View File

@@ -1,13 +1,12 @@
TWBlue - TWBlue -
====== ======
Copyright (C) 2015. [Technow S.L.](https://www.technow.es) Copyright (C) 2016. [Technow S.L.](https://www.technow.es)
TW Blue is an app designed to use Twitter simply and efficiently while using minimal system resources. TW Blue is an app designed to use Twitter simply and efficiently while using minimal system resources.
With this app youll have access to twitter features such as: With this app youll have access to twitter features such as:
* Create, reply to, retweet and delete tweets, * Create, reply to, like, retweet and delete tweets,
* Add and remove tweets from favourites,
* Send and delete direct messages, * Send and delete direct messages,
* See your friends and followers, * See your friends and followers,
* Follow, unfollow, block and report users as spam, * Follow, unfollow, block and report users as spam,
@@ -18,9 +17,9 @@ With this app youll have access to twitter features such as:
See [TWBlue's webpage](http://twblue.es) for more details. See [TWBlue's webpage](http://twblue.es) for more details.
## Using TWBlue from sources ## Running TWBlue from source
This document describes how to run tw blue from source, and, after that, how to build a binary version, which doesn't need Python and the other dependencies to run. 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.
### Required dependencies. ### Required dependencies.
@@ -36,7 +35,6 @@ Although most dependencies can be found in the windows-dependencies directory, w
If you want to build both x86 and x64 binaries, you can install python x86 to C:\python27 and python x64 to C:\python27x64, for example. If you want to build both x86 and x64 binaries, you can install python x86 to C:\python27 and python x64 to C:\python27x64, for example.
* [wxPython](http://www.wxpython.org) for Python 2.7, version 3.0.2.0 * [wxPython](http://www.wxpython.org) for Python 2.7, version 3.0.2.0
* [Python windows extensions (pywin32)](http://www.sourceforge.net/projects/pywin32/) for python 2.7, build 220 * [Python windows extensions (pywin32)](http://www.sourceforge.net/projects/pywin32/) for python 2.7, build 220
* [Pycurl](http://pycurl.sourceforge.net) 7.21.5 for Python 2.7: [downloads](https://pypi.python.org/pypi/pycurl/7.21.5)
* [PyEnchant,](http://pythonhosted.org/pyenchant/) version 1.6.6. * [PyEnchant,](http://pythonhosted.org/pyenchant/) version 1.6.6.
x64 version has been built by TWBlue developers, so you only will find it in windows-dependencies folder x64 version has been built by TWBlue developers, so you only will find it in windows-dependencies folder
@@ -48,7 +46,7 @@ To build a binary version:
#### Dependencies that must be installed using easy_install #### Dependencies that must be installed using easy_install
setuptools install a script, called easy_install. You can find it in the python scripts directory. To install packages using easy_install, you have to navigate to the scripts directory using a command prompt, for example: setuptools installs a script, called easy_install. You can find it in the python scripts directory. To install packages using easy_install, you have to navigate to the scripts directory using a command prompt, for example:
cd C:\python27x64\scripts cd C:\python27x64\scripts
@@ -64,6 +62,7 @@ setuptools install a script, called easy_install. You can find it in the python
* pypubsub * pypubsub
* configobj * configobj
* requests-oauthlib * requests-oauthlib
* requests-toolbelt
* future * future
* pygeocoder * pygeocoder
* suds * suds
@@ -74,7 +73,7 @@ setuptools install a script, called easy_install. You can find it in the python
easy_install will automatically get the additional libraries that these packages need to work properly. easy_install will automatically get the additional libraries that these packages need to work properly.
Run the following command to quickly install and upgrade all packages and their dependencies: Run the following command to quickly install and upgrade all packages and their dependencies:
easy_install -Z --upgrade six configobj goslate markdown future suds requests oauthlib requests-oauthlib pypubsub pygeocoder arrow python-dateutil futures markdown microsofttranslator easy_install -Z --upgrade six configobj goslate markdown future suds requests oauthlib requests-oauthlib requests-toolbelt pypubsub pygeocoder arrow==0.6 python-dateutil futures markdown microsofttranslator winpaths
#### Other dependencies #### Other dependencies
@@ -91,8 +90,11 @@ This dependency has been built using pure basic 4.61. Its source can be found at
#### Dependencies required to build the portableApps.com format archive #### Dependencies required to build the portableApps.com format archive
* [NSIS Unicode Portable,](http://portableapps.com/apps/development/nsis_portable) version 2.46.5 rev 3
* [PortableApps.com Launcher,](http://portableapps.com/apps/development/portableapps.com_launcher) version 2.2 * [PortableApps.com Launcher,](http://portableapps.com/apps/development/portableapps.com_launcher) version 2.2
* [PortableApps.com Installer,](http://portableapps.com/apps/development/portableapps.com_installer) version 3.0.20 * [PortableApps.com Installer,](http://portableapps.com/apps/development/portableapps.com_installer) version 3.1.3
Important! Install these 3 apps into the same folder, otherwise you won't be able to build the pa.c version. For example: D:\portableApps\NSISPortable, D:\PortableApps\PortableApps.com installer, ...
### Running TW Blue from source ### Running TW Blue from source
@@ -121,7 +123,7 @@ To build it, run the following command from the src folder:
### Building an installer ### Building an installer
If you want to install TWBlue in your computer, you must create the installer first. Follow these steps: If you want to install TWBlue on your computer, you must create the installer first. Follow these steps:
* Navigate to the src directory, and create a binary version for x86: C:\python27\python setup.py py2exe * Navigate to the src directory, and create a binary version for x86: C:\python27\python setup.py py2exe
* Move the dist directory to the scripts folder in this repo, and rename it to twblue * Move the dist directory to the scripts folder in this repo, and rename it to twblue
@@ -136,7 +138,7 @@ Run the gen_pot.bat file, located in the tools directory. Your python installati
### How to build the portableApps.com archive ### How to build the portableApps.com archive
If you want to have TWBlue in your PortableApps.com platform, follow these steps: If you want to have TWBlue on your PortableApps.com platform, follow these steps:
* Navigate to the src directory, and create a binary version for x86: C:\python27\python setup.py py2exe * Navigate to the src directory, and create a binary version for x86: C:\python27\python setup.py py2exe
* Move the dist directory to the misc\pa.c format\app folder in this repo, and rename it to twblue * Move the dist directory to the misc\pa.c format\app folder in this repo, and rename it to twblue

View File

@@ -36,4 +36,10 @@ Sussan Leiva
Brian Hartgen Brian Hartgen
PEDRO REINA COLOBON PEDRO REINA COLOBON
Moora-Moora Arrilla Moora-Moora Arrilla
Blake Oliver Blake Oliver
Steffen Schultz
Riku
Burak Yüksek
florian Ionașcu
Christian Leo Mameli
Natalia Hedlund (Наталья Хедлунд)

View File

@@ -2,6 +2,17 @@
name = 'TWBlue' name = 'TWBlue'
snapshot = False snapshot = False
if snapshot == False: if snapshot == False:
version = "0.80" version = "0.83"
update_url = 'http://twblue.es/updates/twblue_ngen.json'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json'
else: else:
version = "7" version = "10.99"
update_url = 'http://twblue.es/updates/snapshots_ngen.json'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/snapshots.json'
author = u"Manuel Cortéz"
authorEmail = "manuel@manuelcortez.net"
copyright = u"Copyright (C) 2015, Technow S.L. \nCopyright (C) 2013-2015, Manuel cortéz."
description = unicode(name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features.")
translators = [u"Bryner Villalobos, Bill Dengler (English)", u"Mohammed Al Shara (Arabic)", u"Joan Rabat, Juan Carlos Rivilla (Catalan)", u"Manuel cortéz (Spanish)", u"Sukil Etxenike Arizaleta (Basque)", u"Jani Kinnunen (finnish)", u"Rémy Ruiz (French)", u"Juan Buño (Galician)", u"Steffen Schultz (German)", u"Robert Osztolykan (Hungarian)", u"Paweł Masarczyk (Polish)", u"Odenilton Júnior Santos (Portuguese)", u"Alexander Jaszyn (Russian)", u"Burak (Turkish)"]
url = u"http://twblue.es"
report_bugs_url = "http://twblue.es/bugs/api/soap/mantisconnect.php?wsdl"

View File

@@ -7,7 +7,7 @@ languageHandler.setLanguage("en")
import strings import strings
# the list of supported language codes of TW Blue # the list of supported language codes of TW Blue
languages = ["en", "es", "fr", "de", "it", "gl", "ja"] languages = ["en", "es", "fr", "de", "it", "gl", "ja", "ru", "ro"]
#"eu", "ar", "ca", "es", "fi", "fr", "gl", "hu", "it", "pl", "pt", "ru", "tr"] #"eu", "ar", "ca", "es", "fi", "fr", "gl", "hu", "it", "pl", "pt", "ru", "tr"]
def generate_document(language): def generate_document(language):

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[Format] [Format]
Type=PortableApps.comFormat Type=PortableApps.comFormat
Version=3.0 Version=3.3
[Details] [Details]
Name=tw blue portable Name=tw blue portable
@@ -20,8 +20,8 @@ CommercialUse=true
EULAVersion=2 EULAVersion=2
[Version] [Version]
PackageVersion=0.80.0.0 PackageVersion=0.83.0.0
DisplayVersion=0.80 DisplayVersion=0.83
[Control] [Control]
Icons=1 Icons=1

View File

@@ -13,9 +13,9 @@ SetCompressor /solid lzma
SetDatablockOptimize on SetDatablockOptimize on
VIAddVersionKey ProductName "TWBlue" VIAddVersionKey ProductName "TWBlue"
VIAddVersionKey LegalCopyright "Copyright 2016 Manuel Cortéz." VIAddVersionKey LegalCopyright "Copyright 2016 Manuel Cortéz."
VIAddVersionKey ProductVersion "0.81" VIAddVersionKey ProductVersion "0.83"
VIAddVersionKey FileVersion "0.81" VIAddVersionKey FileVersion "0.83"
VIProductVersion "0.81.0.0" VIProductVersion "0.83.0.0"
!insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_WELCOME
!define MUI_LICENSEPAGE_RADIOBUTTONS !define MUI_LICENSEPAGE_RADIOBUTTONS
!insertmacro MUI_PAGE_LICENSE "license.txt" !insertmacro MUI_PAGE_LICENSE "license.txt"
@@ -69,10 +69,10 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "D
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.80" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.83"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "http://twblue.es" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "http://twblue.es"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 80 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 82
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1
SectionEnd SectionEnd

View File

@@ -39,6 +39,3 @@ spelling_language = string(default="")
save_followers_in_autocompletion_db = boolean(default=False) save_followers_in_autocompletion_db = boolean(default=False)
save_friends_in_autocompletion_db = boolean(default=False) save_friends_in_autocompletion_db = boolean(default=False)
twishort_enabled = boolean(default=False) twishort_enabled = boolean(default=False)
[services]
pocket_access_token = string(default="")

View File

@@ -15,6 +15,7 @@ speak_ready_msg = boolean(default=True)
log_level = string(default="error") log_level = string(default="error")
load_keymap = string(default="default.keymap") load_keymap = string(default="default.keymap")
donation_dialog_displayed = boolean(default=False) donation_dialog_displayed = boolean(default=False)
check_for_updates = boolean(default=True)
[proxy] [proxy]
server = string(default="") server = string(default="")

View File

@@ -2,7 +2,7 @@
name = 'TWBlue' name = 'TWBlue'
snapshot = False snapshot = False
if snapshot == False: if snapshot == False:
version = "0.81" version = "0.83"
update_url = 'http://twblue.es/updates/twblue_ngen.json' update_url = 'http://twblue.es/updates/twblue_ngen.json'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json' mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json'
else: else:

View File

@@ -10,7 +10,7 @@ def convert_audioboom(url):
audio_id = url.split('.com/')[-1] audio_id = url.split('.com/')[-1]
return 'https://audioboom.com/%s.mp3' % audio_id return 'https://audioboom.com/%s.mp3' % audio_id
@matches_url ('http://soundcloud.com/') @matches_url ('https://soundcloud.com/')
def convert_soundcloud (url): def convert_soundcloud (url):
client_id = "df8113ca95c157b6c9731f54b105b473" client_id = "df8113ca95c157b6c9731f54b105b473"
permalink = urllib.urlopen ('http://api.soundcloud.com/resolve.json?client_id=%s&url=%s' %(client_id, url)) permalink = urllib.urlopen ('http://api.soundcloud.com/resolve.json?client_id=%s&url=%s' %(client_id, url))

File diff suppressed because it is too large Load Diff

38
src/controller/attach.py Normal file
View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
import os
import widgetUtils
import logging
from wxUI.dialogs import attach as gui
log = logging.getLogger("controller.attach")
class attach(object):
def __init__(self):
self.attachments = list()
self.dialog = gui.attachDialog()
widgetUtils.connect_event(self.dialog.photo, widgetUtils.BUTTON_PRESSED, self.upload_image)
widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_attachment)
self.dialog.get_response()
log.debug("Attachments controller started.")
def upload_image(self, *args, **kwargs):
image, description = self.dialog.get_image()
if image != None:
imageInfo = {"type": "photo", "file": image, "description": description}
log.debug("Image data to upload: %r" % (imageInfo,))
self.attachments.append(imageInfo)
info = [_(u"Photo"), description]
self.dialog.attachments.insert_item(False, *info)
self.dialog.remove.Enable(True)
def remove_attachment(self, *args, **kwargs):
current_item = self.dialog.attachments.get_selected()
log.debug("Removing item %d" % (current_item,))
if current_item == -1: current_item = 0
self.attachments.pop(current_item)
self.dialog.attachments.remove_item(current_item)
self.check_remove_status()
log.debug("Removed")
def check_remove_status(self):
if len(self.attachments) == 0 and self.dialog.attachments.get_count() == 0:
self.dialog.remove.Enable(False)

View File

@@ -49,6 +49,7 @@ class bufferController(object):
def get_event(self, ev): def get_event(self, ev):
""" Catches key presses in the WX interface and generate the corresponding event names."""
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio" if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio"
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url" elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url"
elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down" elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
@@ -136,16 +137,25 @@ class bufferController(object):
self.session.settings["mysc"]["twishort_enabled"] = tweet.message.long_tweet.GetValue() self.session.settings["mysc"]["twishort_enabled"] = tweet.message.long_tweet.GetValue()
text = tweet.message.get_text() text = tweet.message.get_text()
if len(text) > 140 and tweet.message.get("long_tweet") == True: if len(text) > 140 and tweet.message.get("long_tweet") == True:
if tweet.image == None: if not hasattr(tweet, "attachments"):
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text) text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text)
else: else:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1) text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1)
if tweet.image == None: if not hasattr(tweet, "attachments") or len(tweet.attachments) == 0:
call_threaded(self.session.api_call, call_name="update_status", status=text) call_threaded(self.session.api_call, call_name="update_status", status=text)
else: else:
call_threaded(self.session.api_call, call_name="update_status_with_media", status=text, media=tweet.image) call_threaded(self.post_with_media, text=text, attachments=tweet.attachments)
if hasattr(tweet.message, "destroy"): tweet.message.destroy() if hasattr(tweet.message, "destroy"): tweet.message.destroy()
def post_with_media(self, text, attachments):
media_ids = []
for i in attachments:
photo = open(i["file"], "rb")
img = self.session.twitter.twitter.upload_media(media=photo)
self.session.twitter.twitter.set_description(media_id=img["media_id"], alt_text=dict(text=i["description"]))
media_ids.append(img["media_id"])
self.session.twitter.twitter.update_status(status=text, media_ids=media_ids)
def save_positions(self): def save_positions(self):
try: try:
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected() self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
@@ -200,7 +210,7 @@ class accountPanel(bufferController):
class emptyPanel(bufferController): class emptyPanel(bufferController):
def __init__(self, parent, name, account): def __init__(self, parent, name, account):
super(emptyPanel, self).__init__(parent, None, name) super(emptyPanel, self).__init__(parent=parent)
log.debug("Initializing buffer %s, account %s" % (name, account,)) log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.emptyPanel(parent, name) self.buffer = buffers.emptyPanel(parent, name)
self.type = self.buffer.type self.type = self.buffer.type
@@ -242,11 +252,11 @@ class baseBufferController(bufferController):
tweet = self.get_right_tweet() tweet = self.get_right_tweet()
tweetsList = [] tweetsList = []
tweet_id = tweet["id"] tweet_id = tweet["id"]
uri = None message = None
if tweet.has_key("long_uri"): if tweet.has_key("message"):
uri = tweet["long_uri"] message = tweet["message"]
try: try:
tweet = self.session.twitter.twitter.show_status(id=tweet_id) tweet = self.session.twitter.twitter.show_status(id=tweet_id, include_ext_alt_text=True)
urls = utils.find_urls_in_text(tweet["text"]) urls = utils.find_urls_in_text(tweet["text"])
for url in range(0, len(urls)): for url in range(0, len(urls)):
try: tweet["text"] = tweet["text"].replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"]) try: tweet["text"] = tweet["text"].replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"])
@@ -254,14 +264,13 @@ class baseBufferController(bufferController):
except TwythonError as e: except TwythonError as e:
utils.twitter_error(e) utils.twitter_error(e)
return return
if uri != None: if message != None:
tweet["text"] = twishort.get_full_text(uri) tweet["message"] = message
l = tweets.is_long(tweet) l = tweets.is_long(tweet)
while l != False: while l != False:
tweetsList.append(tweet) tweetsList.append(tweet)
id = tweets.get_id(l)
try: try:
tweet = self.session.twitter.twitter.show_status(id=id) tweet = self.session.twitter.twitter.show_status(id=l, include_ext_alt_text=True)
urls = utils.find_urls_in_text(tweet["text"]) urls = utils.find_urls_in_text(tweet["text"])
for url in range(0, len(urls)): for url in range(0, len(urls)):
try: tweet["text"] = tweet["text"].replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"]) try: tweet["text"] = tweet["text"].replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"])
@@ -300,7 +309,9 @@ class baseBufferController(bufferController):
except TwythonError as e: except TwythonError as e:
output.speak(e.message, True) output.speak(e.message, True)
for i in items: for i in items:
if utils.is_allowed(i, self.session.settings["twitter"]["ignored_clients"]) == True: if utils.is_allowed(i, self.session.settings["twitter"]["ignored_clients"]) == True and utils.find_item(i["id"], self.session.db[self.name]) == None:
i = self.session.check_quoted_status(i)
i = self.session.check_long_tweet(i)
elements.append(i) elements.append(i)
if self.session.settings["general"]["reverse_timelines"] == False: if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db[self.name].insert(0, i) self.session.db[self.name].insert(0, i)
@@ -326,6 +337,7 @@ class baseBufferController(bufferController):
if dlg == widgetUtils.YES: if dlg == widgetUtils.YES:
if self.name[:-9] in self.session.settings["other_buffers"]["timelines"]: if self.name[:-9] in self.session.settings["other_buffers"]["timelines"]:
self.session.settings["other_buffers"]["timelines"].remove(self.name[:-9]) self.session.settings["other_buffers"]["timelines"].remove(self.name[:-9])
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
@@ -334,6 +346,7 @@ class baseBufferController(bufferController):
if dlg == widgetUtils.YES: if dlg == widgetUtils.YES:
if self.name[:-9] in self.session.settings["other_buffers"]["favourites_timelines"]: if self.name[:-9] in self.session.settings["other_buffers"]["favourites_timelines"]:
self.session.settings["other_buffers"]["favourites_timelines"].remove(self.name[:-9]) self.session.settings["other_buffers"]["favourites_timelines"].remove(self.name[:-9])
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
@@ -341,7 +354,15 @@ class baseBufferController(bufferController):
output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True) output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True)
return False return False
def remove_tweet(self, id):
if type(self.session.db[self.name]) == dict: return
for i in xrange(0, len(self.session.db[self.name])):
if self.session.db[self.name][i]["id"] == id:
self.session.db[self.name].pop(i)
self.remove_item(i)
def put_items_on_list(self, number_of_items): def put_items_on_list(self, number_of_items):
if number_of_items == 0: return
log.debug("The list contains %d items " % (self.buffer.list.get_count(),)) log.debug("The list contains %d items " % (self.buffer.list.get_count(),))
log.debug("Putting %d items on the list" % (number_of_items,)) log.debug("Putting %d items on the list" % (number_of_items,))
if self.buffer.list.get_count() == 0: if self.buffer.list.get_count() == 0:
@@ -351,11 +372,14 @@ class baseBufferController(bufferController):
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"]) self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
elif self.buffer.list.get_count() > 0: elif self.buffer.list.get_count() > 0:
if self.session.settings["general"]["reverse_timelines"] == False: if self.session.settings["general"]["reverse_timelines"] == False:
for i in self.session.db[self.name][:number_of_items]: items = self.session.db[self.name][len(self.session.db[self.name])-number_of_items:]
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"]) tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"])
self.buffer.list.insert_item(False, *tweet) self.buffer.list.insert_item(False, *tweet)
else: else:
for i in self.session.db[self.name][0:number_of_items]: items = self.session.db[self.name][0:number_of_items]
items.reverse()
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"]) tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"])
self.buffer.list.insert_item(True, *tweet) self.buffer.list.insert_item(True, *tweet)
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),)) log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
@@ -664,6 +688,7 @@ class listBufferController(baseBufferController):
if dlg == widgetUtils.YES: if dlg == widgetUtils.YES:
if self.name[:-5] in self.session.settings["other_buffers"]["lists"]: if self.name[:-5] in self.session.settings["other_buffers"]["lists"]:
self.session.settings["other_buffers"]["lists"].remove(self.name[:-5]) self.session.settings["other_buffers"]["lists"].remove(self.name[:-5])
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
@@ -735,6 +760,7 @@ class peopleBufferController(baseBufferController):
if dlg == widgetUtils.YES: if dlg == widgetUtils.YES:
if self.name[:-10] in self.session.settings["other_buffers"]["followers_timelines"]: if self.name[:-10] in self.session.settings["other_buffers"]["followers_timelines"]:
self.session.settings["other_buffers"]["followers_timelines"].remove(self.name[:-10]) self.session.settings["other_buffers"]["followers_timelines"].remove(self.name[:-10])
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
@@ -743,6 +769,7 @@ class peopleBufferController(baseBufferController):
if dlg == widgetUtils.YES: if dlg == widgetUtils.YES:
if self.name[:-8] in self.session.settings["other_buffers"]["friends_timelines"]: if self.name[:-8] in self.session.settings["other_buffers"]["friends_timelines"]:
self.session.settings["other_buffers"]["friends_timelines"].remove(self.name[:-8]) self.session.settings["other_buffers"]["friends_timelines"].remove(self.name[:-8])
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
@@ -819,7 +846,7 @@ class peopleBufferController(baseBufferController):
# self.buffer.set_list_position() # self.buffer.set_list_position()
elif self.buffer.list.get_count() > 0: elif self.buffer.list.get_count() > 0:
if self.session.settings["general"]["reverse_timelines"] == False: if self.session.settings["general"]["reverse_timelines"] == False:
for i in self.session.db[self.name]["items"][:number_of_items]: for i in self.session.db[self.name]["items"][len(self.session.db[self.name]["items"])-number_of_items:]:
tweet = self.compose_function(i, self.session.db) tweet = self.compose_function(i, self.session.db)
self.buffer.list.insert_item(False, *tweet) self.buffer.list.insert_item(False, *tweet)
else: else:
@@ -892,6 +919,7 @@ class searchBufferController(baseBufferController):
if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]: if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]:
self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11]) self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11])
self.timer.cancel() self.timer.cancel()
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
@@ -932,6 +960,7 @@ class searchPeopleBufferController(peopleBufferController):
if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]: if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]:
self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11]) self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11])
self.timer.cancel() self.timer.cancel()
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
@@ -955,10 +984,10 @@ class trendsBufferController(bufferController):
self.get_formatted_message = self.get_message self.get_formatted_message = self.get_message
self.reply = self.search_topic self.reply = self.search_topic
def start_stream(self): def start_stream(self, mandatory=False):
# starts stream every 3 minutes. # starts stream every 3 minutes.
current_time = time.time() current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180: if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory == True:
self.execution_time = current_time self.execution_time = current_time
try: try:
data = self.session.call_paged("get_place_trends", id=self.trendsFor) data = self.session.call_paged("get_place_trends", id=self.trendsFor)
@@ -999,10 +1028,14 @@ class trendsBufferController(bufferController):
if self.name[:-3] in self.session.settings["other_buffers"]["trending_topic_buffers"]: if self.name[:-3] in self.session.settings["other_buffers"]["trending_topic_buffers"]:
self.session.settings["other_buffers"]["trending_topic_buffers"].remove(self.name[:-3]) self.session.settings["other_buffers"]["trending_topic_buffers"].remove(self.name[:-3])
self.timer.cancel() self.timer.cancel()
self.session.db.pop(self.name)
return True return True
elif dlg == widgetUtils.NO: elif dlg == widgetUtils.NO:
return False return False
def url(self, *args, **kwargs):
self.tweet_about_this_trend()
def search_topic(self, *args, **kwargs): def search_topic(self, *args, **kwargs):
topic = self.trends[self.buffer.list.get_selected()]["name"] topic = self.trends[self.buffer.list.get_selected()]["name"]
pub.sendMessage("search", term=topic) pub.sendMessage("search", term=topic)
@@ -1052,10 +1085,10 @@ class trendsBufferController(bufferController):
class conversationBufferController(searchBufferController): class conversationBufferController(searchBufferController):
def start_stream(self, start=False): def start_stream(self, start=False, mandatory=False):
# starts stream every 3 minutes. # starts stream every 3 minutes.
current_time = time.time() current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180: if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory == True:
self.execution_time = current_time self.execution_time = current_time
if start == True: if start == True:
self.statuses = [] self.statuses = []
@@ -1064,7 +1097,10 @@ class conversationBufferController(searchBufferController):
self.ids.append(self.tweet["id"]) self.ids.append(self.tweet["id"])
tweet = self.tweet tweet = self.tweet
while tweet["in_reply_to_status_id"] != None: while tweet["in_reply_to_status_id"] != None:
tweet = self.session.twitter.twitter.show_status(id=tweet["in_reply_to_status_id"]) try:
tweet = self.session.twitter.twitter.show_status(id=tweet["in_reply_to_status_id"])
except TwythonError as err:
break
self.statuses.insert(0, tweet) self.statuses.insert(0, tweet)
self.ids.append(tweet["id"]) self.ids.append(tweet["id"])
if tweet["in_reply_to_status_id"] == None: if tweet["in_reply_to_status_id"] == None:

View File

@@ -119,6 +119,7 @@ class Controller(object):
pub.subscribe(self.manage_item_in_timeline, "item-in-timeline") pub.subscribe(self.manage_item_in_timeline, "item-in-timeline")
pub.subscribe(self.manage_item_in_list, "item-in-list") pub.subscribe(self.manage_item_in_list, "item-in-list")
pub.subscribe(self.restart_streams_, "restart_streams") pub.subscribe(self.restart_streams_, "restart_streams")
pub.subscribe(self.on_tweet_deleted, "tweet-deleted")
widgetUtils.connect_event(self.view, widgetUtils.CLOSE_EVENT, self.exit_) widgetUtils.connect_event(self.view, widgetUtils.CLOSE_EVENT, self.exit_)
def bind_other_events(self): def bind_other_events(self):
@@ -191,6 +192,7 @@ class Controller(object):
widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.update_profile, menuitem=self.systrayIcon.update_profile) widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.update_profile, menuitem=self.systrayIcon.update_profile)
widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.show_hide, menuitem=self.systrayIcon.show_hide) widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.show_hide, menuitem=self.systrayIcon.show_hide)
widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.check_for_updates, menuitem=self.systrayIcon.check_for_updates) widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.check_for_updates, menuitem=self.systrayIcon.check_for_updates)
widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.view_documentation, menuitem=self.systrayIcon.doc)
widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.exit, menuitem=self.systrayIcon.exit) widgetUtils.connect_event(self.systrayIcon, widgetUtils.MENU, self.exit, menuitem=self.systrayIcon.exit)
widgetUtils.connect_event(self.systrayIcon, widgetUtils.TASKBAR_LEFT_CLICK, self.taskbar_left_click) widgetUtils.connect_event(self.systrayIcon, widgetUtils.TASKBAR_LEFT_CLICK, self.taskbar_left_click)
widgetUtils.connect_event(self.systrayIcon, widgetUtils.TASKBAR_RIGHT_CLICK, self.taskbar_right_click) widgetUtils.connect_event(self.systrayIcon, widgetUtils.TASKBAR_RIGHT_CLICK, self.taskbar_right_click)
@@ -1275,7 +1277,7 @@ class Controller(object):
buffer = self.search_buffer("%s-timeline" % (who,), user) buffer = self.search_buffer("%s-timeline" % (who,), user)
if buffer == None: return if buffer == None: return
play_sound = "tweet_timeline.ogg" play_sound = "tweet_timeline.ogg"
if "%s-timeline" % (who,) not in buffer.session.settings["other_buffers"]["muted_buffers"]: if "%s-timeline" % (who,) not in buffer.session.settings["other_buffers"]["muted_buffers"] and buffer.session.settings["sound"]["session_mute"] == False:
self.notify(buffer.session, play_sound=play_sound) self.notify(buffer.session, play_sound=play_sound)
output.speak(_(u"One tweet from %s") % (data["user"]["name"])) output.speak(_(u"One tweet from %s") % (data["user"]["name"]))
buffer.add_new_item(data) buffer.add_new_item(data)
@@ -1284,7 +1286,7 @@ class Controller(object):
buffer = self.search_buffer("%s" % (where,), user) buffer = self.search_buffer("%s" % (where,), user)
if buffer == None: return if buffer == None: return
play_sound = "list_tweet.ogg" play_sound = "list_tweet.ogg"
if "%s" % (where,) not in buffer.session.settings["other_buffers"]["muted_buffers"]: if "%s" % (where,) not in buffer.session.settings["other_buffers"]["muted_buffers"] and buffer.session.settings["sound"]["session_mute"] == False:
self.notify(buffer.session, play_sound=play_sound) self.notify(buffer.session, play_sound=play_sound)
output.speak(_(u"One tweet from %s") % (data["user"]["name"])) output.speak(_(u"One tweet from %s") % (data["user"]["name"]))
buffer.add_new_item(data) buffer.add_new_item(data)
@@ -1484,4 +1486,10 @@ class Controller(object):
output.speak(_(u"Updating buffer...")) output.speak(_(u"Updating buffer..."))
n = bf.start_stream(mandatory=True) n = bf.start_stream(mandatory=True)
if n != None: if n != None:
output.speak(_(u"{0} items retrieved").format(n,)) output.speak(_(u"{0} items retrieved").format(n,))
def on_tweet_deleted(self, data):
id = data["delete"]["status"]["id"]
for i in self.buffers:
if hasattr(i, "remove_tweet") and hasattr(i, "name"):
i.remove_tweet(id)

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
import platform import platform
import attach
system = platform.system() system = platform.system()
import widgetUtils import widgetUtils
import output import output
@@ -32,6 +33,7 @@ class basicTweet(object):
widgetUtils.connect_event(self.message.shortenButton, widgetUtils.BUTTON_PRESSED, self.shorten) widgetUtils.connect_event(self.message.shortenButton, widgetUtils.BUTTON_PRESSED, self.shorten)
widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten) widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten)
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
self.attachments = []
def translate(self, event=None): def translate(self, event=None):
dlg = translator.gui.translateDialog() dlg = translator.gui.translateDialog()
@@ -102,7 +104,7 @@ class basicTweet(object):
self.message.text_focus() self.message.text_focus()
def attach(self, *args, **kwargs): def attach(self, *args, **kwargs):
def completed_callback(): def completed_callback(dlg):
url = dlg.uploaderFunction.get_url() url = dlg.uploaderFunction.get_url()
pub.unsubscribe(dlg.uploaderDialog.update, "uploading") pub.unsubscribe(dlg.uploaderDialog.update, "uploading")
dlg.uploaderDialog.destroy() dlg.uploaderDialog.destroy()
@@ -125,16 +127,9 @@ class tweet(basicTweet):
except AttributeError: pass except AttributeError: pass
def upload_image(self, *args, **kwargs): def upload_image(self, *args, **kwargs):
if self.message.get("upload_image") == _(u"Discard image"): a = attach.attach()
del self.image if len(a.attachments) != 0:
self.image = None self.attachments = a.attachments
output.speak(_(u"Discarded"))
self.message.set("upload_image", _(u"Upload a picture"))
else:
self.image = self.message.get_image()
if self.image != None:
self.message.set("upload_image", _(u"Discard image"))
self.message.text_focus()
def autocomplete_users(self, *args, **kwargs): def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
@@ -165,24 +160,56 @@ class dm(basicTweet):
class viewTweet(basicTweet): class viewTweet(basicTweet):
def __init__(self, tweet, tweetList, is_tweet=True): def __init__(self, tweet, tweetList, is_tweet=True):
""" This represents a tweet displayer. However it could be used for showing something wich is not a tweet, like a direct message or an event.
param tweet: A dictionary that represents a full tweet or a string for non-tweets.
param tweetList: If is_tweet is set to True, this could be a list of quoted tweets.
param is_tweet: True or false, depending wether the passed object is a tweet or not."""
if is_tweet == True: if is_tweet == True:
image_description = []
text = "" text = ""
for i in xrange(0, len(tweetList)): for i in xrange(0, len(tweetList)):
if tweetList[i].has_key("retweeted_status"): # tweets with message keys are longer tweets, the message value is the full messaje taken from twishort.
text = text + "rt @%s: %s\n" % (tweetList[i]["retweeted_status"]["user"]["screen_name"], tweetList[i]["retweeted_status"]["text"]) if tweetList[i].has_key("message") and tweetList[i]["is_quote_status"] == False:
value = "message"
else: else:
text = text + "@%s: %s\n" % (tweetList[i]["user"]["screen_name"], tweetList[i]["text"]) value = "text"
if tweetList[i].has_key("retweeted_status") and tweetList[i]["is_quote_status"] == False:
if tweetList[i].has_key("message") == False:
text = text + "rt @%s: %s\n" % (tweetList[i]["retweeted_status"]["user"]["screen_name"], tweetList[i]["retweeted_status"]["text"])
else:
text = text + "rt @%s: %s\n" % (tweetList[i]["retweeted_status"]["user"]["screen_name"], tweetList[i][value])
else:
text = text + " @%s: %s\n" % (tweetList[i]["user"]["screen_name"], tweetList[i][value])
# tweets with extended_entities could include image descriptions.
if tweetList[i].has_key("extended_entities") and tweetList[i]["extended_entities"].has_key("media"):
for z in tweetList[i]["extended_entities"]["media"]:
if z.has_key("ext_alt_text") and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"])
# set rt and likes counters.
rt_count = str(tweet["retweet_count"]) rt_count = str(tweet["retweet_count"])
favs_count = str(tweet["favorite_count"]) favs_count = str(tweet["favorite_count"])
source = str(re.sub(r"(?s)<.*?>", "", tweet["source"])) # Gets the client from where this tweet was made.
source = str(re.sub(r"(?s)<.*?>", "", tweet["source"].encode("utf-8")))
if text == "": if text == "":
if tweet.has_key("retweeted_status"): if tweet.has_key("message"):
text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], tweet["retweeted_status"]["text"]) value = "message"
else: else:
text = tweet["text"] value = "text"
if tweet.has_key("retweeted_status"):
if tweet.has_key("message") == False:
text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], tweet["retweeted_status"]["text"])
else:
text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], tweet[value])
else:
text = tweet[value]
text = self.clear_text(text) text = self.clear_text(text)
self.message = message.viewTweet(text, rt_count, favs_count,source) if tweet.has_key("extended_entities") and tweet["extended_entities"].has_key("media"):
for z in tweet["extended_entities"]["media"]:
if z.has_key("ext_alt_text") and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"])
self.message = message.viewTweet(text, rt_count, favs_count, source.decode("utf-8"))
self.message.set_title(len(text)) self.message.set_title(len(text))
[self.message.set_image_description(i) for i in image_description]
else: else:
text = tweet text = tweet
self.message = message.viewNonTweet(text) self.message = message.viewNonTweet(text)
@@ -202,5 +229,5 @@ class viewTweet(basicTweet):
urls = utils.find_urls_in_text(text) urls = utils.find_urls_in_text(text)
for i in urls: for i in urls:
if "https://twitter.com/" in i: if "https://twitter.com/" in i:
text = text.replace(i, "") text = text.replace(i, "\n")
return text return text

View File

@@ -64,11 +64,13 @@ class globalSettingsController(object):
self.dialog.set_value("general", "use_invisible_shorcuts", config.app["app-settings"]["use_invisible_keyboard_shorcuts"]) self.dialog.set_value("general", "use_invisible_shorcuts", config.app["app-settings"]["use_invisible_keyboard_shorcuts"])
self.dialog.set_value("general", "disable_sapi5", config.app["app-settings"]["voice_enabled"]) self.dialog.set_value("general", "disable_sapi5", config.app["app-settings"]["voice_enabled"])
self.dialog.set_value("general", "hide_gui", config.app["app-settings"]["hide_gui"]) self.dialog.set_value("general", "hide_gui", config.app["app-settings"]["hide_gui"])
self.dialog.set_value("general", "check_for_updates", config.app["app-settings"]["check_for_updates"])
self.dialog.create_proxy() self.dialog.create_proxy()
self.dialog.set_value("proxy", "server", config.app["proxy"]["server"]) self.dialog.set_value("proxy", "server", config.app["proxy"]["server"])
self.dialog.set_value("proxy", "port", config.app["proxy"]["port"]) self.dialog.set_value("proxy", "port", config.app["proxy"]["port"])
self.dialog.set_value("proxy", "user", config.app["proxy"]["user"]) self.dialog.set_value("proxy", "user", config.app["proxy"]["user"])
self.dialog.set_value("proxy", "password", config.app["proxy"]["password"]) self.dialog.set_value("proxy", "password", config.app["proxy"]["password"])
self.dialog.realize() self.dialog.realize()
self.response = self.dialog.get_response() self.response = self.dialog.get_response()
@@ -92,6 +94,7 @@ class globalSettingsController(object):
config.app["app-settings"]["handle_longtweets"] = self.dialog.get_value("general", "handle_longtweets") config.app["app-settings"]["handle_longtweets"] = self.dialog.get_value("general", "handle_longtweets")
config.app["app-settings"]["play_ready_sound"] = self.dialog.get_value("general", "play_ready_sound") config.app["app-settings"]["play_ready_sound"] = self.dialog.get_value("general", "play_ready_sound")
config.app["app-settings"]["speak_ready_msg"] = self.dialog.get_value("general", "speak_ready_msg") config.app["app-settings"]["speak_ready_msg"] = self.dialog.get_value("general", "speak_ready_msg")
config.app["app-settings"]["check_for_updates"] = self.dialog.get_value("general", "check_for_updates")
if config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"): if config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"):
if self.is_started == True: if self.is_started == True:
self.needs_restart = True self.needs_restart = True

View File

@@ -57,18 +57,16 @@ class audioUploader(object):
url = base_url + '?apikey=' + self.config['sound']['sndup_api_key'] url = base_url + '?apikey=' + self.config['sound']['sndup_api_key']
else: else:
url = base_url url = base_url
self.uploaderFunction = transfer.Upload(field='file', url=url, filename=self.file, completed_callback=completed_callback)
elif self.dialog.get("services") == "TwUp": elif self.dialog.get("services") == "TwUp":
url = "http://api.twup.me/post.json" url = "http://api.twup.me/post.json"
self.uploaderFunction = transfer.Upload(field='file', url=url, filename=self.file, completed_callback=completed_callback) self.uploaderFunction = transfer.Upload(obj=self, field='file', url=url, filename=self.file, completed_callback=completed_callback)
pub.subscribe(self.uploaderDialog.update, "uploading") pub.subscribe(self.uploaderDialog.update, "uploading")
self.uploaderDialog.get_response() self.uploaderDialog.get_response(self.uploaderFunction.perform_threaded)
self.uploaderFunction.perform_threaded()
def get_available_services(self): def get_available_services(self):
services = [] services = []
services.append("TwUp")
services.append("SNDUp") services.append("SNDUp")
services.append("TwUp")
return services return services
def on_pause(self, *args, **kwargs): def on_pause(self, *args, **kwargs):
@@ -116,8 +114,9 @@ class audioUploader(object):
if self.playing: if self.playing:
self._stop() self._stop()
if self.recording != None: if self.recording != None:
self.dialog.disable_control("attach") self.cleanup()
self.dialog.disable_control("play") self.dialog.disable_control("attach")
self.dialog.disable_control("play")
self.file = None self.file = None
self.dialog.enable_control("record") self.dialog.enable_control("record")
self.dialog.enable_control("attach_exists") self.dialog.enable_control("attach_exists")
@@ -174,10 +173,6 @@ class audioUploader(object):
os.remove(self.file) os.remove(self.file)
if hasattr(self, 'wav_file'): if hasattr(self, 'wav_file'):
os.remove(self.wav_file) os.remove(self.wav_file)
del(self.wav_file)
if hasattr(self, 'wav_file') and os.path.exists(self.file):
os.remove(self.file)
def on_attach_exists(self, *args, **kwargs): def on_attach_exists(self, *args, **kwargs):
self.file = self.dialog.get_file() self.file = self.dialog.get_file()

View File

@@ -1,106 +1,68 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pycurl import sys
import sys import threading
import threading import time
import time import logging
import json from utils import convert_bytes
import logging from pubsub import pub
from utils import * log = logging.getLogger("extra.AudioUploader.transfer")
from pubsub import pub from requests_toolbelt.multipart.encoder import MultipartEncoder, MultipartEncoderMonitor
import requests
log = logging.getLogger("extra.AudioUploader.transfer") import os
class Transfer(object): class Upload(object):
def __init__(self, field=None, obj=None, url=None, filename=None, follow_location=True, completed_callback=None, verbose=False, *args, **kwargs):
def __init__(self, url=None, filename=None, follow_location=True, completed_callback=None, verbose=False, *args, **kwargs): super(Upload, self).__init__(*args, **kwargs)
self.url = url self.url=url
self.filename = filename self.filename=filename
log.debug("Uploading audio to %s, filename %s" % (url, filename)) log.debug("Uploading audio to %s, filename %s" % (url, filename))
self.curl = pycurl.Curl() self.start_time = None
self.start_time = None self.completed_callback = completed_callback
self.completed_callback = completed_callback self.background_thread = None
self.background_thread = None self.transfer_rate = 0
self.transfer_rate = 0 self.m = MultipartEncoder(fields={field:(os.path.basename(self.filename), open(self.filename, 'rb'), "application/octet-stream")})
self.curl.setopt(self.curl.PROGRESSFUNCTION, self.progress_callback) self.monitor = MultipartEncoderMonitor(self.m, self.progress_callback)
self.curl.setopt(self.curl.URL, url) self.response=None
self.curl.setopt(self.curl.NOPROGRESS, 0) self.obj=obj
self.curl.setopt(self.curl.HTTP_VERSION, self.curl.CURL_HTTP_VERSION_1_0) self.follow_location=follow_location
self.curl.setopt(self.curl.FOLLOWLOCATION, int(follow_location)) #the verbose parameter is deprecated and will be removed soon
self.curl.setopt(self.curl.VERBOSE, int(verbose))
super(Transfer, self).__init__(*args, **kwargs) def elapsed_time(self):
if not self.start_time:
def elapsed_time(self): return 0
if not self.start_time: return time.time() - self.start_time
return 0
return time.time() - self.start_time def progress_callback(self, monitor):
progress = {}
def progress_callback(self, down_total, down_current, up_total, up_current): progress["total"] = monitor.len
progress = {} progress["current"] = monitor.bytes_read
progress["total"] = up_total if progress["current"] == 0:
progress["current"] = up_current progress["percent"] = 0
# else: self.transfer_rate = 0
# print "Killed function" else:
# return progress["percent"] = int((float(progress["current"]) / progress["total"]) * 100)
if progress["current"] == 0: self.transfer_rate = progress["current"] / self.elapsed_time()
progress["percent"] = 0 progress["speed"] = '%s/s' % convert_bytes(self.transfer_rate)
self.transfer_rate = 0 if self.transfer_rate:
else: progress["eta"] = (progress["total"] - progress["current"]) / self.transfer_rate
progress["percent"] = int((float(progress["current"]) / progress["total"]) * 100) else:
self.transfer_rate = progress["current"] / self.elapsed_time() progress["eta"] = 0
progress["speed"] = '%s/s' % convert_bytes(self.transfer_rate) pub.sendMessage("uploading", data=progress)
if self.transfer_rate:
progress["eta"] = (progress["total"] - progress["current"]) / self.transfer_rate def perform_transfer(self):
else: log.debug("starting upload...")
progress["eta"] = 0 self.start_time = time.time()
pub.sendMessage("uploading", data=progress) self.response=requests.post(url=self.url, data=self.monitor, headers={"Content-Type":self.m.content_type}, allow_redirects=self.follow_location, stream=True)
log.debug("Upload finished.")
def perform_transfer(self): self.complete_transfer()
log.debug("starting upload...")
self.start_time = time.time() def perform_threaded(self, *args, **kwargs):
self.curl.perform() self.background_thread = threading.Thread(target=self.perform_transfer)
self.curl.close() self.background_thread.daemon = True
log.debug("Upload finished.") self.background_thread.start()
self.complete_transfer()
def complete_transfer(self):
def perform_threaded(self): if callable(self.completed_callback):
self.background_thread = threading.Thread(target=self.perform_transfer) self.completed_callback(self.obj)
self.background_thread.daemon = True
self.background_thread.start() def get_url(self):
return self.response.json()['url']
def complete_transfer(self):
if callable(self.completed_callback):
self.curl.close()
self.completed_callback()
class Upload(Transfer):
def __init__(self, field=None, filename=None, *args, **kwargs):
super(Upload, self).__init__(filename=filename, *args, **kwargs)
self.response = dict()
self.curl.setopt(self.curl.POST, 1)
if isinstance(filename, unicode):
local_filename = filename.encode(sys.getfilesystemencoding())
else:
local_filename = filename
self.curl.setopt(self.curl.HTTPPOST, [(field, (self.curl.FORM_FILE, local_filename, self.curl.FORM_FILENAME, filename.encode("utf-8")))])
self.curl.setopt(self.curl.HEADERFUNCTION, self.header_callback)
self.curl.setopt(self.curl.WRITEFUNCTION, self.body_callback)
def header_callback(self, content):
self.response['header'] = content
def body_callback(self, content):
self.response['body'] = content
def get_url(self):
return json.loads(self.response['body'])['url']
class Download(Transfer):
def __init__(self, follow_location=True, *args, **kwargs):
super(Download, self).__init__(*args, **kwargs)
self.download_file = open(self.filename, 'wb')
self.curl.setopt(self.curl.WRITEFUNCTION, self.download_file.write)
def complete_transfer(self):
self.download_file.close()
super(DownloadDialog, self).complete_transfer()

View File

@@ -3,10 +3,10 @@ import wx
from utils import * from utils import *
import widgetUtils import widgetUtils
class TransferDialog(widgetUtils.BaseDialog): class UploadDialog(widgetUtils.BaseDialog):
def __init__(self, filename, *args, **kwargs): def __init__(self, filename, *args, **kwargs):
super(TransferDialog, self).__init__(parent=None, id=wx.NewId(), *args, **kwargs) super(UploadDialog, self).__init__(parent=None, id=wx.NewId(), *args, **kwargs)
self.pane = wx.Panel(self) self.pane = wx.Panel(self)
self.progress_bar = wx.Gauge(parent=self.pane) self.progress_bar = wx.Gauge(parent=self.pane)
fileBox = wx.BoxSizer(wx.HORIZONTAL) fileBox = wx.BoxSizer(wx.HORIZONTAL)
@@ -56,18 +56,6 @@ class TransferDialog(widgetUtils.BaseDialog):
def create_buttons(self): def create_buttons(self):
self.cancel_button = wx.Button(parent=self.pane, id=wx.ID_CANCEL) self.cancel_button = wx.Button(parent=self.pane, id=wx.ID_CANCEL)
def get_response(self): def get_response(self, fn):
self.Show() wx.CallAfter(fn, 0.01)
self.ShowModal()
def destroy(self):
self.Destroy()
class UploadDialog(TransferDialog):
def __init__(self, filename=None, *args, **kwargs):
super(UploadDialog, self).__init__(filename=filename, *args, **kwargs)
class DownloadDialog(TransferDialog):
def __init__(self, *args, **kwargs):
super(Download, self).__init__(*args, **kwargs)

View File

@@ -4,9 +4,10 @@ import sys
import fix_arrow # A few new locales for Three languages in arrow. import fix_arrow # A few new locales for Three languages in arrow.
import fix_urllib3_warnings # Avoiding some SSL warnings related to Twython. import fix_urllib3_warnings # Avoiding some SSL warnings related to Twython.
import fix_win32com import fix_win32com
import fix_requests #fix cacert.pem location for TWBlue binary copies
def setup(): def setup():
fix_arrow.fix() fix_arrow.fix()
if hasattr(sys, "frozen"): if hasattr(sys, "frozen"):
fix_win32com.fix() fix_win32com.fix()
fix_requests.fix()
fix_urllib3_warnings.fix() fix_urllib3_warnings.fix()

10
src/fixes/fix_requests.py Normal file
View File

@@ -0,0 +1,10 @@
from requests import certs, utils, adapters
import paths
def patched_where():
return paths.app_path(u"cacert.pem")
def fix():
certs.where=patched_where
utils.DEFAULT_CA_BUNDLE_PATH=patched_where()
adapters.DEFAULT_CA_BUNDLE_PATH=patched_where()

View File

@@ -47,7 +47,7 @@ class reportBug(object):
issue.project.name = application.name issue.project.name = application.name
issue.project.id = 0 issue.project.id = 0
issue.summary = self.dialog.get("summary"), issue.summary = self.dialog.get("summary"),
issue.description = "Reported by @%s\n\n" % (self.user_name) + self.dialog.get("description") issue.description = "Reported by @%s on version %s (snapshot = %s)\n\n" % (self.user_name, application.version, application.snapshot) + self.dialog.get("description")
# to do: Create getters for category, severity and reproducibility in wx_UI. # to do: Create getters for category, severity and reproducibility in wx_UI.
issue.category = constants.categories[self.dialog.category.GetSelection()] issue.category = constants.categories[self.dialog.category.GetSelection()]
issue.reproducibility.name = constants.reproducibilities[self.dialog.reproducibility.GetSelection()] issue.reproducibility.name = constants.reproducibilities[self.dialog.reproducibility.GetSelection()]

View File

@@ -1,7 +1,7 @@
from pywintypes import com_error from pywintypes import com_error
import win32com import win32com
import paths import paths
win32com.__gen_path__=paths.data_path(u"com_cache") win32com.__gen_path__=paths.com_path()
import sys import sys
import os import os
sys.path.append(os.path.join(win32com.__gen_path__, ".")) sys.path.append(os.path.join(win32com.__gen_path__, "."))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -18,15 +18,10 @@
############################################################ ############################################################
from twitter import utils from twitter import utils
def get_id(url):
return url.split("/")[-1]
def is_long(tweet): def is_long(tweet):
long = False if tweet.has_key("is_quote_status") and tweet["is_quote_status"] == True and tweet.has_key("quoted_status"):
for url in range(0, len(tweet["entities"]["urls"])): return tweet["quoted_status_id"]
if "twitter.com" in tweet["entities"]["urls"][url]["expanded_url"]: return False
long = get_id(tweet["entities"]["urls"][url]["expanded_url"])
return long
def clear_url(tweet): def clear_url(tweet):
urls = utils.find_urls_in_text(tweet["text"]) urls = utils.find_urls_in_text(tweet["text"])

View File

@@ -51,5 +51,4 @@ def create_tweet(user_token, user_secret, text, media=0):
"text": text.encode("utf-8"), "text": text.encode("utf-8"),
"media": media} "media": media}
response = requests.post(url, data=data) response = requests.post(url, data=data)
# print response.json()
return response.json()["text_to_tweet"] return response.json()["text_to_tweet"]

View File

@@ -70,7 +70,8 @@ def setup():
if system == "Windows": if system == "Windows":
if config.app["app-settings"]["donation_dialog_displayed"] == False: if config.app["app-settings"]["donation_dialog_displayed"] == False:
donation() donation()
updater.do_update() if config.app['app-settings']['check_for_updates']:
updater.do_update()
sm = sessionManager.sessionManagerController() sm = sessionManager.sessionManagerController()
sm.fill_list() sm.fill_list()
if len(sm.sessions) == 0: sm.show() if len(sm.sessions) == 0: sm.show()

View File

@@ -69,4 +69,17 @@ def locale_path():
@merge_paths @merge_paths
def sound_path(): def sound_path():
return app_path(u"sounds") return app_path(u"sounds")
@merge_paths
def com_path():
global mode, directory
if mode == "portable":
if directory != None: path = os.path.join(directory, "com_cache")
elif directory == None: path = app_path(u"com_cache")
elif mode == "installed":
path = data_path(u"com_cache")
if not os.path.exists(path):
log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path)
return path

View File

@@ -18,7 +18,7 @@ import os
from mysc.thread_utils import stream_threaded from mysc.thread_utils import stream_threaded
from pubsub import pub from pubsub import pub
log = logging.getLogger("sessionmanager.session") log = logging.getLogger("sessionmanager.session")
from long_tweets import tweets from long_tweets import tweets, twishort
sessions = {} sessions = {}
@@ -64,6 +64,7 @@ class Session(object):
if utils.find_item(i["id"], self.db[name]) == None and utils.is_allowed(i, self.settings["twitter"]["ignored_clients"]) == True: if utils.find_item(i["id"], self.db[name]) == None and utils.is_allowed(i, self.settings["twitter"]["ignored_clients"]) == True:
try: i = self.check_quoted_status(i) try: i = self.check_quoted_status(i)
except: pass except: pass
i = self.check_long_tweet(i)
if self.settings["general"]["reverse_timelines"] == False: self.db[name].append(i) if self.settings["general"]["reverse_timelines"] == False: self.db[name].append(i)
else: self.db[name].insert(0, i) else: self.db[name].insert(0, i)
num = num+1 num = num+1
@@ -425,19 +426,17 @@ class Session(object):
def check_quoted_status(self, tweet): def check_quoted_status(self, tweet):
status = tweets.is_long(tweet) status = tweets.is_long(tweet)
if status != False: if status != False:
tweet["quoted"] = 1
tweet = self.get_quoted_tweet(tweet) tweet = self.get_quoted_tweet(tweet)
return tweet return tweet
def get_quoted_tweet(self, tweet): def get_quoted_tweet(self, tweet):
quoted_tweet = self.twitter.twitter.show_status(id=tweet["id"]) quoted_tweet = tweet
urls = utils.find_urls_in_text(quoted_tweet["text"]) urls = utils.find_urls_in_text(quoted_tweet["text"])
for url in range(0, len(urls)): for url in range(0, len(urls)):
try: quoted_tweet["text"] = quoted_tweet["text"].replace(urls[url], quoted_tweet["entities"]["urls"][url]["expanded_url"]) try: quoted_tweet["text"] = quoted_tweet["text"].replace(urls[url], quoted_tweet["entities"]["urls"][url]["expanded_url"])
except IndexError: pass except IndexError: pass
l = tweets.is_long(quoted_tweet) id = tweets.is_long(quoted_tweet)
id = tweets.get_id(l)
try: original_tweet = self.twitter.twitter.show_status(id=id) try: original_tweet = self.twitter.twitter.show_status(id=id)
except: return quoted_tweet except: return quoted_tweet
urls = utils.find_urls_in_text(original_tweet["text"]) urls = utils.find_urls_in_text(original_tweet["text"])
@@ -446,3 +445,8 @@ class Session(object):
except IndexError: pass except IndexError: pass
return compose.compose_quoted_tweet(quoted_tweet, original_tweet) return compose.compose_quoted_tweet(quoted_tweet, original_tweet)
def check_long_tweet(self, tweet):
long = twishort.is_long(tweet)
if long != False:
tweet["message"] = twishort.get_full_text(long)
return tweet

View File

@@ -44,6 +44,15 @@ class sessionManagerController(object):
log.debug("Adding session %s" % (i,)) log.debug("Adding session %s" % (i,))
strconfig = "%s/session.conf" % (paths.config_path(i)) strconfig = "%s/session.conf" % (paths.config_path(i))
config_test = config_utils.load_config(strconfig) config_test = config_utils.load_config(strconfig)
if len(config_test) == 0:
try:
log.debug("Deleting session %s" % (i,))
shutil.rmtree(paths.config_path(i))
continue
except:
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"] name = config_test["twitter"]["user_name"]
if config_test["twitter"]["user_key"] != "" and config_test["twitter"]["user_secret"] != "": if config_test["twitter"]["user_key"] != "" and config_test["twitter"]["user_secret"] != "":
sessionsList.append(name) sessionsList.append(name)

View File

@@ -26,6 +26,7 @@ import application
import platform import platform
from glob import glob from glob import glob
import wx import wx
from requests import certs
def get_architecture_files(): def get_architecture_files():
if platform.architecture()[0][:2] == "32": if platform.architecture()[0][:2] == "32":
@@ -45,15 +46,15 @@ def get_data():
import enchant import enchant
return [ return [
("", ["conf.defaults", "app-configuration.defaults", "icon.ico"]), ("", ["conf.defaults", "app-configuration.defaults", "icon.ico"]),
("requests", ["cacert.pem"]), ("", [certs.where()]),
("accessible_output2/lib", glob("accessible_output2/lib/*.dll")), ("accessible_output2/lib", glob("accessible_output2/lib/*.dll")),
("keys/lib", glob("keys/lib/*.dll")), ("keys/lib", glob("keys/lib/*.dll")),
("keymaps", glob("keymaps/*.keymap")), ("keymaps", glob("keymaps/*.keymap")),
]+get_sounds()+get_locales()+get_documentation()+sound_lib.find_datafiles()+accessible_output2.find_datafiles()+enchant.utils.win32_data_files()+get_architecture_files()+wx_files() ]+get_sounds()+get_locales()+get_documentation()+sound_lib.find_datafiles()+accessible_output2.find_datafiles()+enchant.utils.win32_data_files()+get_architecture_files()+wx_files()
def get_documentation (): def get_documentation ():
answer = [] answer = [("documentation", ["documentation/license.txt"])]
depth = 6 depth = 10
for root, dirs, files in os.walk('documentation'): for root, dirs, files in os.walk('documentation'):
if depth == 0: if depth == 0:
break break
@@ -112,7 +113,7 @@ options = {
'optimize':2, 'optimize':2,
'packages': ["pubsub", "pubsub.core", "pubsub.core.kwargs", "dbhash"], 'packages': ["pubsub", "pubsub.core", "pubsub.core.kwargs", "dbhash"],
'dll_excludes': ["MPR.dll", "api-ms-win-core-apiquery-l1-1-0.dll", "api-ms-win-core-console-l1-1-0.dll", "api-ms-win-core-delayload-l1-1-1.dll", "api-ms-win-core-errorhandling-l1-1-1.dll", "api-ms-win-core-file-l1-2-0.dll", "api-ms-win-core-handle-l1-1-0.dll", "api-ms-win-core-heap-obsolete-l1-1-0.dll", "api-ms-win-core-libraryloader-l1-1-1.dll", "api-ms-win-core-localization-l1-2-0.dll", "api-ms-win-core-processenvironment-l1-2-0.dll", "api-ms-win-core-processthreads-l1-1-1.dll", "api-ms-win-core-profile-l1-1-0.dll", "api-ms-win-core-registry-l1-1-0.dll", "api-ms-win-core-synch-l1-2-0.dll", "api-ms-win-core-sysinfo-l1-2-0.dll", "api-ms-win-security-base-l1-2-0.dll", "api-ms-win-core-heap-l1-2-0.dll", "api-ms-win-core-interlocked-l1-2-0.dll", "api-ms-win-core-localization-obsolete-l1-1-0.dll", "api-ms-win-core-string-l1-1-0.dll", "api-ms-win-core-string-obsolete-l1-1-0.dll", "WLDAP32.dll", "MSVCP90.dll", "CRYPT32.dll", "mfc90.dll"], 'dll_excludes': ["MPR.dll", "api-ms-win-core-apiquery-l1-1-0.dll", "api-ms-win-core-console-l1-1-0.dll", "api-ms-win-core-delayload-l1-1-1.dll", "api-ms-win-core-errorhandling-l1-1-1.dll", "api-ms-win-core-file-l1-2-0.dll", "api-ms-win-core-handle-l1-1-0.dll", "api-ms-win-core-heap-obsolete-l1-1-0.dll", "api-ms-win-core-libraryloader-l1-1-1.dll", "api-ms-win-core-localization-l1-2-0.dll", "api-ms-win-core-processenvironment-l1-2-0.dll", "api-ms-win-core-processthreads-l1-1-1.dll", "api-ms-win-core-profile-l1-1-0.dll", "api-ms-win-core-registry-l1-1-0.dll", "api-ms-win-core-synch-l1-2-0.dll", "api-ms-win-core-sysinfo-l1-2-0.dll", "api-ms-win-security-base-l1-2-0.dll", "api-ms-win-core-heap-l1-2-0.dll", "api-ms-win-core-interlocked-l1-2-0.dll", "api-ms-win-core-localization-obsolete-l1-1-0.dll", "api-ms-win-core-string-l1-1-0.dll", "api-ms-win-core-string-obsolete-l1-1-0.dll", "WLDAP32.dll", "MSVCP90.dll", "CRYPT32.dll", "mfc90.dll"],
'skip_archive': True 'compressed': True
}, },
}, },
windows = [ windows = [

View File

@@ -1 +1 @@
All the sounds used in the soundpack do not belong to us. They belong to the origenal creaters. These sounds do not belong to us; they are the property of their original creators.

View File

@@ -1 +1,2 @@
All the sounds used in the soundpack do not belong to us. They belong to the origenal creaters. These sounds do not belong to us; they are the property of their original creators.
This soundpack was adapted for TWBlue by Bill Dengler (@codeofdusk on Twitter).

View File

@@ -36,6 +36,7 @@ class timelinesStreamer(TwythonStreamer):
return return
try: try:
data_ = self.session.check_quoted_status(data) data_ = self.session.check_quoted_status(data)
data_ = self.session.check_long_tweet(data_)
data = data_ data = data_
except AttributeError: except AttributeError:
pass pass

View File

@@ -37,11 +37,12 @@ class streamer(TwythonStreamer):
if utils.find_item(data["id"], self.session.db[place]) != None: if utils.find_item(data["id"], self.session.db[place]) != None:
log.error("duplicated tweet. Ignoring it...") log.error("duplicated tweet. Ignoring it...")
return False return False
try: # try:
data_ = self.session.check_quoted_status(data) data_ = self.session.check_quoted_status(data)
data = data_ data_ = self.session.check_long_tweet(data_)
except: data = data_
pass # except:
# pass
if self.session.settings["general"]["reverse_timelines"] == False: if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db[place].append(data) self.session.db[place].append(data)
else: else:
@@ -126,6 +127,8 @@ class streamer(TwythonStreamer):
def on_success(self, data): def on_success(self, data):
try: try:
if "delete" in data:
pub.sendMessage("tweet-deleted", data=data)
if "direct_message" in data: if "direct_message" in data:
self.process_dm(data) self.process_dm(data)
elif "friends" in data: elif "friends" in data:

View File

@@ -30,9 +30,6 @@ chars = "abcdefghijklmnopqrstuvwxyz"
def compose_tweet(tweet, db, relative_times): def compose_tweet(tweet, db, relative_times):
""" It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is.""" """ It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is."""
long = twishort.is_long(tweet)
if long != False:
tweet["long_uri"] = long
if system == "Windows": if system == "Windows":
original_date = arrow.get(tweet["created_at"], "ddd MMM DD H:m:s Z YYYY", locale="en") original_date = arrow.get(tweet["created_at"], "ddd MMM DD H:m:s Z YYYY", locale="en")
if relative_times == True: if relative_times == True:
@@ -41,7 +38,11 @@ def compose_tweet(tweet, db, relative_times):
ts = original_date.replace(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.getLanguage()) ts = original_date.replace(seconds=db["utc_offset"]).format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.getLanguage())
else: else:
ts = tweet["created_at"] ts = tweet["created_at"]
text = StripChars(tweet["text"]) if tweet.has_key("message"):
value = "message"
else:
value = "text"
text = StripChars(tweet[value])
if tweet.has_key("sender"): if tweet.has_key("sender"):
source = "DM" source = "DM"
if db["user_name"] == tweet["sender"]["screen_name"]: user = _(u"Dm to %s ") % (tweet["recipient"]["name"],) if db["user_name"] == tweet["sender"]["screen_name"]: user = _(u"Dm to %s ") % (tweet["recipient"]["name"],)
@@ -49,27 +50,21 @@ def compose_tweet(tweet, db, relative_times):
elif tweet.has_key("user"): elif tweet.has_key("user"):
user = tweet["user"]["name"] user = tweet["user"]["name"]
source = re.sub(r"(?s)<.*?>", "", tweet["source"]) source = re.sub(r"(?s)<.*?>", "", tweet["source"])
try: text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], StripChars(tweet["retweeted_status"]["text"])) if tweet.has_key("retweeted_status"):
except KeyError: text = "%s" % (StripChars(tweet["text"])) if tweet.has_key("message") == False and tweet["retweeted_status"]["is_quote_status"] == False:
text = "RT @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], StripChars(tweet["retweeted_status"]["text"]))
elif tweet["retweeted_status"]["is_quote_status"]:
text = "%s" % (StripChars(tweet[value]))
else:
text = "RT @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], StripChars(tweet[value]))
if text[-1] in chars: text=text+"." if text[-1] in chars: text=text+"."
urls = utils.find_urls_in_text(text) urls = utils.find_urls_in_text(text)
for url in range(0, len(urls)): for url in range(0, len(urls)):
try: text = text.replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"]) try: text = text.replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"])
except IndexError: pass except IndexError: pass
if config.app['app-settings']['handle_longtweets'] and 'long_uri' in tweet: if config.app['app-settings']['handle_longtweets']: pass
try: # return [user+", ", text, ts+", ", source]
oldtext=text return [user+", ", text, ts+", ", source]
text=twishort.get_full_text(tweet['long_uri'])
try: text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], StripChars(text))
except KeyError: pass
except:
text=oldtext
if tweet.has_key("message"):
text = tweet["message"]
return [user+", ", text, ts+", ", source]
tweet["text"] = text
return [user+", ", tweet["text"], ts+", ", source]
def compose_quoted_tweet(quoted_tweet, original_tweet): def compose_quoted_tweet(quoted_tweet, original_tweet):
""" It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is.""" """ It receives a tweet and returns a list with the user, text for the tweet or message, date and the client where user is."""

View File

@@ -23,7 +23,11 @@ def find_urls_in_text(text):
def find_urls (tweet): def find_urls (tweet):
urls = [] urls = []
return [s[0] for s in url_re.findall(tweet["text"])] if tweet.has_key("message"):
i = "message"
else:
i = "text"
return [s[0] for s in url_re.findall(tweet[i])]
def find_item(id, listItem): def find_item(id, listItem):
for i in range(0, len(listItem)): for i in range(0, len(listItem)):

View File

@@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net
""" """
__author__ = 'Ryan McGrath <ryan@venodesigns.net>' __author__ = 'Ryan McGrath <ryan@venodesigns.net>'
__version__ = '3.2.0' __version__ = '3.3.0'
from .api import Twython from .api import Twython
from .streaming import TwythonStreamer from .streaming import TwythonStreamer

View File

@@ -10,6 +10,7 @@ dealing with the Twitter API
""" """
import warnings import warnings
import re
import requests import requests
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
@@ -102,15 +103,10 @@ class Twython(EndpointsMixin, object):
auth = None auth = None
if oauth_version == 1: if oauth_version == 1:
# User Authentication is through OAuth 1 # User Authentication is through OAuth 1
if self.app_key is not None and self.app_secret is not None and \ if self.app_key is not None and self.app_secret is not None:
self.oauth_token is None and self.oauth_token_secret is None:
auth = OAuth1(self.app_key, self.app_secret)
if self.app_key is not None and self.app_secret is not None and \
self.oauth_token is not None and self.oauth_token_secret is \
not None:
auth = OAuth1(self.app_key, self.app_secret, auth = OAuth1(self.app_key, self.app_secret,
self.oauth_token, self.oauth_token_secret) self.oauth_token, self.oauth_token_secret)
elif oauth_version == 2 and self.access_token: elif oauth_version == 2 and self.access_token:
# Application Authentication is through OAuth 2 # Application Authentication is through OAuth 2
token = {'token_type': token_type, token = {'token_type': token_type,
@@ -144,8 +140,11 @@ class Twython(EndpointsMixin, object):
params = params or {} params = params or {}
func = getattr(self.client, method) func = getattr(self.client, method)
params, files = _transparent_params(params) if type(params) is dict:
params, files = _transparent_params(params)
else:
params = params
files = list()
requests_args = {} requests_args = {}
for k, v in self.client_args.items(): for k, v in self.client_args.items():
# Maybe this should be set as a class variable and only done once? # Maybe this should be set as a class variable and only done once?
@@ -198,10 +197,15 @@ class Twython(EndpointsMixin, object):
retry_after=response.headers.get('X-Rate-Limit-Reset')) retry_after=response.headers.get('X-Rate-Limit-Reset'))
try: try:
content = response.json() if response.status_code == 204:
content = response.content
else:
content = response.json()
except ValueError: except ValueError:
raise TwythonError('Response was not valid JSON. \ # Send the response as is for working with /media/metadata/create.json.
Unable to decode.') content = response.content
# raise TwythonError('Response was not valid JSON. \
# Unable to decode.')
return content return content
@@ -254,10 +258,8 @@ class Twython(EndpointsMixin, object):
url = endpoint url = endpoint
else: else:
url = '%s/%s.json' % (self.api_url % version, endpoint) url = '%s/%s.json' % (self.api_url % version, endpoint)
content = self._request(url, method=method, params=params, content = self._request(url, method=method, params=params,
api_call=url) api_call=url)
return content return content
def get(self, endpoint, params=None, version='1.1'): def get(self, endpoint, params=None, version='1.1'):
@@ -528,7 +530,7 @@ class Twython(EndpointsMixin, object):
return str(text) return str(text)
@staticmethod @staticmethod
def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=False):
"""Return HTML for a tweet (urls, mentions, hashtags replaced with links) """Return HTML for a tweet (urls, mentions, hashtags replaced with links)
:param tweet: Tweet object from received from Twitter API :param tweet: Tweet object from received from Twitter API
@@ -550,19 +552,22 @@ class Twython(EndpointsMixin, object):
entities = tweet['entities'] entities = tweet['entities']
# Mentions # Mentions
for entity in entities['user_mentions']: for entity in sorted(entities['user_mentions'],
key=lambda mention: len(mention['screen_name']), reverse=True):
start, end = entity['indices'][0], entity['indices'][1] start, end = entity['indices'][0], entity['indices'][1]
mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>' mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>'
text = text.replace(tweet['text'][start:end], text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)',
mention_html % {'screen_name': entity['screen_name']}) mention_html % {'screen_name': entity['screen_name']}, text)
# Hashtags # Hashtags
for entity in entities['hashtags']: for entity in sorted(entities['hashtags'],
key=lambda hashtag: len(hashtag['text']), reverse=True):
start, end = entity['indices'][0], entity['indices'][1] start, end = entity['indices'][0], entity['indices'][1]
hashtag_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>' hashtag_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>'
text = text.replace(tweet['text'][start:end], hashtag_html % {'hashtag': entity['text']}) text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)',
hashtag_html % {'hashtag': entity['text']}, text)
# Urls # Urls
for entity in entities['urls']: for entity in entities['urls']:
@@ -595,4 +600,16 @@ class Twython(EndpointsMixin, object):
text = text.replace(tweet['text'][start:end], text = text.replace(tweet['text'][start:end],
url_html % (entity['url'], shown_url)) url_html % (entity['url'], shown_url))
if expand_quoted_status and tweet.get('is_quote_status'):
quoted_status = tweet['quoted_status']
text += '<blockquote class="twython-quote">%(quote)s<cite><a href="%(quote_tweet_link)s">' \
'<span class="twython-quote-user-name">%(quote_user_name)s</span>' \
'<span class="twython-quote-user-screenname">@%(quote_user_screen_name)s</span></a>' \
'</cite></blockquote>' % \
{'quote': Twython.html_for_tweet(quoted_status, use_display_url, use_expanded_url, False),
'quote_tweet_link': 'https://twitter.com/%s/status/%s' %
(quoted_status['user']['screen_name'], quoted_status['id_str']),
'quote_user_name': quoted_status['user']['name'],
'quote_user_screen_name': quoted_status['user']['screen_name']}
return text return text

View File

@@ -14,7 +14,13 @@ This map is organized the order functions are documented at:
https://dev.twitter.com/docs/api/1.1 https://dev.twitter.com/docs/api/1.1
""" """
import json
import os
import warnings import warnings
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from .advisory import TwythonDeprecationWarning from .advisory import TwythonDeprecationWarning
@@ -139,6 +145,68 @@ class EndpointsMixin(object):
""" """
return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params)
def set_description(self, **params):
""" Adds a description to an image."""
# This method only accepts strings, no dictionaries.
params = json.dumps(params)
return self.post("media/metadata/create", params=params)
def upload_video(self, media, media_type, size=None):
"""Uploads video file to Twitter servers in chunks. The file will be available to be attached
to a status for 60 minutes. To attach to a update, pass a list of returned media ids
to the 'update_status' method using the 'media_ids' param.
Upload happens in 3 stages:
- INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error.
- APPEND calls each with media chunk. This returns a 204(No Content) if chunk is received.
- FINALIZE call to complete media upload. This returns media_id to be used with status update.
Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each.
Docs:
https://dev.twitter.com/rest/public/uploading-media#chunkedupload
"""
upload_url = 'https://upload.twitter.com/1.1/media/upload.json'
if not size:
media.seek(0, os.SEEK_END)
size = media.tell()
media.seek(0)
# Stage 1: INIT call
params = {
'command': 'INIT',
'media_type': media_type,
'total_bytes': size
}
response_init = self.post(upload_url, params=params)
media_id = response_init['media_id']
# Stage 2: APPEND calls with 1mb chunks
segment_index = 0
while True:
data = media.read(1*1024*1024)
if not data:
break
media_chunk = StringIO()
media_chunk.write(data)
media_chunk.seek(0)
params = {
'command': 'APPEND',
'media_id': media_id,
'segment_index': segment_index,
'media': media_chunk,
}
self.post(upload_url, params=params)
segment_index += 1
# Stage 3: FINALIZE call to complete upload
params = {
'command': 'FINALIZE',
'media_id': media_id
}
return self.post(upload_url, params=params)
def get_oembed_tweet(self, **params): def get_oembed_tweet(self, **params):
"""Returns information allowing the creation of an embedded """Returns information allowing the creation of an embedded
representation of a Tweet on third party sites. representation of a Tweet on third party sites.
@@ -458,7 +526,7 @@ class EndpointsMixin(object):
Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup
""" """
return self.post('users/lookup', params=params) return self.get('users/lookup', params=params)
def show_user(self, **params): def show_user(self, **params):
"""Returns a variety of information about the user specified by the """Returns a variety of information about the user specified by the
@@ -546,7 +614,7 @@ class EndpointsMixin(object):
list_mute_ids.iter_key = 'ids' list_mute_ids.iter_key = 'ids'
def create_mute(self, **params): def create_mute(self, **params):
"""Mutes the specified user, preventing their tweets appearing """Mutes the specified user, preventing their tweets appearing
in the authenticating user's timeline. in the authenticating user's timeline.
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create
@@ -555,7 +623,7 @@ class EndpointsMixin(object):
return self.post('mutes/users/create', params=params) return self.post('mutes/users/create', params=params)
def destroy_mute(self, **params): def destroy_mute(self, **params):
"""Un-mutes the user specified in the user or ID parameter for """Un-mutes the user specified in the user or ID parameter for
the authenticating user. the authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
""" Attach dialog. Taken from socializer: https://github.com/manuelcortez/socializer"""
import wx
import widgetUtils
from multiplatform_widgets import widgets
class attachDialog(widgetUtils.BaseDialog):
def __init__(self):
super(attachDialog, self).__init__(None, title=_(u"Add an attachment"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
lbl1 = wx.StaticText(panel, wx.NewId(), _(u"Attachments"))
self.attachments = widgets.list(panel, _(u"Type"), _(u"Title"), style=wx.LC_REPORT)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(lbl1, 0, wx.ALL, 5)
box.Add(self.attachments.list, 0, wx.ALL, 5)
sizer.Add(box, 0, wx.ALL, 5)
static = wx.StaticBox(panel, label=_(u"Add attachments"))
self.photo = wx.Button(panel, wx.NewId(), _(u"&Photo"))
self.remove = wx.Button(panel, wx.NewId(), _(u"Remove attachment"))
self.remove.Enable(False)
btnsizer = wx.StaticBoxSizer(static, wx.HORIZONTAL)
btnsizer.Add(self.photo, 0, wx.ALL, 5)
sizer.Add(btnsizer, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
ok.SetDefault()
cancelBtn = wx.Button(panel, wx.ID_CANCEL)
btnSizer = wx.BoxSizer()
btnSizer.Add(ok, 0, wx.ALL, 5)
btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
sizer.Add(btnSizer, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def get_image(self):
openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return (None, None)
dsc = self.ask_description()
return (openFileDialog.GetPath(), dsc)
def ask_description(self):
dlg = wx.TextEntryDialog(self, _(u"please provide a description"), _(u"Description"), defaultValue="")
dlg.ShowModal()
result = dlg.GetValue()
dlg.Destroy()
return result

View File

@@ -38,6 +38,8 @@ class general(wx.Panel, baseDialog.BaseWXDialog):
self.km.SetSize(self.km.GetBestSize()) self.km.SetSize(self.km.GetBestSize())
kmbox.Add(km_label, 0, wx.ALL, 5) kmbox.Add(km_label, 0, wx.ALL, 5)
kmbox.Add(self.km, 0, wx.ALL, 5) kmbox.Add(self.km, 0, wx.ALL, 5)
self.check_for_updates = wx.CheckBox(self, -1, _(U"Check for updates when {0} launches").format(application.name,))
sizer.Add(self.check_for_updates, 0, wx.ALL, 5)
sizer.Add(kmbox, 0, wx.ALL, 5) sizer.Add(kmbox, 0, wx.ALL, 5)
self.SetSizer(sizer) self.SetSizer(sizer)
@@ -300,7 +302,6 @@ class servicesPanel(wx.Panel):
return self.pocketBtn.GetLabel() return self.pocketBtn.GetLabel()
class configurationDialog(baseDialog.BaseWXDialog): class configurationDialog(baseDialog.BaseWXDialog):
def set_title(self, title): def set_title(self, title):
self.SetTitle(title) self.SetTitle(title)

View File

@@ -6,7 +6,8 @@ class textLimited(widgetUtils.BaseDialog):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(textLimited, self).__init__(parent=None, *args, **kwargs) super(textLimited, self).__init__(parent=None, *args, **kwargs)
def createTextArea(self, message="", text=""): def createTextArea(self, message="", text=""):
self.panel = wx.Panel(self) if not hasattr(self, "panel"):
self.panel = wx.Panel(self)
self.label = wx.StaticText(self.panel, -1, message) self.label = wx.StaticText(self.panel, -1, message)
self.SetTitle(str(len(text))) self.SetTitle(str(len(text)))
self.text = wx.TextCtrl(self.panel, -1, text, size=(439, -1),style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) self.text = wx.TextCtrl(self.panel, -1, text, size=(439, -1),style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER)
@@ -78,10 +79,10 @@ class tweet(textLimited):
self.shortenButton.Disable() self.shortenButton.Disable()
self.unshortenButton.Disable() self.unshortenButton.Disable()
self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize) self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize)
self.autocompletionButton = wx.Button(self.panel, -1, _(u"&Autocomplete users")) self.autocompletionButton = wx.Button(self.panel, -1, _(u"Auto&complete users"))
self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"&Send"), size=wx.DefaultSize) self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Sen&d"), size=wx.DefaultSize)
self.okButton.SetDefault() self.okButton.SetDefault()
cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"&Close"), size=wx.DefaultSize) cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize)
self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL) self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL)
self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 10) self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 10)
self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10) self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10)
@@ -130,18 +131,18 @@ class retweet(tweet):
self.retweetBox.Add(self.text2, 0, wx.ALL, 5) self.retweetBox.Add(self.text2, 0, wx.ALL, 5)
self.mainBox.Add(self.textBox, 0, wx.ALL, 5) self.mainBox.Add(self.textBox, 0, wx.ALL, 5)
self.mainBox.Add(self.retweetBox, 0, wx.ALL, 5) self.mainBox.Add(self.retweetBox, 0, wx.ALL, 5)
self.upload_image = wx.Button(self.panel, -1, _(u"Upload a picture"), size=wx.DefaultSize) self.upload_image = wx.Button(self.panel, -1, _(u"&Upload image..."), size=wx.DefaultSize)
self.spellcheck = wx.Button(self.panel, -1, _("Spelling correction"), size=wx.DefaultSize) self.spellcheck = wx.Button(self.panel, -1, _("Check &spelling..."), size=wx.DefaultSize)
self.attach = wx.Button(self.panel, -1, _(u"Attach audio"), size=wx.DefaultSize) self.attach = wx.Button(self.panel, -1, _(u"&Attach audio..."), size=wx.DefaultSize)
self.shortenButton = wx.Button(self.panel, -1, _(u"Shorten URL"), size=wx.DefaultSize) self.shortenButton = wx.Button(self.panel, -1, _(u"Sh&orten URL"), size=wx.DefaultSize)
self.unshortenButton = wx.Button(self.panel, -1, _(u"Expand URL"), size=wx.DefaultSize) self.unshortenButton = wx.Button(self.panel, -1, _(u"&Expand URL"), size=wx.DefaultSize)
self.shortenButton.Disable() self.shortenButton.Disable()
self.unshortenButton.Disable() self.unshortenButton.Disable()
self.translateButton = wx.Button(self.panel, -1, _(u"Translate message"), size=wx.DefaultSize) self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize)
self.autocompletionButton = wx.Button(self.panel, -1, _(u"&Autocomplete users")) self.autocompletionButton = wx.Button(self.panel, -1, _(u"Auto&complete users"))
self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Send"), size=wx.DefaultSize) self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Sen&d"), size=wx.DefaultSize)
self.okButton.SetDefault() self.okButton.SetDefault()
cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"Close"), size=wx.DefaultSize) cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize)
self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL) self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL)
self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 10) self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 10)
self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10) self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10)
@@ -181,27 +182,26 @@ class dm(textLimited):
def createControls(self, title, message, users): def createControls(self, title, message, users):
self.panel = wx.Panel(self) self.panel = wx.Panel(self)
self.mainBox = wx.BoxSizer(wx.VERTICAL) self.mainBox = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(self.panel, -1, _(u"Recipient")) label = wx.StaticText(self.panel, -1, _(u"&Recipient"))
self.cb = wx.ComboBox(self.panel, -1, choices=users, value=users[0], size=wx.DefaultSize) self.cb = wx.ComboBox(self.panel, -1, choices=users, value=users[0], size=wx.DefaultSize)
self.autocompletionButton = wx.Button(self.panel, -1, _(u"&Autocomplete users")) self.autocompletionButton = wx.Button(self.panel, -1, _(u"Auto&complete users"))
self.createTextArea(message, text="") self.createTextArea(message, text="")
userBox = wx.BoxSizer(wx.HORIZONTAL) userBox = wx.BoxSizer(wx.HORIZONTAL)
userBox.Add(label, 0, wx.ALL, 5) userBox.Add(label, 0, wx.ALL, 5)
userBox.Add(self.cb, 0, wx.ALL, 5) userBox.Add(self.cb, 0, wx.ALL, 5)
userBox.Add(self.autocompletionButton, 0, wx.ALL, 5) userBox.Add(self.autocompletionButton, 0, wx.ALL, 5)
self.mainBox.Add(userBox, 0, wx.ALL, 5) self.mainBox.Add(userBox, 0, wx.ALL, 5)
# self.mainBox.Add(self.cb, 0, wx.ALL, 5)
self.mainBox.Add(self.textBox, 0, wx.ALL, 5) self.mainBox.Add(self.textBox, 0, wx.ALL, 5)
self.spellcheck = wx.Button(self.panel, -1, _("Spelling correction"), size=wx.DefaultSize) self.spellcheck = wx.Button(self.panel, -1, _("Check &spelling..."), size=wx.DefaultSize)
self.attach = wx.Button(self.panel, -1, _(u"Attach audio"), size=wx.DefaultSize) self.attach = wx.Button(self.panel, -1, _(u"&Attach audio..."), size=wx.DefaultSize)
self.shortenButton = wx.Button(self.panel, -1, _(u"Shorten URL"), size=wx.DefaultSize) self.shortenButton = wx.Button(self.panel, -1, _(u"Sh&orten URL"), size=wx.DefaultSize)
self.unshortenButton = wx.Button(self.panel, -1, _(u"Expand URL"), size=wx.DefaultSize) self.unshortenButton = wx.Button(self.panel, -1, _(u"&Expand URL"), size=wx.DefaultSize)
self.shortenButton.Disable() self.shortenButton.Disable()
self.unshortenButton.Disable() self.unshortenButton.Disable()
self.translateButton = wx.Button(self.panel, -1, _(u"Translate message"), size=wx.DefaultSize) self.translateButton = wx.Button(self.panel, -1, _(u"&Translate..."), size=wx.DefaultSize)
self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Send"), size=wx.DefaultSize) self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Sen&d"), size=wx.DefaultSize)
self.okButton.SetDefault() self.okButton.SetDefault()
cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"Close"), size=wx.DefaultSize) cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize)
self.buttonsBox = wx.BoxSizer(wx.HORIZONTAL) self.buttonsBox = wx.BoxSizer(wx.HORIZONTAL)
self.buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) self.buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5)
self.buttonsBox.Add(self.attach, 0, wx.ALL, 5) self.buttonsBox.Add(self.attach, 0, wx.ALL, 5)
@@ -216,13 +216,13 @@ class dm(textLimited):
self.buttonsBox3.Add(cancelButton, 0, wx.ALL, 5) self.buttonsBox3.Add(cancelButton, 0, wx.ALL, 5)
self.mainBox.Add(self.buttonsBox3, 0, wx.ALL, 5) self.mainBox.Add(self.buttonsBox3, 0, wx.ALL, 5)
self.panel.SetSizer(self.mainBox) self.panel.SetSizer(self.mainBox)
# self.SetClientSize(self.mainBox.CalcMin()) self.SetClientSize(self.mainBox.CalcMin())
def __init__(self, title, message, users): def __init__(self, title, message, users):
super(dm, self).__init__() super(dm, self).__init__()
self.createControls(message, title, users) self.createControls(message, title, users)
# self.onTimer(wx.EVT_CHAR_HOOK) # self.onTimer(wx.EVT_CHAR_HOOK)
self.SetClientSize(self.mainBox.CalcMin()) # self.SetClientSize(self.mainBox.CalcMin())
def get_user(self): def get_user(self):
return self.cb.GetValue() return self.cb.GetValue()
@@ -260,6 +260,17 @@ class viewTweet(widgetUtils.BaseDialog):
textBox.Add(self.text, 1, wx.EXPAND, 5) textBox.Add(self.text, 1, wx.EXPAND, 5)
mainBox = wx.BoxSizer(wx.VERTICAL) mainBox = wx.BoxSizer(wx.VERTICAL)
mainBox.Add(textBox, 0, wx.ALL, 5) mainBox.Add(textBox, 0, wx.ALL, 5)
label2 = wx.StaticText(panel, -1, _(u"Image description"))
self.image_description = wx.TextCtrl(panel, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180))
dc = wx.WindowDC(self.image_description)
dc.SetFont(self.image_description.GetFont())
(x, y, z) = dc.GetMultiLineTextExtent("0"*450)
self.image_description.SetSize((x, y))
self.image_description.Enable(False)
iBox = wx.BoxSizer(wx.HORIZONTAL)
iBox.Add(label2, 0, wx.ALL, 5)
iBox.Add(self.image_description, 1, wx.EXPAND, 5)
mainBox.Add(iBox, 0, wx.ALL, 5)
rtCountLabel = wx.StaticText(panel, -1, _(u"Retweets: ")) rtCountLabel = wx.StaticText(panel, -1, _(u"Retweets: "))
rtCount = wx.TextCtrl(panel, -1, rt_count, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE) rtCount = wx.TextCtrl(panel, -1, rt_count, size=wx.DefaultSize, style=wx.TE_READONLY|wx.TE_MULTILINE)
rtBox = wx.BoxSizer(wx.HORIZONTAL) rtBox = wx.BoxSizer(wx.HORIZONTAL)
@@ -280,11 +291,11 @@ class viewTweet(widgetUtils.BaseDialog):
infoBox.Add(favsBox, 0, wx.ALL, 5) infoBox.Add(favsBox, 0, wx.ALL, 5)
infoBox.Add(sourceBox, 0, wx.ALL, 5) infoBox.Add(sourceBox, 0, wx.ALL, 5)
mainBox.Add(infoBox, 0, wx.ALL, 5) mainBox.Add(infoBox, 0, wx.ALL, 5)
self.spellcheck = wx.Button(panel, -1, _("Spelling correction"), size=wx.DefaultSize) self.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize)
self.unshortenButton = wx.Button(panel, -1, _(u"Expand URL"), size=wx.DefaultSize) self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize)
self.unshortenButton.Disable() self.unshortenButton.Disable()
self.translateButton = wx.Button(panel, -1, _(u"Translate message"), size=wx.DefaultSize) self.translateButton = wx.Button(panel, -1, _(u"&Translate..."), size=wx.DefaultSize)
cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"Close"), size=wx.DefaultSize) cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize)
cancelButton.SetDefault() cancelButton.SetDefault()
buttonsBox = wx.BoxSizer(wx.HORIZONTAL) buttonsBox = wx.BoxSizer(wx.HORIZONTAL)
buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5)
@@ -307,6 +318,13 @@ class viewTweet(widgetUtils.BaseDialog):
def get_text(self): def get_text(self):
return self.text.GetValue() return self.text.GetValue()
def set_image_description(self, desc):
self.image_description.Enable(True)
if len(self.image_description.GetValue()) == 0:
self.image_description.SetValue(desc)
else:
self.image_description.SetValue(self.image_description.GetValue()+"\n"+desc)
def text_focus(self): def text_focus(self):
self.text.SetFocus() self.text.SetFocus()
@@ -335,11 +353,11 @@ class viewNonTweet(widgetUtils.BaseDialog):
textBox.Add(self.text, 1, wx.EXPAND, 5) textBox.Add(self.text, 1, wx.EXPAND, 5)
mainBox = wx.BoxSizer(wx.VERTICAL) mainBox = wx.BoxSizer(wx.VERTICAL)
mainBox.Add(textBox, 0, wx.ALL, 5) mainBox.Add(textBox, 0, wx.ALL, 5)
self.spellcheck = wx.Button(panel, -1, _("Spelling correction"), size=wx.DefaultSize) self.spellcheck = wx.Button(panel, -1, _("Check &spelling..."), size=wx.DefaultSize)
self.unshortenButton = wx.Button(panel, -1, _(u"Expand URL"), size=wx.DefaultSize) self.unshortenButton = wx.Button(panel, -1, _(u"&Expand URL"), size=wx.DefaultSize)
self.unshortenButton.Disable() self.unshortenButton.Disable()
self.translateButton = wx.Button(panel, -1, _(u"Translate message"), size=wx.DefaultSize) self.translateButton = wx.Button(panel, -1, _(u"&Translate..."), size=wx.DefaultSize)
cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"Close"), size=wx.DefaultSize) cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"C&lose"), size=wx.DefaultSize)
cancelButton.SetDefault() cancelButton.SetDefault()
buttonsBox = wx.BoxSizer(wx.HORIZONTAL) buttonsBox = wx.BoxSizer(wx.HORIZONTAL)
buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5) buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5)

View File

@@ -36,7 +36,6 @@ class SysTrayIcon(wx.TaskBarIcon):
self.update_profile = self.menu.Append(wx.ID_ANY, _(u"Update &profile")) self.update_profile = self.menu.Append(wx.ID_ANY, _(u"Update &profile"))
self.show_hide = self.menu.Append(wx.ID_ANY, _(u"&Show / hide")) self.show_hide = self.menu.Append(wx.ID_ANY, _(u"&Show / hide"))
self.doc = self.menu.Append(wx.ID_ANY, _(u"&Documentation")) self.doc = self.menu.Append(wx.ID_ANY, _(u"&Documentation"))
self.doc.Enable(False)
self.check_for_updates = self.menu.Append(wx.ID_ANY, _(u"Check for &updates")) self.check_for_updates = self.menu.Append(wx.ID_ANY, _(u"Check for &updates"))
self.exit = self.menu.Append(wx.ID_ANY, _(u"&Exit")) self.exit = self.menu.Append(wx.ID_ANY, _(u"&Exit"))

View File

@@ -1,4 +1,4 @@
{"current_version": "0.80", {"current_version": "0.83",
"description": "The first version for the new generation of TWBlue.", "description": "The first version for the new generation of TWBlue.",
"downloads": "downloads":
{"Windows32": "http://twblue.es/pubs/twblue_ngen_0.80_x86.zip", {"Windows32": "http://twblue.es/pubs/twblue_ngen_0.80_x86.zip",