mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2025-04-20 09:31:43 -04:00
Sync upstream.
This commit is contained in:
commit
a49c4fa4f1
12
README.md
12
README.md
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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 = []
|
||||||
|
@ -73,4 +73,17 @@ class sessionManagerWindow(wx.Dialog):
|
|||||||
self.configuration.Hide()
|
self.configuration.Hide()
|
||||||
|
|
||||||
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)
|
@ -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
|
||||||
|
@ -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()
|
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()
|
@ -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"])
|
||||||
|
@ -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)):
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -10,11 +10,12 @@ 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...")
|
||||||
return do_update(endpoint=application.mirror_update_url)
|
return do_update(endpoint=application.mirror_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
|
@ -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__:
|
||||||
|
@ -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"]
|
||||||
|
27
src/url_shortener/shorteners/acortame.py
Normal file
27
src/url_shortener/shorteners/acortame.py
Normal 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
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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")
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user