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 #### 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. 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 221
* [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
@ -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 #### 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 #### 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 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, ... Important! Install these 3 apps into the same folder, otherwise you won't be able to build the pa.c version. For example: D:\portableApps\NSISPortable, D:\PortableApps\PortableApps.com installer, ...
#### Dependencies to make the spell checker multilingual #### #### Dependencies to make the spell checker multilingual ####
In order to add the support for spell checking in more languages than english you need to add some additional dictionaries to pyenchant. These are located on the dictionaries folder under windows-dependencies. Simply copy them to the share/enchant/myspell folder located in your enchant installation 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 ### Running TW Blue from source

View File

@ -2,7 +2,7 @@
name = 'TWBlue' name = 'TWBlue'
snapshot = False snapshot = False
if snapshot == False: if snapshot == False:
version = "0.91" version = "0.92"
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

@ -2,6 +2,14 @@
## changes in this version ## 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)) * 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)) * 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)) * Release audio files after uploading them. ([#130](https://github.com/manuelcortez/TWBlue/issues/130))

View File

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

View File

@ -15,10 +15,10 @@ 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.91" VIAddVersionKey ProductVersion "0.92"
VIAddVersionKey FileVersion "0.91" VIAddVersionKey FileVersion "0.92"
VIProductVersion "0.91.0.0" VIProductVersion "0.92.0.0"
VIFileVersion "0.91.0.0" VIFileVersion "0.92.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"
@ -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\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.91" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.92"
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" 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" "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

@ -2,7 +2,7 @@
name = 'TWBlue' name = 'TWBlue'
snapshot = False snapshot = False
if snapshot == False: if snapshot == False:
version = "0.91" version = "0.92"
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

@ -28,21 +28,5 @@ def convert_soundcloud (url):
else: else:
raise TypeError('%r is not streamable' % url) 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): def convert_generic_audio(url):
return url return url

View File

@ -157,7 +157,7 @@ class bufferController(object):
for i in attachments: for i in attachments:
photo = open(i["file"], "rb") photo = open(i["file"], "rb")
img = self.session.twitter.twitter.upload_media(media=photo) 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"]) media_ids.append(img["media_id"])
self.session.twitter.twitter.update_status(status=text, media_ids=media_ids) self.session.twitter.twitter.update_status(status=text, media_ids=media_ids)
@ -988,7 +988,7 @@ class searchBufferController(baseBufferController):
# return None # return None
num = self.session.order_buffer(self.name, val) num = self.session.order_buffer(self.name, val)
self.put_items_on_list(num) 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") self.session.sound.play("search_updated.ogg")
return num return num
@ -1068,7 +1068,7 @@ class searchPeopleBufferController(peopleBufferController):
number_of_items = self.session.order_cursored_buffer(self.name, val) number_of_items = self.session.order_cursored_buffer(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,)) log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(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") self.session.sound.play("search_updated.ogg")
return number_of_items return number_of_items
@ -1148,7 +1148,8 @@ class trendsBufferController(bufferController):
self.name_ = data[0]["locations"][0]["name"] self.name_ = data[0]["locations"][0]["name"]
self.trends = data[0]["trends"] self.trends = data[0]["trends"]
self.put_items_on_the_list() 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): def put_items_on_the_list(self):
selected_item = self.buffer.list.get_selected() 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) number_of_items = self.session.order_buffer(self.name, self.statuses)
log.debug("Number of items retrieved: %d" % (number_of_items,)) log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(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") self.session.sound.play("search_updated.ogg")
return number_of_items return number_of_items

View File

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

View File

@ -40,9 +40,8 @@ class basicTweet(object):
dlg = translator.gui.translateDialog() dlg = translator.gui.translateDialog()
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
text_to_translate = self.message.get_text() text_to_translate = self.message.get_text()
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")] 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.message.set_text(msg)
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()

View File

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from yandex_translate import YandexTranslate 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") 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 = { languages = {
"af": _(u"Afrikaans"), "af": _(u"Afrikaans"),
"sq": _(u"Albanian"), "sq": _(u"Albanian"),
@ -71,7 +73,7 @@ languages = {
"ps": _(u"Pashto"), "ps": _(u"Pashto"),
"fa": _(u"Persian"), "fa": _(u"Persian"),
"pl": _(u"Polish"), "pl": _(u"Polish"),
"pt-PT": _(u"Portuguese"), "pt": _(u"Portuguese"),
"pa": _(u"Punjabi"), "pa": _(u"Punjabi"),
"ro": _(u"Romanian"), "ro": _(u"Romanian"),
"ru": _(u"Russian"), "ru": _(u"Russian"),
@ -101,8 +103,11 @@ languages = {
} }
def available_languages(): def available_languages():
l = languages.keys() global supported_langs, d
d = languages.values() if supported_langs == None and d == None:
l.insert(0, '') t = YandexTranslate("trnsl.1.1.20161012T134532Z.d01b9c75fc39aa74.7d1be75a5166a80583eeb020e10f584168da6bf7")
d.insert(0, _(u"autodetect")) supported_langs = t.langs
return sorted(zip(l, d)) 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")) super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
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")) 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 = 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 = wx.BoxSizer(wx.HORIZONTAL)
listSizer.Add(staticSource)
listSizer.Add(self.source_lang)
listSizer.Add(staticDest) listSizer.Add(staticDest)
listSizer.Add(self.dest_lang) listSizer.Add(self.dest_lang)
ok = wx.Button(panel, wx.ID_OK) ok = wx.Button(panel, wx.ID_OK)

View File

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

View File

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

View File

@ -2,11 +2,12 @@
import BaseHTTPServer import BaseHTTPServer
import application import application
from urlparse import urlparse, parse_qs from urlparse import urlparse, parse_qs
from pubsub import pub
logged = False logged = False
verifier = None verifier = None
class handler(BaseHTTPServer.BaseHTTPRequestHandler): class handler(BaseHTTPServer.BaseHTTPRequestHandler, object):
def do_GET(self): def do_GET(self):
global logged global logged
@ -18,4 +19,14 @@ class handler(BaseHTTPServer.BaseHTTPRequestHandler):
global verifier global verifier
verifier = params.get('oauth_verifier', [None])[0] 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)) 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() 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"]) 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": 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"]) 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": 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"]) 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"]) 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" i = "full_text"
else: else:
i = "text" 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): 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.3.0' __version__ = '3.6.0'
from .api import Twython from .api import Twython
from .streaming import TwythonStreamer from .streaming import TwythonStreamer

