Sync upstream.

This commit is contained in:
Bill Dengler 2017-10-15 22:16:18 +00:00
commit a49c4fa4f1
36 changed files with 361 additions and 187 deletions

View File

@ -29,10 +29,10 @@ Although most dependencies can be found in the windows-dependencies directory, w
#### Dependencies packaged in windows installers
* [Python,](http://python.org) version 2.7.13
* [Python,](http://python.org) version 2.7.14
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
* [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 221
* [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
@ -85,19 +85,19 @@ This dependency has been built using pure basic 4.61. Its source can be found at
#### Dependencies required to build the installer
* [NSIS,](http://nsis.sourceforge.net/) version 3.01
* [NSIS,](http://nsis.sourceforge.net/) version 3.02.1
#### Dependencies required to build the portableApps.com format archive
* [NSIS Portable,](http://portableapps.com/apps/development/nsis_portable) version 3.0
* [NSIS Portable,](http://portableapps.com/apps/development/nsis_portable) version 3.02.1
* [PortableApps.com Launcher,](http://portableapps.com/apps/development/portableapps.com_launcher) version 2.2.1
* [PortableApps.com Installer,](http://portableapps.com/apps/development/portableapps.com_installer) version 3.4.4
* [PortableApps.com Installer,](http://portableapps.com/apps/development/portableapps.com_installer) version 3.5.5
Important! Install these 3 apps into the same folder, otherwise you won't be able to build the pa.c version. For example: D:\portableApps\NSISPortable, D:\PortableApps\PortableApps.com installer, ...
#### Dependencies to make the spell checker multilingual ####
In order to add the support for spell checking in more languages than english you need to add some additional dictionaries to pyenchant. These are located on the dictionaries folder under windows-dependencies. Simply copy them to the share/enchant/myspell folder located in your enchant installation or in the compiled copy of TWBlue.
In order to add the support for spell checking in more languages than english you need to add some additional dictionaries to pyenchant. These are located on the dictionaries folder under windows-dependencies. Simply copy them to the share/enchant/myspell folder located in your enchant installation. They will be automatically copied when building a binary version.
### Running TW Blue from source

View File

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

View File

@ -2,6 +2,14 @@
## changes in this version
* When authorising an account, you will see a dialogue with a cancel button, in case you want to abort the process. Also, NVDA will not be blocked when the process starts. ([#101](https://github.com/manuelcortez/TWBlue/issues/101))
* In the translator module, the list of available languages is fetched automatically from the provider. That means all of these languages will work and there will not be inconsistencies. Also we've removed the first combo box, because the language is detected automatically by Yandex'S API. ([#153](https://github.com/manuelcortez/TWBlue/issues/153))
* Trending topics, searches and conversation buffers will use mute settings set for the session in wich they were opened. ([#157](https://github.com/manuelcortez/TWBlue/issues/157))
* And more. ([#156,](https://github.com/manuelcortez/TWBlue/issues/156) [#163,](https://github.com/manuelcortez/TWBlue/issues/163) [#159,](https://github.com/manuelcortez/TWBlue/issues/159))
## changes in version 0.91 and 0.92
* Fixed incorrect unicode handling when copying tweet to clipboard. ([#150](https://github.com/manuelcortez/TWBlue/issues/150))
* TWBlue will show an error when trying to open a timeline for a suspended user. ([#128](https://github.com/manuelcortez/TWBlue/issues/128))
* Removed TwUp as service as it no longer exists. ([#112](https://github.com/manuelcortez/TWBlue/issues/112))
* Release audio files after uploading them. ([#130](https://github.com/manuelcortez/TWBlue/issues/130))

View File

@ -20,8 +20,8 @@ CommercialUse=true
EULAVersion=2
[Version]
PackageVersion=0.91.0.0
DisplayVersion=0.91
PackageVersion=0.92.0.0
DisplayVersion=0.92
[Control]
Icons=1

View File

@ -15,10 +15,10 @@ SetCompressor /solid lzma
SetDatablockOptimize on
VIAddVersionKey ProductName "TWBlue"
VIAddVersionKey LegalCopyright "Copyright 2016 Manuel Cortéz."
VIAddVersionKey ProductVersion "0.91"
VIAddVersionKey FileVersion "0.91"
VIProductVersion "0.91.0.0"
VIFileVersion "0.91.0.0"
VIAddVersionKey ProductVersion "0.92"
VIAddVersionKey FileVersion "0.92"
VIProductVersion "0.92.0.0"
VIFileVersion "0.92.0.0"
!insertmacro MUI_PAGE_WELCOME
!define MUI_LICENSEPAGE_RADIOBUTTONS
!insertmacro MUI_PAGE_LICENSE "license.txt"
@ -72,10 +72,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" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.91"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.92"
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" "VersionMinor" 91
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 92
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1
SectionEnd

View File

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

View File

@ -28,21 +28,5 @@ def convert_soundcloud (url):
else:
raise TypeError('%r is not streamable' % url)
@matches_url('http://twup.me')
def convert_twup(url):
result = re.match("^http://twup.me/(?P<audio_id>[A-Za-z0-9]+/?)$", url, re.I)
if not result or result.group("audio_id") is None:
raise TypeError('%r is not a valid URL' % url)
audio_id = result.group("audio_id")
return 'http://twup.me/%s' % audio_id
#@matches_url('http://sndup.net')
#def convert_sndup(url):
# result = re.match("^http://sndup.net/(?P<audio_id>[a-z0-9]+/?)(|d|l|a)/?$", url, re.I)
# if not result or result.group("audio_id") is None:
# raise TypeError('%r is not a valid URL' % url)
# audio_id = result.group("audio_id")
# return 'http://sndup.net/%s/a' % audio_id
def convert_generic_audio(url):
return url

View File

@ -157,7 +157,7 @@ class bufferController(object):
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"]))
self.session.twitter.twitter.create_metadata(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)
@ -988,7 +988,7 @@ class searchBufferController(baseBufferController):
# return None
num = self.session.order_buffer(self.name, val)
self.put_items_on_list(num)
if num > 0:
if num > 0 and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"]:
self.session.sound.play("search_updated.ogg")
return num
@ -1068,7 +1068,7 @@ class searchPeopleBufferController(peopleBufferController):
number_of_items = self.session.order_cursored_buffer(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if number_of_items > 0:
if number_of_items > 0 and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"]:
self.session.sound.play("search_updated.ogg")
return number_of_items
@ -1148,7 +1148,8 @@ class trendsBufferController(bufferController):
self.name_ = data[0]["locations"][0]["name"]
self.trends = data[0]["trends"]
self.put_items_on_the_list()
self.session.sound.play(self.sound)
if self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"]:
self.session.sound.play(self.sound)
def put_items_on_the_list(self):
selected_item = self.buffer.list.get_selected()
@ -1270,7 +1271,7 @@ class conversationBufferController(searchBufferController):
number_of_items = self.session.order_buffer(self.name, self.statuses)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if number_of_items > 0:
if number_of_items > 0 and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"]:
self.session.sound.play("search_updated.ogg")
return number_of_items

View File

@ -477,12 +477,11 @@ class Controller(object):
output.speak(_(u"Empty buffer."), True)
return
start = page.buffer.list.get_selected()
for i in xrange(start,count):
page.buffer.list.select_item(i)
if string.lower() in page.get_message().lower():
for i in xrange(start, count):
if string.lower() in page.buffer.list.get_text_column(i, 1).lower():
page.buffer.list.select_item(i)
return output.speak(page.get_message(), True)
output.speak(_(u"{0} not found.").format(string,), True)
page.buffer.list.select_item(start)
def seekLeft(self, *args, **kwargs):
try:
@ -931,8 +930,8 @@ class Controller(object):
self.view.insert_buffer(buffer.buffer, name=_(u"Trending topics for %s") % (trends.get_string()), pos=pos)
self.buffers.append(buffer)
buffer.start_stream()
timer = RepeatingTimer(300, buffer.start_stream)
timer.start()
buffer.timer = RepeatingTimer(300, buffer.start_stream)
buffer.timer.start()
buffer.session.settings["other_buffers"]["trending_topic_buffers"].append(woeid)
buffer.session.settings.write()
@ -987,9 +986,10 @@ class Controller(object):
buff = self.view.search(buffer.name, buffer.account)
answer = buffer.remove_buffer()
if answer == False: return
if hasattr(buff, "timer"):
log.debug("destroying buffer...")
if hasattr(buffer, "timer"):
log.debug("Stopping timer...")
buff.timer.cancel()
buffer.timer.cancel()
log.debug("Timer cancelled.")
self.right()
self.view.delete_buffer(buff)

View File

@ -40,9 +40,8 @@ class basicTweet(object):
dlg = translator.gui.translateDialog()
if dlg.get_response() == widgetUtils.OK:
text_to_translate = self.message.get_text()
source = [x[0] for x in translator.translator.available_languages()][dlg.get("source_lang")]
dest = [x[0] for x in translator.translator.available_languages()][dlg.get("dest_lang")]
msg = translator.translator.translate(text=text_to_translate, source=source, target=dest)
msg = translator.translator.translate(text=text_to_translate, target=dest)
self.message.set_text(msg)
self.text_processor()
self.message.text_focus()

View File

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from yandex_translate import YandexTranslate
def translate(text="", source="auto", target="en"):
def translate(text="", target="en"):
t = YandexTranslate("trnsl.1.1.20161012T134532Z.d01b9c75fc39aa74.7d1be75a5166a80583eeb020e10f584168da6bf7")
return t.translate(text, target)["text"][0]
vars = dict(text=text, lang=target)
return t.translate(**vars)["text"][0]
supported_langs = None
d = None
languages = {
"af": _(u"Afrikaans"),
"sq": _(u"Albanian"),
@ -71,7 +73,7 @@ languages = {
"ps": _(u"Pashto"),
"fa": _(u"Persian"),
"pl": _(u"Polish"),
"pt-PT": _(u"Portuguese"),
"pt": _(u"Portuguese"),
"pa": _(u"Punjabi"),
"ro": _(u"Romanian"),
"ru": _(u"Russian"),
@ -101,8 +103,11 @@ languages = {
}
def available_languages():
l = languages.keys()
d = languages.values()
l.insert(0, '')
d.insert(0, _(u"autodetect"))
return sorted(zip(l, d))
global supported_langs, d
if supported_langs == None and d == None:
t = YandexTranslate("trnsl.1.1.20161012T134532Z.d01b9c75fc39aa74.7d1be75a5166a80583eeb020e10f584168da6bf7")
supported_langs = t.langs
d = []
for i in supported_langs:
d.append(languages[i])
return sorted(zip(supported_langs, d))

View File

@ -25,15 +25,11 @@ class translateDialog(baseDialog.BaseWXDialog):
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
staticSource = wx.StaticText(panel, -1, _(u"Source language"))
self.source_lang = wx.ComboBox(panel, -1, choices=[x[1] for x in translator.available_languages()], style = wx.CB_READONLY)
self.source_lang.SetFocus()
staticDest = wx.StaticText(panel, -1, _(u"Target language"))
self.source_lang.SetSelection(0)
self.dest_lang = wx.ComboBox(panel, -1, choices=[x[1] for x in translator.available_languages()], style = wx.CB_READONLY)
self.dest_lang.SetFocus()
self.dest_lang.SetSelection(0)
listSizer = wx.BoxSizer(wx.HORIZONTAL)
listSizer.Add(staticSource)
listSizer.Add(self.source_lang)
listSizer.Add(staticDest)
listSizer.Add(self.dest_lang)
ok = wx.Button(panel, wx.ID_OK)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
""" The main session object. Here are the twitter functions to interact with the "model" of TWBlue."""
import wx
import urllib2
import config
import twitter
@ -16,10 +17,11 @@ import config_utils
import shelve
import application
import os
from mysc.thread_utils import stream_threaded
from mysc.thread_utils import stream_threaded, call_threaded
from pubsub import pub
log = logging.getLogger("sessionmanager.session")
from long_tweets import tweets, twishort
from wxUI import authorisationDialog
sessions = {}
@ -166,7 +168,20 @@ class Session(object):
if self.logged == True:
raise Exceptions.AlreadyAuthorisedError("The authorisation process is not needed at this time.")
else:
self.twitter.authorise(self.settings)
self.authorisation_thread = call_threaded(self.twitter.authorise, self.settings)
self.authorisation_dialog = authorisationDialog()
self.authorisation_dialog.cancel.Bind(wx.EVT_BUTTON, self.authorisation_cancelled)
pub.subscribe(self.authorisation_accepted, "authorisation-accepted")
self.authorisation_dialog.ShowModal()
def authorisation_cancelled(self, *args, **kwargs):
pub.sendMessage("authorisation-cancelled")
self.authorisation_dialog.Destroy()
del self.authorisation_dialog
def authorisation_accepted(self):
pub.unsubscribe(self.authorisation_accepted, "authorisation-accepted")
self.authorisation_dialog.Destroy()
def get_more_items(self, update_function, users=False, name=None, *args, **kwargs):
results = []

View File

@ -74,3 +74,16 @@ class sessionManagerWindow(wx.Dialog):
def destroy(self):
self.Destroy()
class authorisationDialog(wx.Dialog):
def __init__(self):
super(authorisationDialog, self).__init__(parent=None, title=_(u"Authorising account..."))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.text = wx.TextCtrl(panel, -1, _("Waiting for account authorisation..."), style=wx.TE_READONLY|wx.TE_MULTILINE)
self.cancel = wx.Button(panel, wx.ID_CANCEL)
sizer.Add(self.text, 0, wx.ALL, 5)
sizer.Add(self.cancel, 0, wx.ALL, 5)
panel.SetSizer(sizer)
min = sizer.CalcMin()
self.SetClientSize(min)

View File

@ -104,7 +104,7 @@ class soundSystem(object):
sound_object.play()
class URLStream(object):
def __init__(self,url=None):
def __init__(self, url=None):
self.url = url
self.prepared = False
log.debug("URL Player initialized")
@ -118,12 +118,10 @@ class URLStream(object):
transformer = audio_services.find_url_transformer(self.url)
self.url = transformer(self.url)
log.debug("Transformed URL: %s. Prepared" % (self.url,))
self.prepared = True
else:
self.url = url
log.debug("Transformed URL: %s. Prepared" % (self.url,))
self.prepared = True
self.prepared = True
def seek(self,step):
pos=self.stream.get_position()
@ -154,7 +152,7 @@ class URLStream(object):
self.stream.volume = float(volume)
self.stream.play()
log.debug("played")
# call_threaded(self.delete_when_done)
self.prepared=False
def stop_audio(self,delete=False):
if hasattr(self, "stream"):
@ -167,6 +165,7 @@ class URLStream(object):
if delete:
del self.stream
log.debug("Deleted audio stream.")
self.prepared=False
return True
else:
return False

View File

@ -2,11 +2,12 @@
import BaseHTTPServer
import application
from urlparse import urlparse, parse_qs
from pubsub import pub
logged = False
verifier = None
class handler(BaseHTTPServer.BaseHTTPRequestHandler):
class handler(BaseHTTPServer.BaseHTTPRequestHandler, object):
def do_GET(self):
global logged
@ -18,4 +19,14 @@ class handler(BaseHTTPServer.BaseHTTPRequestHandler):
global verifier
verifier = params.get('oauth_verifier', [None])[0]
self.wfile.write(u"You have successfully logged into Twitter with {0}. You can close this window now.".format(application.name))
pub.sendMessage("authorisation-accepted")
pub.unsubscribe(self.cancelled, "authorisation-cancelled")
self.finish()
def __init__(self, *args, **kwargs):
pub.subscribe(self.cancelled, "authorisation-cancelled")
super(handler, self).__init__(*args, **kwargs)
def cancelled(self):
pub.unsubscribe(self.cancelled, "authorisation-cancelled")
self.finish()

View File

@ -184,7 +184,7 @@ def compose_event(data, username, show_screen_names=False):
else: event = _(u"%s(@%s) has removed you from the list %s") % (data["source"]["name"], data["source"]["screen_name"], data["target_object"]["name"])
elif data["event"] == "list_user_subscribed":
if data["source"]["screen_name"] == username: event = _(u"You've subscribed to the list %s, which is owned by %s(@%s)") % (data["target_object"]["name"], data["target"]["name"], data["target"]["screen_name"])
else: event = _(u"%s(@%s) has suscribed you to the list %s") % (data["source"]["name"], data["source"]["screen_name"], data["target_object"]["name"])
else: event = _(u"%s(@%s) has subscribed you to the list %s") % (data["source"]["name"], data["source"]["screen_name"], data["target_object"]["name"])
elif data["event"] == "list_user_unsubscribed":
if data["source"]["screen_name"] == username: event = _(u"You've unsubscribed from the list %s, which is owned by %s(@%s)") % (data["target_object"]["name"], data["target"]["name"], data["target"]["screen_name"])
else: event = _("You've been unsubscribed from the list %s, which is owned by %s(@%s)") % (data["target_object"]["name"], data["source"]["name"], data["source"]["screen_name"])

View File

@ -29,7 +29,12 @@ def find_urls (tweet):
i = "full_text"
else:
i = "text"
return [s[0] for s in url_re.findall(tweet[i])]
shorten_urls = find_urls_in_text(tweet[i])
for url in range(0, len(shorten_urls)):
try:
urls.append(tweet["entities"]["urls"][url]["expanded_url"])
except: pass
return urls
def find_item(id, 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>'
__version__ = '3.3.0'
__version__ = '3.6.0'
from .api import Twython
from .streaming import TwythonStreamer

View File

@ -145,6 +145,7 @@ class Twython(EndpointsMixin, object):
else:
params = params
files = list()
requests_args = {}
for k, v in self.client_args.items():
# Maybe this should be set as a class variable and only done once?
@ -195,17 +196,16 @@ class Twython(EndpointsMixin, object):
error_message,
error_code=response.status_code,
retry_after=response.headers.get('X-Rate-Limit-Reset'))
content=""
try:
if response.status_code == 204:
content = response.content
else:
content = response.json()
except ValueError:
# Send the response as is for working with /media/metadata/create.json.
content = response.content
# raise TwythonError('Response was not valid JSON. \
# Unable to decode.')
if response.content!="":
raise TwythonError('Response was not valid JSON. \
Unable to decode.')
return content
@ -258,8 +258,10 @@ class Twython(EndpointsMixin, object):
url = endpoint
else:
url = '%s/%s.json' % (self.api_url % version, endpoint)
content = self._request(url, method=method, params=params,
api_call=url)
return content
def get(self, endpoint, params=None, version='1.1'):
@ -473,6 +475,11 @@ class Twython(EndpointsMixin, object):
>>> print result
"""
if not callable(function):
raise TypeError('.cursor() takes a Twython function as its first \
argument. Did you provide the result of a \
function call?')
if not hasattr(function, 'iter_mode'):
raise TwythonError('Unable to create generator for Twython \
method "%s"' % function.__name__)
@ -531,7 +538,7 @@ class Twython(EndpointsMixin, object):
@staticmethod
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, symbols replaced with links)
:param tweet: Tweet object from received from Twitter API
:param use_display_url: Use display URL to represent link
@ -547,62 +554,116 @@ class Twython(EndpointsMixin, object):
if 'retweeted_status' in tweet:
tweet = tweet['retweeted_status']
if 'extended_tweet' in tweet:
tweet = tweet['extended_tweet']
orig_tweet_text = tweet.get('full_text') or tweet['text']
display_text_range = tweet.get('display_text_range') or [0, len(orig_tweet_text)]
display_text_start, display_text_end = display_text_range[0], display_text_range[1]
display_text = orig_tweet_text[display_text_start:display_text_end]
prefix_text = orig_tweet_text[0:display_text_start]
suffix_text = orig_tweet_text[display_text_end:len(orig_tweet_text)]
if 'entities' in tweet:
text = tweet['text']
entities = tweet['entities']
# We'll put all the bits of replacement HTML and their starts/ends
# in this list:
entities = []
# 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]
if 'user_mentions' in tweet['entities']:
for entity in tweet['entities']['user_mentions']:
temp = {}
temp['start'] = entity['indices'][0]
temp['end'] = entity['indices'][1]
mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>'
text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)',
mention_html % {'screen_name': entity['screen_name']}, text)
mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>' % {'screen_name': entity['screen_name']}
if display_text_start <= temp['start'] <= display_text_end:
temp['replacement'] = mention_html
entities.append(temp)
else:
# Make the '@username' at the start, before
# display_text, into a link:
sub_expr = r'(?<!>)' + orig_tweet_text[temp['start']:temp['end']] + '(?!</a>)'
prefix_text = re.sub(sub_expr, mention_html, prefix_text)
# Hashtags
for entity in sorted(entities['hashtags'],
key=lambda hashtag: len(hashtag['text']), reverse=True):
start, end = entity['indices'][0], entity['indices'][1]
if 'hashtags' in tweet['entities']:
for entity in tweet['entities']['hashtags']:
temp = {}
temp['start'] = entity['indices'][0]
temp['end'] = entity['indices'][1]
hashtag_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>'
text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)',
hashtag_html % {'hashtag': entity['text']}, text)
url_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>' % {'hashtag': entity['text']}
# Urls
for entity in entities['urls']:
start, end = entity['indices'][0], entity['indices'][1]
if use_display_url and entity.get('display_url') \
and not use_expanded_url:
shown_url = entity['display_url']
elif use_expanded_url and entity.get('expanded_url'):
shown_url = entity['expanded_url']
else:
shown_url = entity['url']
temp['replacement'] = url_html
entities.append(temp)
url_html = '<a href="%s" class="twython-url">%s</a>'
text = text.replace(tweet['text'][start:end],
url_html % (entity['url'], shown_url))
# Symbols
if 'symbols' in tweet['entities']:
for entity in tweet['entities']['symbols']:
temp = {}
temp['start'] = entity['indices'][0]
temp['end'] = entity['indices'][1]
# Media
if 'media' in entities:
for entity in entities['media']:
start, end = entity['indices'][0], entity['indices'][1]
if use_display_url and entity.get('display_url') \
and not use_expanded_url:
url_html = '<a href="https://twitter.com/search?q=%%24%(symbol)s" class="twython-symbol">$%(symbol)s</a>' % {'symbol': entity['text']}
temp['replacement'] = url_html
entities.append(temp)
# URLs
if 'urls' in tweet['entities']:
for entity in tweet['entities']['urls']:
temp = {}
temp['start'] = entity['indices'][0]
temp['end'] = entity['indices'][1]
if use_display_url and entity.get('display_url') and not use_expanded_url:
shown_url = entity['display_url']
elif use_expanded_url and entity.get('expanded_url'):
shown_url = entity['expanded_url']
else:
shown_url = entity['url']
url_html = '<a href="%s" class="twython-media">%s</a>'
text = text.replace(tweet['text'][start:end],
url_html % (entity['url'], shown_url))
url_html = '<a href="%s" class="twython-url">%s</a>' % (entity['url'], shown_url)
if expand_quoted_status and tweet.get('is_quote_status'):
if display_text_start <= temp['start'] <= display_text_end:
temp['replacement'] = url_html
entities.append(temp)
else:
suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html)
if 'media' in tweet['entities']:
for entity in tweet['entities']['media']:
temp = {}
temp['start'] = entity['indices'][0]
temp['end'] = entity['indices'][1]
if use_display_url and entity.get('display_url') and not use_expanded_url:
shown_url = entity['display_url']
elif use_expanded_url and entity.get('expanded_url'):
shown_url = entity['expanded_url']
else:
shown_url = entity['url']
url_html = '<a href="%s" class="twython-media">%s</a>' % (entity['url'], shown_url)
if display_text_start <= temp['start'] <= display_text_end:
temp['replacement'] = url_html
entities.append(temp)
else:
suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html)
# Now do all the replacements, starting from the end, so that the
# start/end indices still work:
for entity in sorted(entities, key=lambda e: e['start'], reverse=True):
display_text = display_text[0:entity['start']] + entity['replacement'] + display_text[entity['end']:]
quote_text = ''
if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'):
quoted_status = tweet['quoted_status']
text += '<blockquote class="twython-quote">%(quote)s<cite><a href="%(quote_tweet_link)s">' \
quote_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>' % \
@ -612,4 +673,9 @@ class Twython(EndpointsMixin, object):
'quote_user_name': quoted_status['user']['name'],
'quote_user_screen_name': quoted_status['user']['screen_name']}
return text
return '%(prefix)s%(display)s%(suffix)s%(quote)s' % {
'prefix': '<span class="twython-tweet-prefix">%s</span>' % prefix_text if prefix_text else '',
'display': display_text,
'suffix': '<span class="twython-tweet-suffix">%s</span>' % suffix_text if suffix_text else '',
'quote': quote_text
}

View File

@ -17,10 +17,12 @@ https://dev.twitter.com/docs/api/1.1
import json
import os
import warnings
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from io import BytesIO
from time import sleep
#try:
#from StringIO import StringIO
#except ImportError:
#from io import StringIO
from .advisory import TwythonDeprecationWarning
@ -143,15 +145,20 @@ class EndpointsMixin(object):
Docs:
https://dev.twitter.com/rest/reference/post/media/upload
"""
# https://dev.twitter.com/rest/reference/get/media/upload-status
if params and params.get('command', '') == 'STATUS':
return self.get('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.
def create_metadata(self, **params):
""" Adds metadata to a media element, such as image descriptions for visually impaired.
Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create
"""
params = json.dumps(params)
return self.post("media/metadata/create", params=params)
return self.post("https://upload.twitter.com/1.1/media/metadata/create.json", params=params)
def upload_video(self, media, media_type, size=None):
def upload_video(self, media, media_type, media_category=None, size=None, check_progress=False):
"""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.
@ -176,7 +183,8 @@ class EndpointsMixin(object):
params = {
'command': 'INIT',
'media_type': media_type,
'total_bytes': size
'total_bytes': size,
'media_category': media_category
}
response_init = self.post(upload_url, params=params)
media_id = response_init['media_id']
@ -187,7 +195,7 @@ class EndpointsMixin(object):
data = media.read(1*1024*1024)
if not data:
break
media_chunk = StringIO()
media_chunk = BytesIO()
media_chunk.write(data)
media_chunk.seek(0)
@ -205,7 +213,38 @@ class EndpointsMixin(object):
'command': 'FINALIZE',
'media_id': media_id
}
return self.post(upload_url, params=params)
response = self.post(upload_url, params=params)
# Only get the status if explicity asked to
# Default to False
if check_progress:
# Stage 4: STATUS call if still processing
params = {
'command': 'STATUS',
'media_id': media_id
}
# added code to handle if media_category is NOT set and check_progress=True
# the API will return a NoneType object in this case
try:
processing_state = response.get('processing_info').get('state')
except AttributeError:
return response
if processing_state:
while (processing_state == 'pending' or processing_state == 'in_progress') :
# get the secs to wait
check_after_secs = response.get('processing_info').get('check_after_secs')
if check_after_secs:
sleep(check_after_secs)
response = self.get(upload_url, params=params)
# get new state after waiting
processing_state = response.get('processing_info').get('state')
return response
def get_oembed_tweet(self, **params):
"""Returns information allowing the creation of an embedded

View File

@ -10,7 +10,7 @@ logger = logging.getLogger("updater")
def do_update(endpoint=application.update_url):
try:
update.perform_update(endpoint=endpoint, current_version=application.version, app_name=application.name, update_available_callback=available_update_dialog, progress_callback=progress_callback, update_complete_callback=update_finished)
result = update.perform_update(endpoint=endpoint, current_version=application.version, app_name=application.name, update_available_callback=available_update_dialog, progress_callback=progress_callback, update_complete_callback=update_finished)
except:
if endpoint == application.update_url:
logger.error("Update failed! Using mirror URL...")
@ -18,3 +18,4 @@ def do_update(endpoint=application.update_url):
else:
logger.exception("Update failed.")
output.speak("An exception occurred while attempting to update " + application.name + ". If this message persists, contact the " + application.name + " developers. More information about the exception has been written to the error log.",True)
return result

View File

@ -25,7 +25,7 @@ def unshorten (url, service=None, **kwargs):
def default_service ():
return shorteners.TinyurlShortener
return shorteners.AcortameShortener
def find_service (service, **kwargs):
for i in shorteners.__all__:

View File

@ -6,4 +6,5 @@ from tinyarrows import TinyArrowsShortener
from tinyurl import TinyurlShortener
from xedcc import XedccShortener
from clckru import ClckruShortener
__all__ = ["HKCShortener", "IsgdShortener", "OnjmeShortener", "TinyArrowsShortener", "TinyurlShortener", "XedccShortener", "ClckruShortener"]
from acortame import AcortameShortener
__all__ = ["HKCShortener", "IsgdShortener", "OnjmeShortener", "TinyArrowsShortener", "TinyurlShortener", "XedccShortener", "ClckruShortener", "AcortameShortener"]

View File

@ -0,0 +1,27 @@
from url_shortener import URLShortener
import requests
import urllib
class AcortameShortener (URLShortener):
def __init__(self, *args, **kwargs):
self.name = "acorta.me"
super(AcortameShortener, self).__init__(*args, **kwargs)
def _shorten (self, url):
answer = url
api = requests.get ("https://acorta.me/api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer
def created_url (self, url):
return 'acorta.me' in url
def unshorten (self, url):
if not 'acorta.me' in url:
#use generic expand method
return super(AcortameShortener, self).unshorten(url)
answer = url
api = requests.get ("https://acorta.me/api.php?action=expand&format=simple&shorturl=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer

View File

@ -1,19 +1,18 @@
import urllib
import requests
from url_shortener import URLShortener
class ClckruShortener (URLShortener):
def __init__ (self, *args, **kwargs):
self.name = "clck.ru"
return super(ClckruShortener, self).__init__(*args, **kwargs)
super(ClckruShortener, self).__init__(*args, **kwargs)
def _shorten (self, url):
answer = url
api = urllib.urlopen ("http://clck.ru/--?url=" + urllib.quote(url))
if api.getcode() == 200:
answer = api.read()
api.close()
api = requests.get ("http://clck.ru/--?url=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer
def created_url (self, url):

View File

@ -1,5 +1,5 @@
import urllib
import requests
from url_shortener import URLShortener
class HKCShortener (URLShortener):
@ -9,10 +9,9 @@ class HKCShortener (URLShortener):
def _shorten (self, url):
answer = url
api = urllib.urlopen ("http://hkc.im/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.getcode() == 200:
answer = api.read()
api.close()
api = requests.get ("http://hkc.im/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer
def created_url (self, url):

View File

@ -1,19 +1,18 @@
import urllib
import requests
from url_shortener import URLShortener
class IsgdShortener (URLShortener):
def __init__ (self, *args, **kwargs):
self.name = "Is.gd"
return super(IsgdShortener, self).__init__(*args, **kwargs)
super(IsgdShortener, self).__init__(*args, **kwargs)
def _shorten (self, url):
answer = url
api = urllib.urlopen ("http://is.gd/api.php?longurl=" + urllib.quote(url))
if api.getcode() == 200:
answer = api.read()
api.close()
api = requests.get ("http://is.gd/api.php?longurl=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer
def created_url (self, url):

View File

@ -1,5 +1,5 @@
import urllib
import requests
from url_shortener import URLShortener
class OnjmeShortener (URLShortener):
@ -9,10 +9,9 @@ class OnjmeShortener (URLShortener):
def _shorten (self, url):
answer = url
api = urllib.urlopen ("http://onj.me/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.getcode() == 200:
answer = api.read()
api.close()
api = requests.get ("http://onj.me/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer
def created_url (self, url):

View File

@ -1,5 +1,5 @@
import urllib
import requests
from url_shortener import URLShortener
class TinyArrowsShortener (URLShortener):
@ -9,8 +9,10 @@ class TinyArrowsShortener (URLShortener):
def _shorten (self, url):
answer = url
answer = urllib.urlopen("http://tinyarro.ws/api-create.php?utfpure=1&url=%s" % urllib.quote(url)).read()
api = requests.get("http://tinyarro.ws/api-create.php?utfpure=1&url=%s" % urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer.decode('UTF-8')
def created_url(self, url):
return False
return "tinyarro.ws" in url

View File

@ -1,4 +1,5 @@
from url_shortener import URLShortener
import requests
import urllib
class TinyurlShortener (URLShortener):
def __init__(self, *args, **kwargs):
@ -6,12 +7,10 @@ class TinyurlShortener (URLShortener):
super(TinyurlShortener, self).__init__(*args, **kwargs)
def _shorten (self, url):
answer = url
api = urllib.urlopen ("http://tinyurl.com/api-create.php?url=" + urllib.quote(url))
if api.getcode() == 200:
answer = api.read()
api.close()
api = requests.get ("http://tinyurl.com/api-create.php?url=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer
def created_url (self, url):

View File

@ -1,6 +1,4 @@
from httplib import HTTPConnection
from urlparse import urlparse
import requests
class URLShortener (object):
@ -22,12 +20,18 @@ class URLShortener (object):
raise NotImplementedError
def unshorten(self, url):
working = urlparse(url)
if not working.netloc:
raise TypeError, "Unable to parse URL."
con = HTTPConnection(working.netloc)
con.connect()
con.request('GET', working.path)
resp = con.getresponse()
con.close()
return resp.getheader('location')
try:
r=requests.head(url)
if 'location' in r.headers.keys():
if 'dropbox.com' in r.headers['location']:
return handle_dropbox(r.headers['location'])
else:
return r.headers['location']
except:
return url #we cannot expand
def handle_dropbox(url):
if url.endswith("dl=1"):
return url
else:
return url.replace("dl=0", "dl=1")

View File

@ -1,5 +1,5 @@
import urllib
import requests
from url_shortener import URLShortener
class XedccShortener (URLShortener):
@ -9,10 +9,9 @@ class XedccShortener (URLShortener):
def _shorten (self, url):
answer = url
api = urllib.urlopen ("http://xed.cc/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.getcode() == 200:
answer = api.read()
api.close()
api = requests.get ("http://xed.cc/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.status_code == 200:
answer = api.text
return answer
def created_url (self, url):

View File

@ -26,8 +26,7 @@ class searchDialog(baseDialog.BaseWXDialog):
radioSizer.Add(self.users, 0, wx.ALL, 5)
sizer.Add(radioSizer, 0, wx.ALL, 5)
lang = wx.StaticText(panel, -1, _(u"&Language for results: "))
langs = [x[1] for x in translator.translator.available_languages()]
langs[:] = langs[1:]
langs = [x for x in translator.translator.languages.values()]
langs.insert(0, _(u"any"))
self.lang = wx.ComboBox(panel, -1, choices=langs, value=langs[0], style = wx.CB_READONLY)
langBox = wx.BoxSizer(wx.HORIZONTAL)
@ -51,7 +50,12 @@ class searchDialog(baseDialog.BaseWXDialog):
self.SetClientSize(sizer.CalcMin())
def get_language(self):
return [x[0] for x in translator.translator.available_languages()][self.lang.GetSelection()]
l = self.lang.GetStringSelection()
if l == _(u"any"):
return ""
for langcode, langname in translator.translator.languages.iteritems():
if langname == l:
return langcode
def get_result_type(self):
r = self.resultstype.GetValue()

View File

@ -1,4 +1,4 @@
{"current_version": "0.91",
{"current_version": "0.92",
"description": "The first version for the new generation of TWBlue.",
"date": "day_name_abr month day_numb, 2016",
"downloads":

@ -1 +1 @@
Subproject commit f8bd2d8c6ce174f31cb28c47172935a5162af546
Subproject commit 990bb47acc45df5bc7996aae676c27a4a3467edd