View File

@ -145,6 +145,7 @@ class Twython(EndpointsMixin, object):
else: else:
params = params params = params
files = list() 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?
@ -195,17 +196,16 @@ class Twython(EndpointsMixin, object):
error_message, error_message,
error_code=response.status_code, error_code=response.status_code,
retry_after=response.headers.get('X-Rate-Limit-Reset')) retry_after=response.headers.get('X-Rate-Limit-Reset'))
content=""
try: try:
if response.status_code == 204: if response.status_code == 204:
content = response.content content = response.content
else: else:
content = response.json() content = response.json()
except ValueError: except ValueError:
# Send the response as is for working with /media/metadata/create.json. if response.content!="":
content = response.content raise TwythonError('Response was not valid JSON. \
# raise TwythonError('Response was not valid JSON. \ Unable to decode.')
# Unable to decode.')
return content return content
@ -258,8 +258,10 @@ 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'):
@ -473,6 +475,11 @@ class Twython(EndpointsMixin, object):
>>> print result >>> 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'): if not hasattr(function, 'iter_mode'):
raise TwythonError('Unable to create generator for Twython \ raise TwythonError('Unable to create generator for Twython \
method "%s"' % function.__name__) method "%s"' % function.__name__)
@ -531,7 +538,7 @@ class Twython(EndpointsMixin, object):
@staticmethod @staticmethod
def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=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, symbols replaced with links)
:param tweet: Tweet object from received from Twitter API :param tweet: Tweet object from received from Twitter API
:param use_display_url: Use display URL to represent link :param use_display_url: Use display URL to represent link
@ -547,62 +554,116 @@ class Twython(EndpointsMixin, object):
if 'retweeted_status' in tweet: if 'retweeted_status' in tweet:
tweet = tweet['retweeted_status'] 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: if 'entities' in tweet:
text = tweet['text'] # We'll put all the bits of replacement HTML and their starts/ends
entities = tweet['entities'] # in this list:
entities = []
# Mentions # Mentions
for entity in sorted(entities['user_mentions'], if 'user_mentions' in tweet['entities']:
key=lambda mention: len(mention['screen_name']), reverse=True): for entity in tweet['entities']['user_mentions']:
start, end = entity['indices'][0], entity['indices'][1] 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>' mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>' % {'screen_name': entity['screen_name']}
text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)',
mention_html % {'screen_name': entity['screen_name']}, text) 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 # Hashtags
for entity in sorted(entities['hashtags'], if 'hashtags' in tweet['entities']:
key=lambda hashtag: len(hashtag['text']), reverse=True): for entity in tweet['entities']['hashtags']:
start, end = entity['indices'][0], entity['indices'][1] 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>' url_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>' % {'hashtag': entity['text']}
text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)',
hashtag_html % {'hashtag': entity['text']}, text)
# Urls temp['replacement'] = url_html
for entity in entities['urls']: entities.append(temp)
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']
url_html = '<a href="%s" class="twython-url">%s</a>' # Symbols
text = text.replace(tweet['text'][start:end], if 'symbols' in tweet['entities']:
url_html % (entity['url'], shown_url)) for entity in tweet['entities']['symbols']:
temp = {}
temp['start'] = entity['indices'][0]
temp['end'] = entity['indices'][1]
# Media url_html = '<a href="https://twitter.com/search?q=%%24%(symbol)s" class="twython-symbol">$%(symbol)s</a>' % {'symbol': entity['text']}
if 'media' in entities:
for entity in entities['media']: temp['replacement'] = url_html
start, end = entity['indices'][0], entity['indices'][1] entities.append(temp)
if use_display_url and entity.get('display_url') \
and not use_expanded_url: # 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'] shown_url = entity['display_url']
elif use_expanded_url and entity.get('expanded_url'): elif use_expanded_url and entity.get('expanded_url'):
shown_url = entity['expanded_url'] shown_url = entity['expanded_url']
else: else:
shown_url = entity['url'] shown_url = entity['url']
url_html = '<a href="%s" class="twython-media">%s</a>' url_html = '<a href="%s" class="twython-url">%s</a>' % (entity['url'], shown_url)
text = text.replace(tweet['text'][start:end],
url_html % (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'] 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-name">%(quote_user_name)s</span>' \
'<span class="twython-quote-user-screenname">@%(quote_user_screen_name)s</span></a>' \ '<span class="twython-quote-user-screenname">@%(quote_user_screen_name)s</span></a>' \
'</cite></blockquote>' % \ '</cite></blockquote>' % \
@ -612,4 +673,9 @@ class Twython(EndpointsMixin, object):
'quote_user_name': quoted_status['user']['name'], 'quote_user_name': quoted_status['user']['name'],
'quote_user_screen_name': quoted_status['user']['screen_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 json
import os import os
import warnings import warnings
try: from io import BytesIO
from StringIO import StringIO from time import sleep
except ImportError: #try:
from io import StringIO #from StringIO import StringIO
#except ImportError:
#from io import StringIO
from .advisory import TwythonDeprecationWarning from .advisory import TwythonDeprecationWarning
@ -143,15 +145,20 @@ class EndpointsMixin(object):
Docs: Docs:
https://dev.twitter.com/rest/reference/post/media/upload 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) return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params)
def set_description(self, **params): def create_metadata(self, **params):
""" Adds a description to an image.""" """ Adds metadata to a media element, such as image descriptions for visually impaired.
# This method only accepts strings, no dictionaries. Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create
"""
params = json.dumps(params) 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 """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 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. to the 'update_status' method using the 'media_ids' param.
@ -176,7 +183,8 @@ class EndpointsMixin(object):
params = { params = {
'command': 'INIT', 'command': 'INIT',
'media_type': media_type, 'media_type': media_type,
'total_bytes': size 'total_bytes': size,
'media_category': media_category
} }
response_init = self.post(upload_url, params=params) response_init = self.post(upload_url, params=params)
media_id = response_init['media_id'] media_id = response_init['media_id']
@ -187,7 +195,7 @@ class EndpointsMixin(object):
data = media.read(1*1024*1024) data = media.read(1*1024*1024)
if not data: if not data:
break break
media_chunk = StringIO() media_chunk = BytesIO()
media_chunk.write(data) media_chunk.write(data)
media_chunk.seek(0) media_chunk.seek(0)
@ -205,7 +213,38 @@ class EndpointsMixin(object):
'command': 'FINALIZE', 'command': 'FINALIZE',
'media_id': media_id '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): def get_oembed_tweet(self, **params):
"""Returns information allowing the creation of an embedded """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): def do_update(endpoint=application.update_url):
try: 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: except:
if endpoint == application.update_url: if endpoint == application.update_url:
logger.error("Update failed! Using mirror URL...") logger.error("Update failed! Using mirror URL...")
@ -18,3 +18,4 @@ def do_update(endpoint=application.update_url):
else: else:
logger.exception("Update failed.") 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) 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 (): def default_service ():
return shorteners.TinyurlShortener return shorteners.AcortameShortener
def find_service (service, **kwargs): def find_service (service, **kwargs):
for i in shorteners.__all__: for i in shorteners.__all__:

View File

@ -6,4 +6,5 @@ from tinyarrows import TinyArrowsShortener
from tinyurl import TinyurlShortener from tinyurl import TinyurlShortener
from xedcc import XedccShortener from xedcc import XedccShortener
from clckru import ClckruShortener 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 urllib
import requests
from url_shortener import URLShortener from url_shortener import URLShortener
class ClckruShortener (URLShortener): class ClckruShortener (URLShortener):
def __init__ (self, *args, **kwargs): def __init__ (self, *args, **kwargs):
self.name = "clck.ru" self.name = "clck.ru"
return super(ClckruShortener, self).__init__(*args, **kwargs) super(ClckruShortener, self).__init__(*args, **kwargs)
def _shorten (self, url): def _shorten (self, url):
answer = url answer = url
api = urllib.urlopen ("http://clck.ru/--?url=" + urllib.quote(url)) api = requests.get ("http://clck.ru/--?url=" + urllib.quote(url))
if api.getcode() == 200: if api.status_code == 200:
answer = api.read() answer = api.text
api.close()
return answer return answer
def created_url (self, url): def created_url (self, url):

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import urllib import urllib
import requests
from url_shortener import URLShortener from url_shortener import URLShortener
class TinyArrowsShortener (URLShortener): class TinyArrowsShortener (URLShortener):
@ -9,8 +9,10 @@ class TinyArrowsShortener (URLShortener):
def _shorten (self, url): def _shorten (self, url):
answer = 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') return answer.decode('UTF-8')
def created_url(self, url): def created_url(self, url):
return False return "tinyarro.ws" in url

View File

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

View File

@ -1,6 +1,4 @@
from httplib import HTTPConnection import requests
from urlparse import urlparse
class URLShortener (object): class URLShortener (object):
@ -22,12 +20,18 @@ class URLShortener (object):
raise NotImplementedError raise NotImplementedError
def unshorten(self, url): def unshorten(self, url):
working = urlparse(url) try:
if not working.netloc: r=requests.head(url)
raise TypeError, "Unable to parse URL." if 'location' in r.headers.keys():
con = HTTPConnection(working.netloc) if 'dropbox.com' in r.headers['location']:
con.connect() return handle_dropbox(r.headers['location'])
con.request('GET', working.path) else:
resp = con.getresponse() return r.headers['location']
con.close() except:
return resp.getheader('location') 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 urllib
import requests
from url_shortener import URLShortener from url_shortener import URLShortener
class XedccShortener (URLShortener): class XedccShortener (URLShortener):
@ -9,10 +9,9 @@ class XedccShortener (URLShortener):
def _shorten (self, url): def _shorten (self, url):
answer = url answer = url
api = urllib.urlopen ("http://xed.cc/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url)) api = requests.get ("http://xed.cc/yourls-api.php?action=shorturl&format=simple&url=" + urllib.quote(url))
if api.getcode() == 200: if api.status_code == 200:
answer = api.read() answer = api.text
api.close()
return answer return answer
def created_url (self, url): def created_url (self, url):

View File

@ -26,8 +26,7 @@ class searchDialog(baseDialog.BaseWXDialog):
radioSizer.Add(self.users, 0, wx.ALL, 5) radioSizer.Add(self.users, 0, wx.ALL, 5)
sizer.Add(radioSizer, 0, wx.ALL, 5) sizer.Add(radioSizer, 0, wx.ALL, 5)
lang = wx.StaticText(panel, -1, _(u"&Language for results: ")) lang = wx.StaticText(panel, -1, _(u"&Language for results: "))
langs = [x[1] for x in translator.translator.available_languages()] langs = [x for x in translator.translator.languages.values()]
langs[:] = langs[1:]
langs.insert(0, _(u"any")) langs.insert(0, _(u"any"))
self.lang = wx.ComboBox(panel, -1, choices=langs, value=langs[0], style = wx.CB_READONLY) self.lang = wx.ComboBox(panel, -1, choices=langs, value=langs[0], style = wx.CB_READONLY)
langBox = wx.BoxSizer(wx.HORIZONTAL) langBox = wx.BoxSizer(wx.HORIZONTAL)
@ -51,7 +50,12 @@ class searchDialog(baseDialog.BaseWXDialog):
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def get_language(self): 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): def get_result_type(self):
r = self.resultstype.GetValue() 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.", "description": "The first version for the new generation of TWBlue.",
"date": "day_name_abr month day_numb, 2016", "date": "day_name_abr month day_numb, 2016",
"downloads": "downloads":

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