Added some code for starting

This commit is contained in:
Manuel Cortez 2016-02-13 17:06:36 -06:00
parent 364472da5c
commit 0266100d66
110 changed files with 6044 additions and 0 deletions

View File

@ -0,0 +1,29 @@
import ctypes
import os
import types
from platform_utils import paths
def load_library(libname):
if paths.is_frozen():
libfile = os.path.join(paths.embedded_data_path(), 'accessible_output2', 'lib', libname)
else:
libfile = os.path.join(paths.module_path(), 'lib', libname)
return ctypes.windll[libfile]
def get_output_classes():
import outputs
module_type = types.ModuleType
classes = [m.output_class for m in outputs.__dict__.itervalues() if type(m) == module_type and hasattr(m, 'output_class')]
return sorted(classes, key=lambda c: c.priority)
def find_datafiles():
import os
import platform
from glob import glob
import accessible_output2
if platform.system() != 'Windows':
return []
path = os.path.join(accessible_output2.__path__[0], 'lib', '*.dll')
results = glob(path)
dest_dir = os.path.join('accessible_output2', 'lib')
return [(dest_dir, results)]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,14 @@
import platform
if platform.system() == 'Windows':
import nvda
import jaws
import window_eyes
import system_access
import dolphin
import pc_talker
elif platform.system() == "Darwin":
import voiceover
elif platform.system() == "Linux":
import speechDispatcher
import auto

View File

@ -0,0 +1,42 @@
import platform
import accessible_output2
from base import Output, OutputError
class Auto(Output):
def __init__(self):
if platform.system() == "Darwin":
import voiceover
self.outputs = [voiceover.VoiceOver()]
elif platform.system() == "Linux":
import speechDispatcher
self.outputs = [speechDispatcher.SpeechDispatcher()]
elif platform.system() == "Windows":
output_classes = accessible_output2.get_output_classes()
self.outputs = []
for output in output_classes:
try:
self.outputs.append(output())
except OutputError:
pass
def get_first_available_output(self):
for output in self.outputs:
if output.is_active():
return output
return None
def speak(self, *args, **kwargs):
output = self.get_first_available_output()
if output:
output.speak(*args, **kwargs)
def braille(self, *args, **kwargs):
output = self.get_first_available_output()
if output:
output.braille(*args, **kwargs)
def output(self, *args, **kwargs):
output = self.get_first_available_output()
if output:
output.speak(*args, **kwargs)

View File

@ -0,0 +1,31 @@
from accessible_output2 import load_library
import platform
class OutputError(Exception):
pass
class Output(object):
name = "Unnamed Output" #The name of this output
lib32 = None #name of 32-bit lib
lib64 = None #name of 64-bit lib
priority = 100 #Where to sort in the list of available outputs for automaticly speaking
def __init__(self):
is_32bit = platform.architecture()[0] == "32bit"
if self.lib32 and is_32bit:
self.lib = load_library(self.lib32)
elif self.lib64:
self.lib = load_library(self.lib64)
def output(self, text, **options):
output = False
if hasattr(self, 'speak') and callable(self.speak):
self.speak(text, **options)
output = True
if hasattr(self, 'braille') and callable(self.braille):
self.braille(text, **options)
output = True
if not output:
raise RuntimeError("Output %r does not have any method defined to output" % self)

View File

@ -0,0 +1,27 @@
import os
from base import Output
class Dolphin (Output):
"""Supports dolphin products."""
name = 'Dolphin'
lib32 = 'dolapi.dll'
def speak(self, text, interrupt=0):
if interrupt:
self.silence()
#If we don't call this, the API won't let us speak.
if self.is_active():
self.lib.DolAccess_Command(unicode(text), (len(text)*2)+2, 1)
def silence(self):
self.lib.DolAccess_Action(141)
def is_active(self):
try:
return self.lib.DolAccess_GetSystem() in (1, 4, 8)
except:
return False
output_class = Dolphin

View File

@ -0,0 +1,33 @@
import win32gui
from libloader.com import load_com
import pywintypes
from base import Output, OutputError
class Jaws (Output):
"""Output supporting the Jaws for Windows screen reader."""
name = 'jaws'
def __init__(self, *args, **kwargs):
super (Jaws, self).__init__(*args, **kwargs)
try:
self.object = load_com("FreedomSci.JawsApi", "jfwapi")
except pywintypes.com_error:
raise OutputError
def braille(self, text, **options):
# HACK: replace " with ', Jaws doesn't seem to understand escaping them with \
text = text.replace('"', "'")
self.object.RunFunction("BrailleString(\"%s\")" % text)
def speak(self, text, interrupt=False):
self.object.SayString(' %s' % text, interrupt)
def is_active(self):
try:
return self.object.SayString('',0) == True or win32gui.FindWindow("JFWUI2", "JAWS") != 0
except:
return False
output_class = Jaws

View File

@ -0,0 +1,31 @@
import os
import platform
from platform_utils import paths
from libloader import load_library
from base import Output
class NVDA(Output):
"""Supports The NVDA screen reader"""
name = "NVDA"
lib32 = 'nvdaControllerClient32.dll'
lib64 = 'nvdaControllerClient64.dll'
def is_active(self):
try:
return self.lib.nvdaController_testIfRunning() == 0
except:
return False
def braille(self, text, **options):
self.lib.nvdaController_brailleMessage(unicode(text))
def speak(self, text, interrupt=False):
if interrupt:
self.silence()
self.lib.nvdaController_speakText(unicode(text))
def silence(self):
self.lib.nvdaController_cancelSpeech()
output_class = NVDA

View File

@ -0,0 +1,19 @@
import ctypes
from base import Output
class PCTalker(Output):
lib32 = 'pctkusr.dll'
lib64 = 'pctkusr64.dll'
def speak(self, text, interrupt=False):
if interrupt:
self.silence()
self.lib.PCTKPRead(text.encode('cp932', 'replace'))
def silence(self):
self.lib.PCTKVReset()
def is_active(self):
return self.lib.PCTKStatus() != 0
output_class = PCTalker

View File

@ -0,0 +1,29 @@
from base import Output, OutputError
import atexit
import application
class SpeechDispatcher(Output):
"""Supports speech dispatcher on Linux.
Note that this module will use the configuration of speech dispatcher, the user will need to configure the voice, language, punctuation and rate before using this module.
"""
name = 'SpeechDispatcher'
def __init__(self, *args, **kwargs):
super(SpeechDispatcher, self).__init__(*args, **kwargs)
try:
import speechd
self.spd = speechd.SSIPClient(application.name)
except ImportError:
raise OutputError
atexit.register(self.on_exit_event)
def speak(self, text, interupt=False):
if interupt == True:
self.spd.cancel()
self.spd.speak(text)
def is_active(self):
return True
def on_exit_event(self):
self.spd.close()
del self.spd

View File

@ -0,0 +1,18 @@
# Copyright (C) 2001, 2002 Brailcom, o.p.s.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from .client import *

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
SPD_SPAWN_CMD = "/usr/bin/speech-dispatcher"

View File

@ -0,0 +1,23 @@
from base import Output
class SystemAccess (Output):
"""Supports System Access and System Access Mobile"""
name = "System Access"
lib32 = 'saapi32.dll'
priority = 99
def braille(self, text, **options):
self.lib.SA_BrlShowTextW(unicode(text))
def speak(self, text, interrupt=False):
if self.is_active():
self.dll.SA_SayW(unicode(text))
def is_active(self):
try:
return self.dll.SA_IsRunning()
except:
return False
output_class = SystemAccess

View File

@ -0,0 +1,23 @@
from base import Output, OutputError
class VoiceOver (Output):
"""Supports the VoiceOver screenreader on the Mac.
Note that this will also output as a message to the braille display if VoiceOver is used with braille.
Calling this module could cause VoiceOver to be started.
"""
name = 'VoiceOver'
def __init__(self, *args, **kwargs):
super(VoiceOver, self).__init__(*args, **kwargs)
try:
from appscript import app
self.app = app('VoiceOver')
except ImportError:
raise OutputError
def speak(self, text, interupt=False):
self.app.output(text)
def is_active(self):
return True

View File

@ -0,0 +1,32 @@
import win32gui
from libloader.com import load_com
from base import Output, OutputError
import pywintypes
class WindowEyes (Output):
"""Speech output supporting the WindowEyes screen reader"""
name = 'Window-Eyes'
def __init__(self, *args, **kwargs):
super(WindowEyes, self).__init__(*args, **kwargs)
try:
self.object = load_com("gwspeak.speak")
except pywintypes.com_error:
raise OutputError
def speak(self, text, interrupt=0):
if interrupt:
self.silence()
self.object.SpeakString(text)
def silence (self):
self.object.Silence()
def is_active(self):
try:
return win32gui.FindWindow("GWMExternalControl", "External Control") != 0
except:
return False
output_class = WindowEyes

View File

@ -0,0 +1,4 @@
[app-settings]
username = string(default="")
password = string(default="")
language = string(default="system")

16
src/config.py Normal file
View File

@ -0,0 +1,16 @@
# -*- coding: cp1252 -*-
import config_utils
import paths
import logging
log = logging.getLogger("config")
MAINFILE = "socializer.conf"
MAINSPEC = "app-configuration.defaults"
app = None
def setup ():
global app
log.debug("Loading global app settings...")
app = config_utils.load_config(paths.config_path(MAINFILE), paths.app_path(MAINSPEC))

73
src/config_utils.py Normal file
View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
from configobj import ConfigObj, ParseError
from validate import Validator, ValidateError
import os
import string
class ConfigLoadError(Exception): pass
def load_config(config_path, configspec_path=None, *args, **kwargs):
if os.path.exists(config_path):
clean_config(config_path)
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
try:
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
except ParseError:
raise ConfigLoadError("Unable to load %r" % config_path)
validator = Validator()
validated = config.validate(validator, copy=True)
if validated == True:
config.write()
return config
def is_blank(arg):
"Check if a line is blank."
for c in arg:
if c not in string.whitespace:
return False
return True
def get_keys(path):
"Gets the keys of a configobj config file."
res=[]
fin=open(path)
for line in fin:
if not is_blank(line):
res.append(line[0:line.find('=')].strip())
fin.close()
return res
def hist(keys):
"Generates a histogram of an iterable."
res={}
for k in keys:
res[k]=res.setdefault(k,0)+1
return res
def find_problems(hist):
"Takes a histogram and returns a list of items occurring more than once."
res=[]
for k,v in hist.items():
if v>1:
res.append(k)
return res
def clean_config(path):
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
orig=[]
cleaned=[]
fin=open(path)
for line in fin:
orig.append(line)
fin.close()
for p in find_problems(hist(get_keys(path))):
for o in orig:
o.strip()
if p not in o:
cleaned.append(o)
if len(cleaned) != 0:
cam=open(path,'w')
for c in cleaned:
cam.write(c)
cam.close()
return True
else:
return False

View File

65
src/controller/buffers.py Normal file
View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
from sessionmanager import session
import widgetUtils
import messages
from wxUI.tabs import home
from pubsub import pub
class baseBuffer(object):
def __init__(self, parent=None, name="", session=None, composefunc=None, *args, **kwargs):
super(baseBuffer, self).__init__()
self.args = args
self.kwargs = kwargs
self.create_tab(parent)
self.tab.name = name
self.session = session
self.compose_function = composefunc
self.update_function = "get_page"
self.name = name
self.connect_events()
def create_tab(self, parent):
self.tab = home.homeTab(parent)
def insert(self, item, reversed=False):
item_ = getattr(session, self.compose_function)(item, self.session)
self.tab.list.insert_item(reversed, *item_)
def get_items(self, no_next=True):
num = getattr(self.session, "get_newsfeed")(no_next=no_next, name=self.name, *self.args, **self.kwargs)
print num
if no_next == True:
if self.tab.list.get_count() > 0 and num > 0:
print "inserting a value"
[self.insert(i, True) for i in self.session.db[self.name]["items"][-num:]]
else:
[self.insert(i) for i in self.session.db[self.name]["items"][:num]]
def post(self, *args, **kwargs):
p = messages.post(title=_(u"Write your post"), caption="", text="")
if p.message.get_response() == widgetUtils.OK:
msg = p.message.get_text().encode("utf-8")
privacy_opts = p.get_privacy_options()
self.session.post_wall_status(message=msg, friends_only=privacy_opts)
pub.sendMessage("posted", buffer=self.name)
def connect_events(self):
widgetUtils.connect_event(self.tab.post, widgetUtils.BUTTON_PRESSED, self.post)
class feedBuffer(baseBuffer):
def get_items(self, no_next=True):
num = getattr(self.session, "get_page")(no_next=no_next, name=self.name, *self.args, **self.kwargs)
print num
if no_next == True:
if self.tab.list.get_count() > 0 and num > 0:
print "inserting a value"
[self.insert(i, True) for i in self.session.db[self.name]["items"][-num:]]
else:
[self.insert(i) for i in self.session.db[self.name]["items"][:num]]
class audioBuffer(feedBuffer):
def create_tab(self, parent):
self.tab = home.audioTab(parent)

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
import widgetUtils
import messages
from wxUI import (mainWindow)
import buffers
from pubsub import pub
from mysc.repeating_timer import RepeatingTimer
from sessionmanager import session
class Controller(object):
def search(self, tab_name):
for i in xrange(0, len(self.buffers)):
if self.buffers[i].name == tab_name:
return self.buffers[i]
return False
def __init__(self):
super(Controller, self).__init__()
self.buffers = []
self.window = mainWindow.mainWindow()
self.window.change_status(_(u"Ready"))
self.session = session.sessions[session.sessions.keys()[0]]
# self.session.authorise()
self.create_controls()
self.window.Show()
def create_controls(self):
home = buffers.baseBuffer(parent=self.window.tb, name="home_timeline", session=self.session, composefunc="compose_new", endpoint="newsfeed", identifier="id")
self.buffers.append(home)
self.window.add_buffer(home.tab, _(u"Home"))
self.repeatedUpdate = RepeatingTimer(180, self.update_all_buffers)
self.repeatedUpdate.start()
feed = buffers.feedBuffer(parent=self.window.tb, name="me_feed", composefunc="compose_status", session=self.session, endpoint="get", parent_endpoint="wall", identifier="id")
self.buffers.append(feed)
self.window.add_buffer(feed.tab, _(u"My wall"))
audio = buffers.audioBuffer(parent=self.window.tb, name="me_audio", composefunc="compose_audio", session=self.session, endpoint="get", parent_endpoint="audio", full_list=True, identifier="aid")
self.buffers.append(audio)
self.window.add_buffer(audio.tab, _(u"My audios"))
pub.subscribe(self.in_post, "posted")
def login(self):
self.window.change_status(_(u"Logging in VK"))
self.session.authorise()
self.window.change_status(_(u"Ready"))
for i in self.buffers:
if hasattr(i, "get_items"):
self.window.change_status(_(u"Loading items for {0}").format(i.name,))
i.get_items()
self.window.change_status(_(u"Ready"))
def in_post(self, buffer):
buffer = self.search(buffer)
buffer.get_items()
buffer = self.search("home_timeline")
buffer.get_items()
def update_all_buffers(self):
for i in self.buffers:
if hasattr(i, "get_items"):
i.get_items()
print "executed for %s" % (i.name)

118
src/controller/messages.py Normal file
View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
import widgetUtils
import output
from pubsub import pub
from wxUI.dialogs import message
from extra import SpellChecker, translator
class post(object):
def __init__(self, title, caption, text, post_type="post"):
super(post, self).__init__()
self.title = title
self.message = getattr(message, post_type)(title, caption, text)
self.message.set_title(title)
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
# self.text_processor()
self.image = None
# widgetUtils.connect_event(self.message.upload_image, widgetUtils.BUTTON_PRESSED, self.upload_image)
def get_privacy_options(self):
p = self.message.get("privacy")
if p == _(u"Friends of friends"):
privacy = 0
elif p == _(u"All users"):
privacy = 1
return privacy
def translate(self, *args, **kwargs):
dlg = translator.gui.translateDialog()
if dlg.get_response() == widgetUtils.OK:
text_to_translate = self.message.get_text().encode("utf-8")
source = [x[0] for x in translator.translator.available_languages()][dlg.get("source_lang")]
dest = [x[0] for x in translator.translator.available_languages()][dlg.get("dest_lang")]
msg = translator.translator.translate(text_to_translate, source, dest)
self.message.set_text(msg)
self.message.text_focus()
output.speak(_(u"Translated"))
else:
return
# def shorten(self, event=None):
# urls = utils.find_urls_in_text(self.message.get_text())
# if len(urls) == 0:
# output.speak(_(u"There's no URL to be shortened"))
# self.message.text_focus()
# elif len(urls) == 1:
# self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.shorten(urls[0])))
# output.speak(_(u"URL shortened"))
# self.message.text_focus()
# elif len(urls) > 1:
# list_urls = urlList.urlList()
# list_urls.populate_list(urls)
# if list_urls.get_response() == widgetUtils.OK:
# self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.shorten(list_urls.get_string())))
# output.speak(_(u"URL shortened"))
# self.message.text_focus()
# def unshorten(self, event=None):
# urls = utils.find_urls_in_text(self.message.get_text())
# if len(urls) == 0:
# output.speak(_(u"There's no URL to be expanded"))
# self.message.text_focus()
# elif len(urls) == 1:
# self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.unshorten(urls[0])))
# output.speak(_(u"URL expanded"))
# self.message.text_focus()
# elif len(urls) > 1:
# list_urls = urlList.urlList()
# list_urls.populate_list(urls)
# if list_urls.get_response() == widgetUtils.OK:
# self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.unshorten(list_urls.get_string())))
# output.speak(_(u"URL expanded"))
# self.message.text_focus()
# def text_processor(self, *args, **kwargs):
# self.message.set_title(_(u"%s - %s of 140 characters") % (self.title, len(self.message.get_text())))
# if len(self.message.get_text()) > 1:
# self.message.enable_button("shortenButton")
# self.message.enable_button("unshortenButton")
# else:
# self.message.disable_button("shortenButton")
# self.message.disable_button("unshortenButton")
# if len(self.message.get_text()) > 140:
# self.session.sound.play("max_length.ogg")
def spellcheck(self, event=None):
text = self.message.get_text()
checker = SpellChecker.spellchecker.spellChecker(text, "")
if hasattr(checker, "fixed_text"):
self.message.set_text(checker.fixed_text)
# def attach(self, *args, **kwargs):
# def completed_callback():
# url = dlg.uploaderFunction.get_url()
# pub.unsubscribe(dlg.uploaderDialog.update, "uploading")
# dlg.uploaderDialog.destroy()
# if url != 0:
# self.message.set_text(self.message.get_text()+url+" #audio")
# else:
# output.speak(_(u"Unable to upload the audio"))
# dlg.cleanup()
# dlg = audioUploader.audioUploader(self.session.settings, completed_callback)
def upload_image(self, *args, **kwargs):
if self.message.get("upload_image") == _(u"Discard image"):
del self.image
self.image = None
output.speak(_(u"Discarded"))
self.message.set("upload_image", _(u"Upload a picture"))
else:
self.image = self.message.get_image()
if self.image != None:
self.message.set("upload_image", _(u"Discard image"))
class comment(post):
def __init__(self, title, caption, text):
super(comment, self).__init__(title, caption, text, "comment")
self.message.set_title(_(u"New comment"))

189
src/controller/posts.py Normal file
View File

@ -0,0 +1,189 @@
# -*- coding: utf-8 -*-
import arrow
import messages
import languageHandler
import widgetUtils
import output
import wx
import webbrowser
from pubsub import pub
from wxUI.dialogs import postDialogs, urlList
from extra import SpellChecker, translator
from mysc.thread_utils import call_threaded
from wxUI import menus
from utils import find_urls
class postController(object):
def __init__(self, session, postObject):
super(postController, self).__init__()
self.session = session
self.post = postObject
self.dialog = postDialogs.post()
self.dialog.comments.list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.show_comment)
from_ = self.session.get_user_name(self.post["from_id"])
# if self.post.has_key("owner_id"):
# to_ = [i["name"] for i in self.post["to"]["data"]]
# title = _(u"Post from {0} in {1}").format(from_, "".join(to_))
# else:
title = _(u"Post from {0}").format(from_,)
self.dialog.set_title(title)
message = story = u""
if self.post.has_key("message"):
message = self.post["message"]
if self.post.has_key("story"):
story = self.post["story"]
if self.post.has_key("name") and self.post.has_key("link"):
message += u". {0}, {1}".format(self.post["name"], self.post["link"])
if story != "":
final_msg = u"{0} \n\n{1}".format(story, message)
else:
final_msg = message
self.dialog.set_post(final_msg)
# widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
# widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
# self.text_processor()
widgetUtils.connect_event(self.dialog.like, widgetUtils.BUTTON_PRESSED, self.post_like)
widgetUtils.connect_event(self.dialog.comment, widgetUtils.BUTTON_PRESSED, self.add_comment)
widgetUtils.connect_event(self.dialog.tools, widgetUtils.BUTTON_PRESSED, self.show_tools_menu)
self.dialog.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu, self.dialog.comments.list)
self.dialog.Bind(wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key, self.dialog.comments.list)
call_threaded(self.load_all_components)
def load_all_components(self):
self.get_likes()
self.get_shares()
self.get_comments()
def post_like(self, *args, **kwargs):
lk = self.session.like(self.post["id"])
self.get_likes()
def get_likes(self):
self.likes = self.session.fb.client.get_connections(id=self.post["id"], connection_name="likes", summary=True)
self.dialog.set_likes(self.likes["summary"]["total_count"])
def get_shares(self):
self.shares = self.session.fb.client.get_connections(id=self.post["id"], connection_name="sharedposts")
self.dialog.set_shares(str(len(self.shares["data"])))
def get_comments(self):
self.comments = self.session.fb.client.get_connections(id=self.post["id"], connection_name="comments", filter="stream")
comments = []
for i in self.comments["data"]:
from_ = i["from"]["name"]
if len(i["message"]) > 100:
comment = i["message"][:100]
else:
comment = i["message"]
original_date = arrow.get(i["created_time"], "YYYY-MM-DTHH:m:sZ", locale="en")
created_at = original_date.humanize(locale=languageHandler.getLanguage())
likes = str(i["like_count"])
comments.append([from_, comment, created_at, likes,])
self.dialog.insert_comments(comments)
def add_comment(self, *args, **kwargs):
comment = messages.comment(title=_(u"Add a comment"), caption="", text="")
if comment.message.get_response() == widgetUtils.OK:
msg = comment.message.get_text().encode("utf-8")
try:
self.session.fb.client.put_comment(self.post["id"], msg)
output.speak(_(u"You've posted a comment"))
if len(self.comments["data"]) < 25:
self.clear_comments_list()
self.get_comments()
except Exception as msg:
print msg
def clear_comments_list(self):
self.dialog.comments.clear()
def show_comment(self, *args, **kwargs):
c = comment(self.session, self.comments["data"][self.dialog.comments.get_selected()])
c.dialog.get_response()
def show_menu(self, *args, **kwargs):
if self.dialog.comments.get_count() == 0: return
menu = menus.commentMenu()
widgetUtils.connect_event(self.dialog, widgetUtils.MENU, self.show_comment, menuitem=menu.open)
widgetUtils.connect_event(self.dialog, widgetUtils.MENU, self.comment_like, menuitem=menu.like)
widgetUtils.connect_event(self.dialog, widgetUtils.MENU, self.comment_unlike, menuitem=menu.unlike)
self.dialog.PopupMenu(menu, self.dialog.comments.list.GetPosition())
def show_menu_by_key(self, ev):
if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU:
self.show_menu()
def show_tools_menu(self, *args, **kwargs):
menu = menus.toolsMenu()
widgetUtils.connect_event(self.dialog, widgetUtils.MENU, self.open_url, menuitem=menu.url)
widgetUtils.connect_event(self.dialog, widgetUtils.MENU, self.translate, menuitem=menu.translate)
widgetUtils.connect_event(self.dialog, widgetUtils.MENU, self.spellcheck, menuitem=menu.CheckSpelling)
self.dialog.PopupMenu(menu, self.dialog.tools.GetPosition())
def comment_like(self, *args, **kwargs):
comment_id = self.comments["data"][self.dialog.comments.get_selected()]["id"]
self.session.like(comment_id)
output.speak(_(u"You do like this comment"))
def comment_unlike(self, *args, **kwargs):
comment_id = self.comments["data"][self.dialog.comments.get_selected()]["id"]
self.session.unlike(comment_id)
output.speak(_(u"You don't like this comment"))
def translate(self, *args, **kwargs):
dlg = translator.gui.translateDialog()
if dlg.get_response() == widgetUtils.OK:
text_to_translate = self.dialog.post_view.GetValue().encode("utf-8")
source = [x[0] for x in translator.translator.available_languages()][dlg.get("source_lang")]
dest = [x[0] for x in translator.translator.available_languages()][dlg.get("dest_lang")]
msg = translator.translator.translate(text_to_translate, source, dest)
self.dialog.post_view.ChangeValue(msg)
output.speak(_(u"Translated"))
else:
return
def spellcheck(self, *args, **kwargs):
text = self.dialog.post_view.GetValue()
checker = SpellChecker.spellchecker.spellChecker(text, "")
if hasattr(checker, "fixed_text"):
self.dialog.post_view.ChangeValue(checker.fixed_text)
def open_url(self, *args, **kwargs):
text = self.dialog.post_view.GetValue()
urls = find_urls(text)
url = None
if len(urls) == 0: return
if len(urls) == 1:
url = urls[0]
elif len(urls) > 1:
url_list = urlList.urlList()
url_list.populate_list(urls)
if url_list.get_response() == widgetUtils.OK:
url = urls[url_list.get_item()]
if url != None:
output.speak(_(u"Opening URL..."), True)
webbrowser.open_new_tab(url)
class comment(object):
def __init__(self, session, comment_object):
super(comment, self).__init__()
self.session = session
self.comment = comment_object
self.dialog = postDialogs.comment()
from_ = self.comment["from"]["name"]
message = self.comment["message"]
original_date = arrow.get(self.comment["created_time"], "YYYY-MM-DTHH:m:sZ", locale="en")
created_at = original_date.humanize(locale=languageHandler.getLanguage())
self.dialog.set_post(message)
self.dialog.set_title(_(u"Comment from {0}").format(from_,))
widgetUtils.connect_event(self.dialog.like, widgetUtils.BUTTON_PRESSED, self.post_like)
call_threaded(self.get_likes)
def get_likes(self):
self.likes = self.session.fb.client.get_connections(id=self.comment["id"], connection_name="likes", summary=True)
self.dialog.set_likes(self.likes["summary"]["total_count"])
def post_like(self, *args, **kwargs):
lk = self.session.like(self.comment["id"])
self.get_likes()

View File

View File

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import widgetUtils
import wx_ui
import wx_transfer_dialogs
import transfer
import output
import tempfile
import sound
import os
import config
from pubsub import pub
from mysc.thread_utils import call_threaded
import sound_lib
import logging
log = logging.getLogger("extra.AudioUploader.audioUploader")
class audioUploader(object):
def __init__(self, configFile, completed_callback):
self.config = configFile
super(audioUploader, self).__init__()
self.dialog = wx_ui.audioDialog(services=self.get_available_services())
self.file = None
self.recorded = False
self.recording = None
self.playing = None
widgetUtils.connect_event(self.dialog.play, widgetUtils.BUTTON_PRESSED, self.on_play)
widgetUtils.connect_event(self.dialog.pause, widgetUtils.BUTTON_PRESSED, self.on_pause)
widgetUtils.connect_event(self.dialog.record, widgetUtils.BUTTON_PRESSED, self.on_record)
widgetUtils.connect_event(self.dialog.attach_exists, widgetUtils.BUTTON_PRESSED, self.on_attach_exists)
widgetUtils.connect_event(self.dialog.discard, widgetUtils.BUTTON_PRESSED, self.on_discard)
if self.dialog.get_response() == widgetUtils.OK:
self.postprocess()
log.debug("Uploading file %s to %s..." % (self.file, self.dialog.get("services")))
self.uploaderDialog = wx_transfer_dialogs.UploadDialog(self.file)
output.speak(_(u"Attaching..."))
if self.dialog.get("services") == "SNDUp":
base_url = "http://sndup.net/post.php"
if len(self.config["sound"]["sndup_api_key"]) > 0:
url = base_url + '?apikey=' + self.config['sound']['sndup_api_key']
else:
url = base_url
self.uploaderFunction = transfer.Upload(field='file', url=url, filename=self.file, completed_callback=completed_callback)
elif self.dialog.get("services") == "TwUp":
url = "http://api.twup.me/post.json"
self.uploaderFunction = transfer.Upload(field='file', url=url, filename=self.file, completed_callback=completed_callback)
pub.subscribe(self.uploaderDialog.update, "uploading")
self.uploaderDialog.get_response()
self.uploaderFunction.perform_threaded()
def get_available_services(self):
services = []
services.append("TwUp")
services.append("SNDUp")
return services
def on_pause(self, *args, **kwargs):
if self.dialog.get("pause") == _(u"Pause"):
self.recording.pause()
self.dialog.set("pause", _(u"&Resume"))
elif self.dialog.get("pause") == _(u"Resume"):
self.recording.play()
self.dialog.set("pause", _(U"&Pause"))
def on_record(self, *args, **kwargs):
if self.recording != None:
self.stop_recording()
self.dialog.disable_control("pause")
else:
self.start_recording()
self.dialog.enable_control("pause")
def start_recording(self):
self.dialog.disable_control("attach_exists")
self.file = tempfile.mktemp(suffix='.wav')
self.recording = sound.recording(self.file)
self.recording.play()
self.dialog.set("record", _(u"&Stop"))
output.speak(_(u"Recording"))
def stop_recording(self):
self.recording.stop()
self.recording.free()
output.speak(_(u"Stopped"))
self.recorded = True
self.dialog.set("record", _(u"&Record"))
self.file_attached()
def file_attached(self):
self.dialog.set("pause", _(u"&Pause"))
self.dialog.disable_control("record")
self.dialog.enable_control("play")
self.dialog.enable_control("discard")
self.dialog.disable_control("attach_exists")
self.dialog.enable_control("attach")
self.dialog.play.SetFocus()
def on_discard(self, *args, **kwargs):
if self.playing:
self._stop()
if self.recording != None:
self.dialog.disable_control("attach")
self.dialog.disable_control("play")
self.file = None
self.dialog.enable_control("record")
self.dialog.enable_control("attach_exists")
self.dialog.record.SetFocus()
self.dialog.disable_control("discard")
self.recording = None
output.speak(_(u"Discarded"))
def on_play(self, *args, **kwargs):
if not self.playing:
call_threaded(self._play)
else:
self._stop()
def _play(self):
output.speak(_(u"Playing..."))
# try:
self.playing = sound_lib.stream.FileStream(file=unicode(self.file), flags=sound_lib.stream.BASS_UNICODE)
self.playing.play()
self.dialog.set("play", _(u"&Stop"))
try:
while self.playing.is_playing:
pass
self.dialog.set("play", _(u"&Play"))
self.playing.free()
self.playing = None
except:
pass
def _stop(self):
output.speak(_(u"Stopped"))
self.playing.stop()
self.playing.free()
self.dialog.set("play", _(u"&Play"))
self.playing = None
def postprocess(self):
if self.file.lower().endswith('.wav'):
output.speak(_(u"Recoding audio..."))
sound.recode_audio(self.file)
self.wav_file = self.file
self.file = '%s.ogg' % self.file[:-4]
def cleanup(self):
if self.playing and self.playing.is_playing:
self.playing.stop()
if self.recording != None:
if self.recording.is_playing:
self.recording.stop()
try:
self.recording.free()
except:
pass
os.remove(self.file)
if hasattr(self, 'wav_file'):
os.remove(self.wav_file)
del(self.wav_file)
if hasattr(self, 'wav_file') and os.path.exists(self.file):
os.remove(self.file)
def on_attach_exists(self, *args, **kwargs):
self.file = self.dialog.get_file()
if self.file != False:
self.file_attached()

View File

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

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
def convert_bytes(n):
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
if n >= P:
return '%.2fPb' % (float(n) / T)
elif n >= T:
return '%.2fTb' % (float(n) / T)
elif n >= G:
return '%.2fGb' % (float(n) / G)
elif n >= M:
return '%.2fMb' % (float(n) / M)
elif n >= K:
return '%.2fKb' % (float(n) / K)
else:
return '%d' % n
def seconds_to_string(seconds, precision=0):
day = seconds // 86400
hour = seconds // 3600
min = (seconds // 60) % 60
sec = seconds - (hour * 3600) - (min * 60)
sec_spec = "." + str(precision) + "f"
sec_string = sec.__format__(sec_spec)
string = ""
if day == 1:
string += _(u"%d day, ") % day
elif day >= 2:
string += _(u"%d days, ") % day
if (hour == 1):
string += _(u"%d hour, ") % hour
elif (hour >= 2):
string += _("%d hours, ") % hour
if (min == 1):
string += _(u"%d minute, ") % min
elif (min >= 2):
string += _(u"%d minutes, ") % min
if sec >= 0 and sec <= 2:
string += _(u"%s second") % sec_string
else:
string += _(u"%s seconds") % sec_string
return string

View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
import wx
from utils import *
import widgetUtils
class TransferDialog(widgetUtils.BaseDialog):
def __init__(self, filename, *args, **kwargs):
super(TransferDialog, self).__init__(parent=None, id=wx.NewId(), *args, **kwargs)
self.pane = wx.Panel(self)
self.progress_bar = wx.Gauge(parent=self.pane)
fileBox = wx.BoxSizer(wx.HORIZONTAL)
fileLabel = wx.StaticText(self.pane, -1, _(u"File"))
self.file = wx.TextCtrl(self.pane, -1, value=filename, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(200, 100))
self.file.SetFocus()
fileBox.Add(fileLabel)
fileBox.Add(self.file)
currentAmountBox = wx.BoxSizer(wx.HORIZONTAL)
current_amount_label = wx.StaticText(self.pane, -1, _(u"Transferred"))
self.current_amount = wx.TextCtrl(self.pane, -1, value='0', style=wx.TE_READONLY|wx.TE_MULTILINE)
currentAmountBox.Add(current_amount_label)
currentAmountBox.Add(self.current_amount)
totalSizeBox = wx.BoxSizer(wx.HORIZONTAL)
total_size_label = wx.StaticText(self.pane, -1, _(u"Total file size"))
self.total_size = wx.TextCtrl(self.pane, -1, value='0', style=wx.TE_READONLY|wx.TE_MULTILINE)
totalSizeBox.Add(total_size_label)
totalSizeBox.Add(self.total_size)
speedBox = wx.BoxSizer(wx.HORIZONTAL)
speedLabel = wx.StaticText(self.pane, -1, _(u"Transfer rate"))
self.speed = wx.TextCtrl(self.pane, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, value="0 Kb/s")
speedBox.Add(speedLabel)
speedBox.Add(self.speed)
etaBox = wx.BoxSizer(wx.HORIZONTAL)
etaLabel = wx.StaticText(self.pane, -1, _(u"Time left"))
self.eta = wx.TextCtrl(self.pane, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, value="Unknown", size=(200, 100))
etaBox.Add(etaLabel)
etaBox.Add(self.eta)
self.create_buttons()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(fileBox)
sizer.Add(currentAmountBox)
sizer.Add(totalSizeBox)
sizer.Add(speedBox)
sizer.Add(etaBox)
sizer.Add(self.progress_bar)
self.pane.SetSizerAndFit(sizer)
def update(self, data):
wx.CallAfter(self.progress_bar.SetValue, data["percent"])
wx.CallAfter(self.current_amount.SetValue, '%s (%d%%)' % (convert_bytes(data["current"]), data["percent"]))
wx.CallAfter(self.total_size.SetValue, convert_bytes(data["total"]))
wx.CallAfter(self.speed.SetValue, data["speed"])
if data["eta"]:
wx.CallAfter(self.eta.SetValue, seconds_to_string(data["eta"]))
def create_buttons(self):
self.cancel_button = wx.Button(parent=self.pane, id=wx.ID_CANCEL)
def get_response(self):
self.Show()
def destroy(self):
self.Destroy()
class UploadDialog(TransferDialog):
def __init__(self, filename=None, *args, **kwargs):
super(UploadDialog, self).__init__(filename=filename, *args, **kwargs)
class DownloadDialog(TransferDialog):
def __init__(self, *args, **kwargs):
super(Download, self).__init__(*args, **kwargs)

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import wx
import widgetUtils
import output
import logging
log = logging.getLogger("extra.AudioUploader.wx_UI")
class audioDialog(widgetUtils.BaseDialog):
def __init__(self, services):
log.debug("creating audio dialog.")
super(audioDialog, self).__init__(None, -1, _(u"Attach audio"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer2 = wx.BoxSizer(wx.HORIZONTAL)
self.play = wx.Button(panel, -1, _(u"&Play"))
self.play.Disable()
self.pause = wx.Button(panel, -1, _(u"&Pause"))
self.pause.Disable()
self.record = wx.Button(panel, -1, _(u"&Record"))
self.record.SetFocus()
self.attach_exists = wx.Button(panel, -1, _(u"&Add an existing file"))
self.discard = wx.Button(panel, -1, _(u"&Discard"))
self.discard.Disable()
label = wx.StaticText(panel, -1, _(u"Upload to"))
self.services = wx.ComboBox(panel, -1, choices=services, value=services[0], style=wx.CB_READONLY)
servicesBox = wx.BoxSizer(wx.HORIZONTAL)
servicesBox.Add(label, 0, wx.ALL, 5)
servicesBox.Add(self.services, 0, wx.ALL, 5)
self.attach = wx.Button(panel, wx.ID_OK, _(u"Attach"))
self.attach.Disable()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"&Cancel"))
btnSizer.Add(self.play, 0, wx.ALL, 5)
btnSizer.Add(self.pause, 0, wx.ALL, 5)
btnSizer.Add(self.record, 0, wx.ALL, 5)
btnSizer2.Add(self.attach_exists, 0, wx.ALL, 5)
btnSizer2.Add(self.discard, 0, wx.ALL, 5)
btnSizer2.Add(self.attach, 0, wx.ALL, 5)
btnSizer2.Add(cancel, 0, wx.ALL, 5)
sizer.Add(servicesBox, 0, wx.ALL, 5)
sizer.Add(btnSizer, 0, wx.ALL, 5)
sizer.Add(btnSizer2, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def enable_control(self, control):
log.debug("Enabling control %s" % (control,))
if hasattr(self, control):
getattr(self, control).Enable()
def disable_control(self, control):
log.debug("Disabling control %s" % (control,))
if hasattr(self, control):
getattr(self, control).Disable()
def get_file(self):
openFileDialog = wx.FileDialog(self, _(u"Select the audio file to be uploaded"), "", "", _("Audio Files (*.mp3, *.ogg, *.wav)|*.mp3; *.ogg; *.wav"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return False
return openFileDialog.GetPath()

View File

@ -0,0 +1 @@
from soundsTutorial import soundsTutorial

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
import widgetUtils
class soundsTutorialDialog(Gtk.Dialog):
def __init__(self, actions):
super(soundsTutorialDialog, self).__init__("Sounds tutorial", None, 0, (Gtk.STOCK_CANCEL, widgetUtils.CANCEL))
box = self.get_content_area()
label = Gtk.Label("Press enter for listen the sound")
self.list = widgetUtils.list("Action")
self.populate_actions(actions)
lBox = Gtk.Box(spacing=6)
lBox.add(label)
lBox.add(self.list.list)
box.add(lBox)
self.play = Gtk.Button("Play")
box.add(self.play)
self.show_all()
def populate_actions(self, actions):
for i in actions:
self.list.insert_item(i)
def get_selected(self):
return self.list.get_selected()

View File

@ -0,0 +1,11 @@
#Reverse sort, by Bill Dengler <codeofdusk@gmail.com> for use in TWBlue http://twblue.es
def invert_tuples(t):
"Invert a list of tuples, so that the 0th element becomes the -1th, and the -1th becomes the 0th."
res=[]
for i in t:
res.append(i[::-1])
return res
def reverse_sort(t):
"Sorts a list of tuples/lists by their last elements, not their first."
return invert_tuples(sorted(invert_tuples(t)))

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
import platform
import widgetUtils
import os
import paths
import logging
log = logging.getLogger("extra.SoundsTutorial.soundsTutorial")
import soundsTutorial_constants
if platform.system() == "Windows":
import wx_ui as UI
elif platform.system() == "Linux":
import gtk_ui as UI
class soundsTutorial(object):
def __init__(self, sessionObject):
log.debug("Creating sounds tutorial object...")
super(soundsTutorial, self).__init__()
self.session = sessionObject
self.actions = []
log.debug("Loading actions for sounds tutorial...")
[self.actions.append(i[1]) for i in soundsTutorial_constants.actions]
self.files = []
log.debug("Searching sound files...")
[self.files.append(i[0]) for i in soundsTutorial_constants.actions]
log.debug("Creating dialog...")
self.dialog = UI.soundsTutorialDialog(self.actions)
widgetUtils.connect_event(self.dialog.play, widgetUtils.BUTTON_PRESSED, self.on_play)
self.dialog.get_response()
def on_play(self, *args, **kwargs):
try:
self.session.sound.play(self.files[self.dialog.get_selection()]+".ogg")
except:
log.exception("Error playing the %s sound" % (self.files[self.dialog.items.GetSelection()],))

View File

@ -0,0 +1,27 @@
#-*- coding: utf-8 -*-
import reverse_sort
import application
actions = reverse_sort.reverse_sort([ ("audio", _(u"Audio tweet.")),
("create_timeline", _(u"User timeline buffer created.")),
("delete_timeline", _(u"Buffer destroied.")),
("dm_received", _(u"Direct message received.")),
("dm_sent", _(u"Direct message sent.")),
("error", _(u"Error.")),
("favourite", _(u"Tweet liked.")),
("favourites_timeline_updated", _(u"Likes buffer updated.")),
("geo", _(u"Geotweet.")),
("limit", _(u"Boundary reached.")),
("list_tweet", _(u"List updated.")),
("max_length", _(u"Too many characters.")),
("mention_received", _(u"Mention received.")),
("new_event", _(u"New event.")),
("ready", _(u"{0} is ready.").format(application.name,)),
("reply_send", _(u"Mention sent.")),
("retweet_send", _(u"Tweet retweeted.")),
("search_updated", _(u"Search buffer updated.")),
("tweet_received", _(u"Tweet received.")),
("tweet_send", _(u"Tweet sent.")),
("trends_updated", _(u"Trending topics buffer updated.")),
("tweet_timeline", _(u"New tweet in user timeline buffer.")),
("update_followers", _(u"New follower.")),
("volume_changed", _(u"Volume changed."))])

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
class soundsTutorialDialog(widgetUtils.BaseDialog):
def __init__(self, actions):
super(soundsTutorialDialog, self).__init__(None, -1)
self.SetTitle(_(u"Sounds tutorial"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(panel, -1, _(u"Press enter to listen to the sound for the selected event"))
self.items = wx.ListBox(panel, 1, choices=actions, style=wx.LB_SINGLE)
self.items.SetSelection(0)
listBox = wx.BoxSizer(wx.HORIZONTAL)
listBox.Add(label)
listBox.Add(self.items)
self.play = wx.Button(panel, 1, (u"Play"))
self.play.SetDefault()
close = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.play)
btnBox.Add(close)
sizer.Add(listBox)
sizer.Add(btnBox)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def get_selection(self):
return self.items.GetSelection()

View File

@ -0,0 +1,4 @@
import spellchecker
import platform
if platform.system() == "Windows":
from wx_ui import *

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
import logging
log = logging.getLogger("extra.SpellChecker.spellChecker")
import wx_ui
import widgetUtils
import output
import config
import languageHandler
from enchant.checker import SpellChecker
from enchant.errors import DictNotFoundError
from enchant import tokenize
import twitterFilter
class spellChecker(object):
def __init__(self, text, dictionary):
super(spellChecker, self).__init__()
log.debug("Creating the SpellChecker object. Dictionary: %s" % (dictionary,))
self.active = True
try:
if config.app["app-settings"]["language"] == "system":
log.debug("Using the system language")
self.checker = SpellChecker(filters=[twitterFilter.TwitterFilter, tokenize.EmailFilter, tokenize.URLFilter])
else:
log.debug("Using language: %s" % (languageHandler.getLanguage(),))
self.checker = SpellChecker(languageHandler.getLanguage(), filters=[twitterFilter.TwitterFilter, tokenize.EmailFilter, tokenize.URLFilter])
self.checker.set_text(text)
except DictNotFoundError:
log.exception("Dictionary for language %s not found." % (dictionary,))
wx_ui.dict_not_found_error()
self.active = False
if self.active == True:
log.debug("Creating dialog...")
self.dialog = wx_ui.spellCheckerDialog()
widgetUtils.connect_event(self.dialog.ignore, widgetUtils.BUTTON_PRESSED, self.ignore)
widgetUtils.connect_event(self.dialog.ignoreAll, widgetUtils.BUTTON_PRESSED, self.ignoreAll)
widgetUtils.connect_event(self.dialog.replace, widgetUtils.BUTTON_PRESSED, self.replace)
widgetUtils.connect_event(self.dialog.replaceAll, widgetUtils.BUTTON_PRESSED, self.replaceAll)
self.check()
self.dialog.get_response()
self.fixed_text = self.checker.get_text()
def check(self):
try:
self.checker.next()
textToSay = _(u"Misspelled word: %s") % (self.checker.word,)
context = u"... %s %s %s" % (self.checker.leading_context(10), self.checker.word, self.checker.trailing_context(10))
self.dialog.set_title(textToSay)
output.speak(textToSay)
self.dialog.set_word_and_suggestions(word=self.checker.word, context=context, suggestions=self.checker.suggest())
except StopIteration:
log.debug("Process finished.")
wx_ui.finished()
self.dialog.Destroy()
# except AttributeError:
# pass
def ignore(self, ev):
self.check()
def ignoreAll(self, ev):
self.checker.ignore_always(word=self.checker.word)
self.check()
def replace(self, ev):
self.checker.replace(self.dialog.get_selected_suggestion())
self.check()
def replaceAll(self, ev):
self.checker.replace_always(self.dialog.get_selected_suggestion())
self.check()

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
import re
from enchant.tokenize import Filter
class TwitterFilter(Filter):
"""Filter skipping over twitter usernames and hashtags.
This filter skips any words matching the following regular expression:
^[#@](\S){1, }$
That is, any words that resemble users and hashtags.
"""
_pattern = re.compile(r"^[#@](\S){1,}$")
def _skip(self,word):
if self._pattern.match(word):
return True
return False

View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import wx
import application
class spellCheckerDialog(wx.Dialog):
def __init__(self):
super(spellCheckerDialog, self).__init__(None, 1)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
word = wx.StaticText(panel, -1, _(u"Misspelled word"))
self.word = wx.TextCtrl(panel, -1)
wordBox = wx.BoxSizer(wx.HORIZONTAL)
wordBox.Add(word, 0, wx.ALL, 5)
wordBox.Add(self.word, 0, wx.ALL, 5)
context = wx.StaticText(panel, -1, _(u"Context"))
self.context = wx.TextCtrl(panel, -1)
contextBox = wx.BoxSizer(wx.HORIZONTAL)
contextBox.Add(context, 0, wx.ALL, 5)
contextBox.Add(self.context, 0, wx.ALL, 5)
suggest = wx.StaticText(panel, -1, _(u"Suggestions"))
self.suggestions = wx.ListBox(panel, -1, choices=[], style=wx.LB_SINGLE)
suggestionsBox = wx.BoxSizer(wx.HORIZONTAL)
suggestionsBox.Add(suggest, 0, wx.ALL, 5)
suggestionsBox.Add(self.suggestions, 0, wx.ALL, 5)
self.ignore = wx.Button(panel, -1, _(u"Ignore"))
self.ignoreAll = wx.Button(panel, -1, _(u"Ignore all"))
self.replace = wx.Button(panel, -1, _(u"Replace"))
self.replaceAll = wx.Button(panel, -1, _(u"Replace all"))
close = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ignore, 0, wx.ALL, 5)
btnBox.Add(self.ignoreAll, 0, wx.ALL, 5)
btnBox.Add(self.replace, 0, wx.ALL, 5)
btnBox.Add(self.replaceAll, 0, wx.ALL, 5)
btnBox.Add(close, 0, wx.ALL, 5)
sizer.Add(wordBox, 0, wx.ALL, 5)
sizer.Add(contextBox, 0, wx.ALL, 5)
sizer.Add(suggestionsBox, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def get_response(self):
return self.ShowModal()
def set_title(self, title):
return self.SetTitle(title)
def set_word_and_suggestions(self, word, context, suggestions):
self.word.SetValue(word)
self.context.ChangeValue(context)
self.suggestions.Set(suggestions)
self.suggestions.SetFocus()
def get_selected_suggestion(self):
return self.suggestions.GetStringSelection()
def dict_not_found_error():
wx.MessageDialog(None, _(u"An error has occurred. There are no dictionaries available for the selected language in {0}").format(application.name,), _(u"Error"), wx.ICON_ERROR).ShowModal()
def finished():
wx.MessageDialog(None, _(u"Spell check complete."), application.name, style=wx.OK).ShowModal()

0
src/extra/__init__.py Normal file
View File

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import completion, settings

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
import output
import storage
import wx_menu
class autocompletionUsers(object):
def __init__(self, window, session_id):
super(autocompletionUsers, self).__init__()
self.window = window
self.db = storage.storage(session_id)
def show_menu(self, mode="tweet"):
position = self.window.get_position()
if mode == "tweet":
text = self.window.get_text()
text = text[:position]
try:
pattern = text.split()[-1]
except IndexError:
output.speak(_(u"You have to start writing"))
return
if pattern.startswith("@") == True:
menu = wx_menu.menu(self.window.text, pattern[1:], mode=mode)
users = self.db.get_users(pattern[1:])
if len(users) > 0:
menu.append_options(users)
self.window.popup_menu(menu)
menu.destroy()
else:
output.speak(_(u"There are no results in your users database"))
else:
output.speak(_(u"Autocompletion only works for users."))
elif mode == "dm":
text = self.window.get_user()
try:
pattern = text.split()[-1]
except IndexError:
output.speak(_(u"You have to start writing"))
return
menu = wx_menu.menu(self.window.cb, pattern, mode=mode)
users = self.db.get_users(pattern)
if len(users) > 0:
menu.append_options(users)
self.window.popup_menu(menu)
menu.destroy()
else:
output.speak(_(u"There are no results in your users database"))

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
import storage
import widgetUtils
import wx_manage
from wxUI import commonMessageDialogs
class autocompletionManage(object):
def __init__(self, session):
super(autocompletionManage, self).__init__()
self.session = session
self.dialog = wx_manage.autocompletionManageDialog()
self.database = storage.storage(self.session.session_id)
self.users = self.database.get_all_users()
self.dialog.put_users(self.users)
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add_user)
widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_user)
self.dialog.get_response()
def update_list(self):
item = self.dialog.users.get_selected()
self.dialog.users.clear()
self.users = self.database.get_all_users()
self.dialog.put_users(self.users)
self.dialog.users.select_item(item)
def add_user(self, *args, **kwargs):
usr = self.dialog.get_user()
if usr == False:
return
try:
data = self.session.twitter.twitter.show_user(screen_name=usr)
except:
self.dialog.show_invalid_user_error()
return
self.database.set_user(data["screen_name"], data["name"], 0)
self.update_list()
def remove_user(self, ev):
if commonMessageDialogs.delete_user_from_db() == widgetUtils.YES:
item = self.dialog.users.get_selected()
user = self.users[item]
self.database.remove_user(user[0])
self.update_list()

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
import storage
import widgetUtils
import wx_settings
import manage
import output
from mysc.thread_utils import call_threaded
class autocompletionSettings(object):
def __init__(self, config, buffer, window):
super(autocompletionSettings, self).__init__()
self.config = config
self.buffer = buffer
self.window = window
self.dialog = wx_settings.autocompletionSettingsDialog()
self.dialog.set("friends_buffer", self.config["mysc"]["save_friends_in_autocompletion_db"])
self.dialog.set("followers_buffer", self.config["mysc"]["save_followers_in_autocompletion_db"])
widgetUtils.connect_event(self.dialog.viewList, widgetUtils.BUTTON_PRESSED, self.view_list)
if self.dialog.get_response() == widgetUtils.OK:
call_threaded(self.add_users_to_database)
def add_users_to_database(self):
self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer")
self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer")
output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes."))
database = storage.storage(self.buffer.session.session_id)
if self.dialog.get("followers_buffer") == True:
buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]["items"]:
database.set_user(i["screen_name"], i["name"], 1)
else:
database.remove_by_buffer(1)
if self.dialog.get("friends_buffer") == True:
buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]["items"]:
database.set_user(i["screen_name"], i["name"], 2)
else:
database.remove_by_buffer(2)
wx_settings.show_success_dialog()
self.dialog.destroy()
def view_list(self, ev):
q = manage.autocompletionManage(self.buffer.session)
def execute_at_startup(window, buffer, config):
database = storage.storage(buffer.session.session_id)
if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True:
buffer = window.search_buffer("followers", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 1)
else:
database.remove_by_buffer(1)
if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True:
buffer = window.search_buffer("friends", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 2)
else:
database.remove_by_buffer(2)

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import sqlite3, paths
class storage(object):
def __init__(self, session_id):
self.connection = sqlite3.connect(paths.config_path("%s/autocompletionUsers.dat" % (session_id)))
self.cursor = self.connection.cursor()
if self.table_exist("users") == False:
self.create_table()
def table_exist(self, table):
ask = self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='%s'" % (table))
answer = ask.fetchone()
if answer == None:
return False
else:
return True
def get_all_users(self):
self.cursor.execute("""select * from users""")
return self.cursor.fetchall()
def get_users(self, term):
self.cursor.execute("""SELECT * FROM users WHERE user LIKE ?""", ('{}%'.format(term),))
return self.cursor.fetchall()
def set_user(self, screen_name, user_name, from_a_buffer):
self.cursor.execute("""insert or ignore into users values(?, ?, ?)""", (screen_name, user_name, from_a_buffer))
self.connection.commit()
def remove_user(self, user):
self.cursor.execute("""DELETE FROM users WHERE user = ?""", (user,))
self.connection.commit()
return self.cursor.fetchone()
def remove_by_buffer(self, bufferType):
""" Removes all users saved on a buffer. BufferType is 0 for no buffer, 1 for friends and 2 for followers"""
self.cursor.execute("""DELETE FROM users WHERE from_a_buffer = ?""", (bufferType,))
self.connection.commit()
return self.cursor.fetchone()
def create_table(self):
self.cursor.execute("""
create table users(
user TEXT UNIQUE,
name TEXT,
from_a_buffer INTEGER
)""")
def __del__(self):
self.cursor.close()
self.connection.close()

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
from multiplatform_widgets import widgets
import application
class autocompletionManageDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionManageDialog, self).__init__(parent=None, id=-1, title=_(u"Manage Autocompletion database"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(panel, -1, _(u"Editing {0} users database").format(application.name,))
self.users = widgets.list(panel, _(u"Username"), _(u"Name"), style=wx.LC_REPORT)
sizer.Add(label, 0, wx.ALL, 5)
sizer.Add(self.users.list, 0, wx.ALL, 5)
self.add = wx.Button(panel, -1, _(u"Add user"))
self.remove = wx.Button(panel, -1, _(u"Remove user"))
optionsBox = wx.BoxSizer(wx.HORIZONTAL)
optionsBox.Add(self.add, 0, wx.ALL, 5)
optionsBox.Add(self.remove, 0, wx.ALL, 5)
sizer.Add(optionsBox, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def put_users(self, users):
for i in users:
j = [i[0], i[1]]
self.users.insert_item(False, *j)
def get_user(self):
usr = False
userDlg = wx.TextEntryDialog(None, _(u"Twitter username"), _(u"Add user to database"))
if userDlg.ShowModal() == wx.ID_OK:
usr = userDlg.GetValue()
return usr
def show_invalid_user_error(self):
wx.MessageDialog(None, _(u"The user does not exist"), _(u"Error!"), wx.ICON_ERROR).ShowModal()

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import wx
class menu(wx.Menu):
def __init__(self, window, pattern, mode):
super(menu, self).__init__()
self.window = window
self.pattern = pattern
self.mode = mode
def append_options(self, options):
for i in options:
item = wx.MenuItem(self, wx.NewId(), "%s (@%s)" % (i[1], i[0]))
self.AppendItem(item)
self.Bind(wx.EVT_MENU, lambda evt, temp=i[0]: self.select_text(evt, temp), item)
def select_text(self, ev, text):
if self.mode == "tweet":
self.window.ChangeValue(self.window.GetValue().replace("@"+self.pattern, "@"+text+" "))
elif self.mode == "dm":
self.window.SetValue(self.window.GetValue().replace(self.pattern, text))
self.window.SetInsertionPointEnd()
def destroy(self):
self.Destroy()

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
import application
class autocompletionSettingsDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionSettingsDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.followers_buffer = wx.CheckBox(panel, -1, _(u"Add users from followers buffer"))
self.friends_buffer = wx.CheckBox(panel, -1, _(u"Add users from friends buffer"))
sizer.Add(self.followers_buffer, 0, wx.ALL, 5)
sizer.Add(self.friends_buffer, 0, wx.ALL, 5)
self.viewList = wx.Button(panel, -1, _(u"Manage database..."))
sizer.Add(self.viewList, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def show_success_dialog():
wx.MessageDialog(None, _(u"{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK).ShowModal()

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
import translator
import platform
if platform.system() == "Windows":
import wx_ui as gui

View File

@ -0,0 +1,251 @@
# encoding: utf-8
#
# Copyright (C) 2013 Mesar Hameed <mhameed@src.gnome.org>
# This file is covered by the GNU General Public License.
import os
import re
import sys
import threading
from time import sleep
from random import randint
import logging
log = logging.getLogger("translator")
import urllib2
# Each group has to be a class of possible breaking points for the writing script.
# Usually this is the major syntax marks, such as:
# full stop, comma, exclaim, question, etc.
arabicBreaks = u'[،؛؟]'
# Thanks to Talori in the NVDA irc room:
# U+3000 to U+303F, U+FE10 to U+FE1F, U+FE30 to U+FE6F, U+FF01 to U+FF60
chineseBreaks = u'[ -〿︐-︟︰-﹯!-⦆]'
latinBreaks = r'[.,!?;:\n]'
splitReg = re.compile(u"{arabic}|{chinese}|{latin}".format(arabic=arabicBreaks, chinese=chineseBreaks, latin=latinBreaks))
def translate(text, source="auto", target="en"):
if source == "": source = "auto"
t = Translator(lang_from=source, lang_to=target, text=text)
t.start()
while t.isAlive():
sleep(0.1)
t.join()
return t.translation
def splitChunks(text, chunksize):
pos = 0
potentialPos = 0
for splitMark in splitReg.finditer(text):
if (splitMark.start() - pos +1) < chunksize:
potentialPos = splitMark.start()
continue
else:
yield text[pos:potentialPos+1]
pos = potentialPos + 1
potentialPos = splitMark.start()
yield text[pos:]
class Translator(threading.Thread):
def __init__(self, lang_from, lang_to, text, lang_swap=None, chunksize=350, *args, **kwargs):
super(Translator, self).__init__(*args, **kwargs)
self._stop = threading.Event()
self.text = text
self.chunksize = chunksize
self.lang_to = lang_to
self.lang_from = lang_from
self.lang_swap = lang_swap
self.translation = ''
self.lang_translated = ''
self.firstChunk = True
def stop(self):
self._stop.set()
def run(self):
for chunk in splitChunks(self.text, self.chunksize):
# Make sure we don't send requests to google too often.
# Try to simulate a human.
if not self.firstChunk:
sleep(randint(1, 10))
req = self.buildRequest(chunk, self.lang_from, self.lang_to)
try:
response = urllib2.urlopen(req)
translation, lang_translated = self.parseData(response)
if self.firstChunk and self.lang_from == "auto" and lang_translated == self.lang_to and self.lang_swap is not None:
self.lang_to = self.lang_swap
self.firstChunk = False
req = self.buildRequest(chunk.encode('utf-8'), self.lang_from, self.lang_to)
response = urllib2.urlopen(req)
translation, lang_translated = self.parseData(response)
except Exception as e:
log.exception("Can not translate text '%s'" %chunk)
# We have probably been blocked, so stop trying to translate.
raise e
self.translation += translation
# some adjustment, better to do on full text
self.translation = self.fixNewlines(self.translation)
self.lang_translated = lang_translated
def buildRequest(self, text, lang_from, lang_to):
"""Build POST request which will be sent to Google."""
urlTemplate = 'http://translate.google.com/translate_a/single?client=t&sl={lang_from}&tl={lang_to}&ie=utf-8&oe=utf-8&dt=t&dt=bd&tk='
url = urlTemplate.format(lang_from=lang_from, lang_to=lang_to)
header = {'User-agent': 'Mozilla/5.0', 'Content-Type': 'application/x-www-form-urlencoded'}
data = 'text=%s' %urllib2.quote(text)
req = urllib2.Request(url, data, header)
return req
def parseData(self, response):
"""Parse unstructured response."""
data = response.readlines()[0]
# get segments with couples ["translation","original text"]
l1, l2 = data.split(']],', 1)
translation = l1[3:]
if l2.startswith('[[\"'):
# get list of synonyms
syn = l2[l2.find(',[')+1:l2.find(']')].split(',')
temp = ', '.join([x.replace('\"', '') for x in syn])
else:
# get a list with each couple as item
sentences = translation.split('],[')
temp = ''
# get translation, removing first char (quote symbol)
for item in sentences:
item = item.split('\",\"', 1)[0][1:]
# join all translations
temp = ' '.join([temp, item])
translation = temp.decode('string-escape').decode('utf-8')
translation = self.fixPunctuation(translation)
# get the language of original text
tempLang = data.partition(']],,\"')[2]
lang = tempLang[:tempLang.find('\"')]
if lang == '':
lang = _("unavailable")
return translation, lang
def fixPunctuation(self, translation):
"""Clean text from space before punctuation symbol."""
# list of potentially positions of spaces to remove
spacePos = []
for puncMark in splitReg.finditer(translation):
spacePos.append(puncMark.start()-1)
if len(spacePos) == 0:
return translation
fixedTranslation = ''
for n in xrange(0,len(translation)):
temp = translation[n]
if n in spacePos and temp == ' ':
continue
else:
fixedTranslation += temp
return fixedTranslation
def fixNewlines(self, translation):
"""Adjust newlines and (subsequent or double) spaces."""
fixes = [('\r\n ', '\r\n'), ('\n ', '\r\n'), (' ', ' ')]
for fix in fixes:
translation = translation.replace(fix[0], fix[1])
# first char is a space, so...
return translation[1:]
languages = {
"af": _(u"Afrikaans"),
"sq": _(u"Albanian"),
"am": _(u"Amharic"),
"ar": _(u"Arabic"),
"hy": _(u"Armenian"),
"az": _(u"Azerbaijani"),
"eu": _(u"Basque"),
"be": _(u"Belarusian"),
"bn": _(u"Bengali"),
"bh": _(u"Bihari"),
"bg": _(u"Bulgarian"),
"my": _(u"Burmese"),
"ca": _(u"Catalan"),
"chr": _(u"Cherokee"),
"zh": _(u"Chinese"),
"zh-CN": _(u"Chinese_simplified"),
"zh-TW": _(u"Chinese_traditional"),
"hr": _(u"Croatian"),
"cs": _(u"Czech"),
"da": _(u"Danish"),
"dv": _(u"Dhivehi"),
"nl": _(u"Dutch"),
"en": _(u"English"),
"eo": _(u"Esperanto"),
"et": _(u"Estonian"),
"tl": _(u"Filipino"),
"fi": _(u"Finnish"),
"fr": _(u"French"),
"gl": _(u"Galician"),
"ka": _(u"Georgian"),
"de": _(u"German"),
"el": _(u"Greek"),
"gn": _(u"Guarani"),
"gu": _(u"Gujarati"),
"iw": _(u"Hebrew"),
"hi": _(u"Hindi"),
"hu": _(u"Hungarian"),
"is": _(u"Icelandic"),
"id": _(u"Indonesian"),
"iu": _(u"Inuktitut"),
"ga": _(u"Irish"),
"it": _(u"Italian"),
"ja": _(u"Japanese"),
"kn": _(u"Kannada"),
"kk": _(u"Kazakh"),
"km": _(u"Khmer"),
"ko": _(u"Korean"),
"ku": _(u"Kurdish"),
"ky": _(u"Kyrgyz"),
"lo": _(u"Laothian"),
"lv": _(u"Latvian"),
"lt": _(u"Lithuanian"),
"mk": _(u"Macedonian"),
"ms": _(u"Malay"),
"ml": _(u"Malayalam"),
"mt": _(u"Maltese"),
"mr": _(u"Marathi"),
"mn": _(u"Mongolian"),
"ne": _(u"Nepali"),
"no": _(u"Norwegian"),
"or": _(u"Oriya"),
"ps": _(u"Pashto"),
"fa": _(u"Persian"),
"pl": _(u"Polish"),
"pt-PT": _(u"Portuguese"),
"pa": _(u"Punjabi"),
"ro": _(u"Romanian"),
"ru": _(u"Russian"),
"sa": _(u"Sanskrit"),
"sr": _(u"Serbian"),
"sd": _(u"Sindhi"),
"si": _(u"Sinhalese"),
"sk": _(u"Slovak"),
"sl": _(u"Slovenian"),
"es": _(u"Spanish"),
"sw": _(u"Swahili"),
"sv": _(u"Swedish"),
"tg": _(u"Tajik"),
"ta": _(u"Tamil"),
"tl": _(u"Tagalog"),
"te": _(u"Telugu"),
"th": _(u"Thai"),
"bo": _(u"Tibetan"),
"tr": _(u"Turkish"),
"uk": _(u"Ukrainian"),
"ur": _(u"Urdu"),
"uz": _(u"Uzbek"),
"ug": _(u"Uighur"),
"vi": _(u"Vietnamese"),
"cy": _(u"Welsh"),
"yi": _(u"Yiddish")
}
def available_languages():
l = languages.keys()
d = languages.values()
l.insert(0, '')
d.insert(0, _(u"autodetect"))
return sorted(zip(l, d))

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import translator
import wx
from widgetUtils import BaseDialog
class translateDialog(BaseDialog):
def __init__(self):
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
staticSource = wx.StaticText(panel, -1, _(u"Source language"))
self.source_lang = wx.ComboBox(panel, -1, choices=[x[1] for x in translator.available_languages()], style = wx.CB_READONLY)
self.source_lang.SetFocus()
staticDest = wx.StaticText(panel, -1, _(u"Target language"))
self.source_lang.SetSelection(0)
self.dest_lang = wx.ComboBox(panel, -1, choices=[x[1] for x in translator.available_languages()], style = wx.CB_READONLY)
listSizer = wx.BoxSizer(wx.HORIZONTAL)
listSizer.Add(staticSource)
listSizer.Add(self.source_lang)
listSizer.Add(staticDest)
listSizer.Add(self.dest_lang)
ok = wx.Button(panel, wx.ID_OK)
ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL)
self.SetEscapeId(wx.ID_CANCEL)
def get(self, control):
return getattr(self, control).GetSelection()

13
src/keys/__init__.py Normal file
View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
keyring = None
def setup():
global keyring
if keyring == None:
keyring = Keyring()
class Keyring(object):
def get_api_key(self):
return "5093442"

View File

@ -0,0 +1,7 @@
from .libloader import *
__version__ = 0.1
__author__ = 'Christopher Toth <q@q-continuum.net>'
__doc__ = """
Quickly and easily load shared libraries from various platforms. Also includes a libloader.com module for loading com modules on Windows.
"""

21
src/libloader/com.py Normal file
View File

@ -0,0 +1,21 @@
from pywintypes import com_error
from win32com.client import gencache
def prepare_gencache():
gencache.is_readonly = False
gencache.GetGeneratePath()
def load_com(*names):
result = None
for name in names:
try:
result = gencache.EnsureDispatch(name)
break
except com_error:
continue
if result is None:
raise com_error("Unable to load any of the provided com objects.")
return result

View File

@ -0,0 +1,56 @@
import ctypes
import collections
import platform
import os
TYPES = {
'Linux': {
'loader': ctypes.CDLL,
'functype': ctypes.CFUNCTYPE,
'prefix': 'lib',
'extension': '.so'
},
'Darwin': {
'loader': ctypes.CDLL,
'functype': ctypes.CFUNCTYPE,
'prefix': 'lib',
'extension': '.dylib'
},
}
if platform.system() == 'Windows':
TYPES['Windows'] = {
'loader': ctypes.WinDLL,
'functype': ctypes.WINFUNCTYPE,
'prefix': "",
'extension': '.dll'
}
class LibraryLoadError(OSError): pass
def load_library(library, x86_path='.', x64_path='.', *args, **kwargs):
lib = find_library_path(library, x86_path=x86_path, x64_path=x64_path)
loaded = _do_load(lib, *args, **kwargs)
if loaded is not None:
return loaded
raise LibraryLoadError('unable to load %r. Provided library path: %r' % (library, path))
def _do_load(file, *args, **kwargs):
loader = TYPES[platform.system()]['loader']
return loader(file, *args, **kwargs)
def find_library_path(libname, x86_path='.', x64_path='.'):
libname = '%s%s' % (TYPES[platform.system()]['prefix'], libname)
if platform.architecture()[0] == '64bit':
path = os.path.join(x64_path, libname)
else:
path = os.path.join(x86_path, libname)
ext = get_library_extension()
path = '%s%s' % (path, ext)
return os.path.abspath(path)
def get_functype():
return TYPES[platform.system()]['functype']
def get_library_extension():
return TYPES[platform.system()]['extension']

37
src/main.py Normal file
View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
import platform
import languageHandler
import widgetUtils
import paths
import config
import output
import logging
import keys
import application
from mysc.thread_utils import call_threaded
log = logging.getLogger("main")
def setup():
log.debug("Starting Socializer %s" % (application.version,))
config.setup()
log.debug("Using %s %s" % (platform.system(), platform.architecture()[0]))
log.debug("Application path is %s" % (paths.app_path(),))
log.debug("config path is %s" % (paths.config_path(),))
output.setup()
languageHandler.setLanguage("system")
keys.setup()
from controller import mainController
from sessionmanager import sessionManager
app = widgetUtils.mainLoopObject()
sm = sessionManager.sessionManagerController()
sm.fill_list()
if len(sm.sessions) == 0: sm.show()
else:
sm.do_ok()
del sm
r = mainController.Controller()
call_threaded(r.login)
app.run()
setup()

0
src/mysc/__init__.py Normal file
View File

17
src/mysc/localization.py Normal file
View File

@ -0,0 +1,17 @@
import os
import languageHandler
import logging
log = logging.getLogger("mysc.localization")
def get(rootFolder):
log.debug("Getting documentation folder. RootFolder: %s" % (rootFolder,))
defaultLocale = languageHandler.curLang
if len(defaultLocale) > 2:
defaultLocale = defaultLocale[:2]
log.debug("Locale: %s" % (defaultLocale,))
if os.path.exists(rootFolder+"/"+defaultLocale):
return defaultLocale
else:
log.debug("The folder does not exist, using the English folder...")
return "en"

View File

@ -0,0 +1,36 @@
import threading
import logging
log = logging.getLogger("mysc.repeating_timer")
class RepeatingTimer(threading.Thread):
"""Call a function after a specified number of seconds, it will then repeat again after the specified number of seconds
Note: If the function provided takes time to execute, this time is NOT taken from the next wait period
t = RepeatingTimer(30.0, f, args=[], kwargs={})
t.start()
t.cancel() # stop the timer's actions
"""
def __init__(self, interval, function, daemon=True, *args, **kwargs):
threading.Thread.__init__(self)
self.daemon = daemon
self.interval = float(interval)
self.function = function
self.args = args
self.kwargs = kwargs
self.finished = threading.Event()
def cancel(self):
"""Stop the timer if it hasn't finished yet"""
log.debug("Stopping repeater for %s" % (self.function,))
self.finished.set()
stop = cancel
def run(self):
while not self.finished.is_set():
self.finished.wait(self.interval)
if not self.finished.is_set(): #In case someone has canceled while waiting
try:
self.function(*self.args, **self.kwargs)
except:
log.exception("Execution failed. Function: %r args: %r and kwargs: %r" % (self.function, self.args, self.kwargs))

11
src/mysc/restart.py Normal file
View File

@ -0,0 +1,11 @@
# -*- coding: cp1252
import sys, os
def restart_program():
""" Function that restarts the application if is executed."""
args = sys.argv[:]
if not hasattr(sys, "frozen"):
args.insert(0, sys.executable)
if sys.platform == 'win32':
args = ['"%s"' % arg for arg in args]
os.execv(sys.executable, args)

14
src/mysc/thread_utils.py Normal file
View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import threading
def call_threaded(func, *args, **kwargs):
#Call the given function in a daemonized thread and return the thread.
def new_func(*a, **k):
# try:
func(*a, **k)
# except:
# pass
thread = threading.Thread(target=new_func, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return thread

24
src/output.py Normal file
View File

@ -0,0 +1,24 @@
# *- coding: utf-8 -*-
import logging as original_logging
logging = original_logging.getLogger('core.output')
from accessible_output2 import outputs
import sys
speaker = None
def speak(text, interrupt=0):
global speaker
if not speaker:
setup()
speaker.speak(text, interrupt)
speaker.braille(text)
def setup ():
global speaker
logging.debug("Initializing output subsystem.")
try:
speaker = outputs.auto.Auto()
except:
return logging.exception("Output: Error during initialization.")

67
src/paths.py Normal file
View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
import platform
import os
import sys
import logging
from platform_utils import paths as paths_
from functools import wraps
mode = "portable"
directory = None
log = logging.getLogger("paths")
def merge_paths(func):
@wraps(func)
def merge_paths_wrapper(*a):
return unicode(os.path.join(func(), *a))
return merge_paths_wrapper
@merge_paths
def app_path():
return paths_.app_path()
@merge_paths
def config_path():
global mode, directory
if mode == "portable":
if directory != None: path = os.path.join(directory, "config")
elif directory == None: path = app_path(u"config")
elif mode == "installed":
path = data_path("config")
if not os.path.exists(path):
log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path)
return path
@merge_paths
def logs_path():
global mode, directory
if mode == "portable":
if directory != None: path = os.path.join(directory, "logs")
elif directory == None: path = app_path(u"logs")
elif mode == "installed":
path = data_path("logs")
if not os.path.exists(path):
log.debug("%s path does not exist, creating..." % (path,))
os.mkdir(path)
return path
@merge_paths
def data_path(app_name='TW blue'):
if platform.system() == "Windows":
data_path = os.path.join(os.getenv("AppData"), app_name)
else:
data_path = os.path.join(os.environ['HOME'], ".%s" % app_name)
if not os.path.exists(data_path):
os.mkdir(data_path)
return data_path
@merge_paths
def locale_path():
return app_path(u"locales")
@merge_paths
def sound_path():
return app_path(u"sounds")

View File

View File

View File

@ -0,0 +1,41 @@
import _winreg
import os
import sys
from platform_utils import paths
RUN_REGKEY = ur"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
def is_installed(app_subkey):
"""Checks if the currently running copy is installed or portable variant. Requires the name of the application subkey found under the uninstall section in Windows registry."""
try:
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s" % app_subkey)
inst_dir = _winreg.QueryValueEx(key,"InstallLocation")[0]
except WindowsError:
return False
_winreg.CloseKey(key)
try:
return os.stat(inst_dir) == os.stat(paths.app_path())
except WindowsError:
return False
def getAutoStart(app_name):
"""Queries if the automatic startup should be set for the application or not, depending on it's current state."""
try:
key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, RUN_REGKEY)
val = _winreg.QueryValueEx(key, unicode(app_name))[0]
return os.stat(val) == os.stat(sys.argv[0])
except (WindowsError, OSError):
return False
def setAutoStart(app_name, enable=True):
"""Configures automatic startup for the application, if the enable argument is set to True. If set to False, deletes the application AutoStart value."""
if getAutoStart(app_name) == enable:
return
key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, RUN_REGKEY, 0, _winreg.KEY_WRITE)
if enable:
_winreg.SetValueEx(key, unicode(app_name), None, _winreg.REG_SZ, sys.argv[0])
else:
_winreg.DeleteValue(key, unicode(app_name))

View File

@ -0,0 +1,16 @@
# Replacement for py2exe distributed module
# Avoids the use of the standard py2exe console.
# Just import this file and it should go away
import sys
if hasattr(sys,"frozen"): # true only if we are running as a py2exe app
class Blackhole(object):
def write(self,text):
pass
def flush(self):
pass
sys.stdout = Blackhole()
sys.stderr = Blackhole()
del Blackhole
del sys

View File

@ -0,0 +1,51 @@
import ctypes
import collections
import platform
import os
TYPES = {
'Linux': {
'loader': ctypes.CDLL,
'functype': ctypes.CFUNCTYPE,
'prefix': 'lib',
'extension': '.so'
},
'Darwin': {
'loader': ctypes.CDLL,
'functype': ctypes.CFUNCTYPE,
'prefix': 'lib',
'extension': '.dylib'
},
}
if platform.system() == 'Windows':
TYPES['Windows'] = {
'loader': ctypes.WinDLL,
'functype': ctypes.WINFUNCTYPE,
'prefix': "",
'extension': '.dll'
}
class LibraryLoadError(Exception): pass
def load_library(library, x86_path='.', x64_path='.', *args, **kwargs):
lib = find_library_path(library, x86_path=x86_path, x64_path=x64_path)
loaded = _do_load(lib, *args, **kwargs)
if loaded is not None:
return loaded
raise LibraryLoadError('unable to load %r. Provided library path: %r' % (library, path))
def _do_load(file, *args, **kwargs):
loader = TYPES[platform.system()]['loader']
return loader(file, *args, **kwargs)
def find_library_path(libname, x86_path='.', x64_path='.'):
libname = '%s%s' % (TYPES[platform.system()]['prefix'], libname)
if platform.machine() == 'x86_64':
path = os.path.join(x64_path, libname)
else:
path = os.path.join(x86_path, libname)
ext = TYPES[platform.system()]['extension']
return '%s%s' % (path, ext)
def get_functype():
return TYPES[platform.system()]['functype']

114
src/platform_utils/paths.py Normal file
View File

@ -0,0 +1,114 @@
import inspect
import platform
import os
import subprocess
import sys
import string
import unicodedata
def app_data_path(app_name=None):
"""Cross-platform method for determining where to put application data."""
"""Requires the name of the application"""
plat = platform.system()
if plat == 'Windows':
import winpaths
path = winpaths.get_appdata()
elif plat == 'Darwin':
path = os.path.join(os.path.expanduser('~'), 'Library', 'Application Support')
elif plat == 'Linux':
path = os.path.expanduser('~')
app_name = '.%s' % app_name.replace(' ', '_')
return os.path.join(path, app_name)
def prepare_app_data_path(app_name):
"""Creates the application's data directory, given its name."""
dir = app_data_path(app_name)
return ensure_path(dir)
def embedded_data_path():
if platform.system() == 'Darwin' and is_frozen():
return os.path.abspath(os.path.join(executable_directory(), '..', 'Resources'))
return app_path()
def is_frozen():
"""Return a bool indicating if application is compressed"""
import imp
return hasattr(sys, 'frozen') or imp.is_frozen("__main__")
def get_executable():
"""Returns the full executable path/name if frozen, or the full path/name of the main module if not."""
if is_frozen():
if platform.system() != 'Darwin':
return sys.executable
#On darwin, sys.executable points to python. We want the full path to the exe we ran.
exedir = os.path.abspath(os.path.dirname(sys.executable))
items = os.listdir(exedir)
items.remove('python')
return os.path.join(exedir, items[0])
#Not frozen
try:
import __main__
return os.path.abspath(__main__.__file__)
except AttributeError:
return sys.argv[0]
def get_module(level=2):
"""Hacky method for deriving the caller of this function's module."""
return inspect.getmodule(inspect.stack()[level][0]).__file__
def executable_directory():
"""Always determine the directory of the executable, even when run with py2exe or otherwise frozen"""
executable = get_executable()
path = os.path.abspath(os.path.dirname(executable))
return path
def app_path():
"""Return the root of the application's directory"""
path = executable_directory()
if is_frozen() and platform.system() == 'Darwin':
path = os.path.abspath(os.path.join(path, '..', '..'))
return path
def module_path(level=2):
return os.path.abspath(os.path.dirname(get_module(level)))
def documents_path():
"""On windows, returns the path to My Documents. On OSX, returns the user's Documents folder. For anything else, returns the user's home directory."""
plat = platform.system()
if plat == 'Windows':
import winpaths
path = winpaths.get_my_documents()
elif plat == 'Darwin':
path = os.path.join(os.path.expanduser('~'), 'Documents')
else:
path = os.path.expanduser('~')
return path
def safe_filename(filename):
"""Given a filename, returns a safe version with no characters that would not work on different platforms."""
SAFE_FILE_CHARS = "'-_.()[]{}!@#$%^&+=`~ "
filename = unicode(filename)
new_filename = ''.join(c for c in filename if c in SAFE_FILE_CHARS or c.isalnum())
#Windows doesn't like directory names ending in space, macs consider filenames beginning with a dot as hidden, and windows removes dots at the ends of filenames.
return new_filename.strip(' .')
def ensure_path(path):
if not os.path.exists(path):
os.makedirs(path)
return path
def start_file(path):
if platform.system() == 'Windows':
os.startfile(path)
else:
subprocess.Popen(['open', path])
def get_applications_path():
"""Return the directory where applications are commonly installed on the system."""
plat = platform.system()
if plat == 'Windows':
import winpaths
return winpaths.get_program_files()
elif plat == 'Darwin':
return '/Applications'

View File

@ -0,0 +1,27 @@
import platform
import ctypes
import os
import signal
def kill_windows_process(pid):
PROCESS_TERMINATE = 1
SYNCHRONIZE=1048576
handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, False, pid)
ctypes.windll.kernel32.TerminateProcess(handle, -1)
ctypes.windll.kernel32.WaitForSingleObject(handle, 1000)
ctypes.windll.kernel32.CloseHandle(handle)
def kill_unix_process(pid):
try:
os.kill(pid, signal.SIGKILL)
except OSError:
pass
def kill_process(pid):
if pid < 0:
return
if platform.system() == 'Windows':
kill_windows_process(pid)
else:
kill_unix_process(pid)

View File

@ -0,0 +1,10 @@
import _winreg
SHELL_REGKEY = ur"Directory\shell"
def context_menu_integrate(item_key_name, item_display_text, item_command):
app_menu_key = _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, SHELL_REGKEY, 0, _winreg.KEY_WRITE)
menu_item_key = _winreg.CreateKey(app_menu_key, item_key_name)
_winreg.SetValueEx(menu_item_key, None, None, _winreg.REG_SZ, item_display_text)
item_command_key = _winreg.CreateKey(menu_item_key, 'command')
_winreg.SetValueEx(item_command_key, None, None, _winreg.REG_SZ, item_command)

View File

@ -0,0 +1,9 @@
import platform
import webbrowser
def open(url):
if platform.system() == 'Windows':
browser = webbrowser.get('windows-default')
else:
browser = webbrowser
browser.open_new_tab(url)

6
src/session.defaults Normal file
View File

@ -0,0 +1,6 @@
[vk]
user = string(default="")
password = string(default="")
token = string(default="")
[general]
reverse_timelines = boolean(default=False)

View File

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from UserDict import UserDict
from configobj import ConfigObj, ParseError
from validate import Validator, VdtValueError
import os
class ConfigurationResetException(Exception):
pass
class Configuration (UserDict):
def __init__ (self, file=None, spec=None, *args, **kwargs):
self.file = file
self.spec = spec
self.validator = Validator()
self.setup_config(file=file, spec=spec)
self.validated = self.config.validate(self.validator, copy=True)
if self.validated:
self.write()
UserDict.__init__(self, self.config)
def setup_config (self, file, spec):
spec = ConfigObj(spec, list_values=False, encoding="utf-8")
try:
self.config = ConfigObj(infile=file, configspec=spec, create_empty=True, stringify=True, encoding="utf-8")
except ParseError:
os.remove(file)
self.config = ConfigObj(infile=file, configspec=spec, create_empty=True, stringify=True)
raise ConfigurationResetException
def __getitem__ (self, *args, **kwargs):
return dict(self.config).__getitem__(*args, **kwargs)
def __setitem__ (self, *args, **kwargs):
self.config.__setitem__(*args, **kwargs)
UserDict.__setitem__(self, *args, **kwargs)
def write (self):
if hasattr(self.config, 'write'):
self.config.write()
class SessionConfiguration (Configuration):
def setup_config (self, file, spec):
#No infile required.
spec = ConfigObj(spec, list_values=False)
self.config = ConfigObj(configspec=spec, stringify=True)

View File

@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
import arrow
import languageHandler
import paths
import vkSessionHandler
import logging
import utils
from config_utils import Configuration, ConfigurationResetException
log = logging.getLogger("vk.session")
sessions = {}
def add_attachment(attachment):
""" Adds information about the attachment files in posts. It only adds the text, I mean, no attachment file is added here.
This will produce a result like 'Title of a web page: http://url.xxx', etc."""
msg = u""
if attachment["type"] == "link":
msg = u"{0}: {1}".format(attachment["link"]["title"], attachment["link"]["url"])
elif attachment["type"] == "photo":
msg = attachment["photo"]["text"]
if msg == "":
return "photo with no description available"
elif attachment["type"] == "video":
msg = u"video: {0}".format(attachment["video"]["title"],)
return msg
def add_text(status):
""" This shorts the text to 140 characters for displaying it in the list control."""
message = ""
if status.has_key("text"):
if len(status["text"]) < 140:
message = status["text"]
else:
message = status["text"][:139]
return message
def compose_new(status, session):
""" This method is used to compose an item of the news feed."""
user = session.get_user_name(status["source_id"])
message = ""
original_date = arrow.get(status["date"])
created_at = original_date.humanize(locale=languageHandler.getLanguage())
if status["type"] == "post":
message += add_text(status)
if status.has_key("attachment") and len(status["attachment"]) > 0:
message += add_attachment(status["attachment"])
if message == "":
message = "no description available"
elif status["type"] == "audio":
message = u"{0} has posted an audio: {1}".format(user, u", ".join(compose_audio(status["audio"][1], session)),)
elif status["type"] == "friend":
ids = ""
for i in status["friends"][1:]:
ids = ids + "{0}, ".format(i["uid"])
users = session.vk.client.users.get(user_ids=ids, fields="uid, first_name, last_name")
msg_users = u""
for i in users:
msg_users = msg_users + u"{0} {1}, ".format(i["first_name"], i["last_name"])
message = u"{0} hadded friends: {1}".format(user, msg_users)
else:
if status["type"] != "post": print status["type"]
return [user, message, created_at]
def compose_status(status, session):
# print status.keys()
user = session.get_user_name(status["from_id"])
message = ""
# user = status["copy_owner_id"]
original_date = arrow.get(status["date"])
created_at = original_date.humanize(locale=languageHandler.getLanguage())
# created_at = str(status["date"])
if status["post_type"] == "post":
message += add_text(status)
if status.has_key("attachment") and len(status["attachment"]) > 0:
message += add_attachment(status["attachment"])
if message == "":
message = "no description available"
return [user, message, created_at]
def compose_audio(audio, session):
# print audio
return [audio["title"], audio["artist"], utils.seconds_to_string(audio["duration"])]
class vkSession(object):
def order_buffer(self, name, data, field):
""" Put the new items on the local database. Useful for cursored buffers
name str: The name for the buffer stored in the dictionary.
data list: A list with items and some information about cursors.
returns the number of items that has been added in this execution"""
num = 0
if self.db.has_key(name) == False:
self.db[name] = {}
self.db[name]["items"] = []
for i in data:
# print i.keys()
# print i.keys()
# print i["type"]
# if i.has_key(field) and find_item(i[field], self.db[name]["items"], field) == None:
if i.has_key("type") and i["type"] == "wall_photo": continue
if i not in self.db[name]["items"]:
if self.settings["general"]["reverse_timelines"] == False: self.db[name]["items"].append(i)
else: self.db[name]["items"].insert(0, i)
num = num+1
return num
def __init__(self, session_id):
self.session_id = session_id
self.logged = False
self.settings = None
self.vk = vkSessionHandler.vkObject()
self.db = {}
self.db["users"] = {}
self.db["groups"] = {}
@property
def is_logged(self):
return self.logged
def get_configuration(self):
""" Gets settings for a session."""
file_ = "%s/session.conf" % (self.session_id,)
# try:
log.debug("Creating config file %s" % (file_,))
self.settings = Configuration(paths.config_path(file_), paths.app_path("session.defaults"))
# except:
# log.exception("The session configuration has failed.")
def login(self):
""" Login using credentials from settings.
if the user account isn't authorised, it needs to call self.authorise() before login."""
if self.settings["vk"]["token"] != None:
self.vk.login_access_token(self.settings["vk"]["token"])
self.logged = True
log.debug("Logged.")
else:
self.logged = False
raise Exceptions.RequireCredentialsSessionError
def authorise(self):
if self.logged == True:
raise Exceptions.AlreadyAuthorisedError("The authorisation process is not needed at this time.")
else:
self.vk.login(self.settings["vk"]["user"], self.settings["vk"]["password"])
self.settings["vk"]["token"] = self.vk.client._session.access_token
def post_wall_status(self, message, *args, **kwargs):
response = self.vk.client.wall.post(message=message, *args, **kwargs)
# print response
def get_newsfeed(self, name="newsfeed", no_next=True, endpoint="", *args, **kwargs):
data = getattr(self.vk.client.newsfeed, "get")(*args, **kwargs)
# print data
if data != None:
# try:
# num = self.order_buffer(name, data[1:])
# except:
num = self.order_buffer(name, data["items"][:-1], "post_id")
ids = ""
gids = ""
for i in data["items"][:-1]:
if i.has_key("source_id"):
if i["source_id"] > 0:
if str(i["source_id"]) not in ids: ids += "{0},".format(i["source_id"])
else:
if str(i["source_id"]) not in gids: gids += "{0},".format(abs(i["source_id"]))
self.get_users(ids, gids)
return num
def get_page(self, name="", no_next=True, endpoint="", *args, **kwargs):
data = None
full_list = False
if kwargs.has_key("parent_endpoint"):
p = kwargs["parent_endpoint"]
kwargs.pop("parent_endpoint")
if kwargs.has_key("full_list"):
print kwargs
full_list = True
kwargs.pop("full_list")
if kwargs.has_key("identifier"):
identifier = kwargs["identifier"]
kwargs.pop("identifier")
p = getattr(self.vk.client, p)
data = getattr(p, endpoint)(*args, **kwargs)
# print data
if data != None:
# try:
if full_list == False:
num = self.order_buffer(name, data[1:], identifier)
else:
num = self.order_buffer(name, data, identifier)
# except:
# num = self.order_buffer(name, data["items"][:-1])
ids = ""
for i in data[1:]:
if i.has_key("from_id"):
if str(i["from_id"]) not in ids: ids += "{0},".format(i["from_id"])
self.get_users(ids)
return num
def get_user_name(self, user_id):
if user_id > 0:
if self.db["users"].has_key(user_id):
return self.db["users"][user_id]
else:
return "no specified user"
else:
if self.db["groups"].has_key(abs(user_id)):
return self.db["groups"][abs(user_id)]
else:
return "no specified community"
def get_users(self, user_ids=None, group_ids=None):
if user_ids != None:
u = self.vk.client.users.get(user_ids=user_ids, fields="uid, first_name, last_name")
for i in u:
self.db["users"][i["uid"]] = u"{0} {1}".format(i["first_name"], i["last_name"])
if group_ids != None:
g = self.vk.client.groups.getById(group_ids=group_ids, fields="name")
for i in g:
self.db["groups"][i["gid"]] = i["name"]

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
import os
import shutil
import widgetUtils
import wxUI as view
import paths
import time
import os
import logging
import session
from config_utils import Configuration
log = logging.getLogger("sessionmanager.sessionManager")
class sessionManagerController(object):
def __init__(self):
super(sessionManagerController, self).__init__()
log.debug("Setting up the session manager.")
self.view = view.sessionManagerWindow()
widgetUtils.connect_event(self.view.new, widgetUtils.BUTTON_PRESSED, self.manage_new_account)
widgetUtils.connect_event(self.view.remove, widgetUtils.BUTTON_PRESSED, self.remove)
self.new_sessions = {}
self.removed_sessions = []
def fill_list(self):
sessionsList = []
log.debug("Filling the sessions list.")
self.sessions = []
for i in os.listdir(paths.config_path()):
if os.path.isdir(paths.config_path(i)):
log.debug("Adding session %s" % (i,))
strconfig = "%s/session.conf" % (paths.config_path(i))
config_test = Configuration(strconfig)
name = config_test["vk"]["user"]
sessionsList.append(name)
self.sessions.append(i)
self.view.fill_list(sessionsList)
def show(self):
if self.view.get_response() == widgetUtils.OK:
self.do_ok()
def do_ok(self):
log.debug("Starting sessions...")
for i in self.sessions:
if session.sessions.has_key(i) == True: continue
s = session.vkSession(i)
s.get_configuration()
session.sessions[i] = s
self.new_sessions[i] = s
def manage_new_account(self, *args, **kwargs):
if self.view.new_account_dialog() == widgetUtils.YES:
location = (str(time.time())[-6:])
log.debug("Creating session in the %s path" % (location,))
s = session.vkSession(location)
path = paths.config_path(location)
if not os.path.exists(path):
log.debug("Creating %s path" % (paths.config_path(path),))
os.mkdir(path)
s.get_configuration()
self.get_authorisation(s)
self.sessions.append(location)
self.view.add_new_session_to_list()
# except:
# log.exception("Error authorising the session")
# self.view.show_unauthorised_error()
# return
def remove(self, *args, **kwargs):
if self.view.remove_account_dialog() == widgetUtils.YES:
selected_account = self.sessions[self.view.get_selected()]
self.view.remove_session(self.view.get_selected())
self.removed_sessions.append(selected_account)
self.sessions.remove(selected_account)
shutil.rmtree(path=paths.config_path(selected_account), ignore_errors=True)
def get_authorisation(self, c):
dl = view.newSessionDialog()
if dl.ShowModal() == widgetUtils.OK:
c.settings["vk"]["user"] = dl.get_email()
c.settings["vk"]["password"] = dl.get_password()
c.authorise()
c.settings.write()

View File

@ -0,0 +1,17 @@
#!/usr/bin/python
import keys
from vk import API, AuthSession, Session
class vkObject(object):
def __init__(self):
self.api_key = keys.keyring.get_api_key()
def login(self, user, password):
s = AuthSession(app_id=self.api_key, user_login=user, user_password=password, scope="wall, notify, friends, photos, audio, video, docs, notes, pages, status, groups, messages, notifications, stats")
self.client = API(s)
self.client.account.getProfileInfo()
def login_access_token(self, token):
s = Session(access_token=token)
self.client = API(s)

View File

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
class sessionManagerWindow(widgetUtils.BaseDialog):
def __init__(self):
super(sessionManagerWindow, self).__init__(parent=None, title="Session manager", size=wx.DefaultSize)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(panel, -1, u"Accounts", size=wx.DefaultSize)
listSizer = wx.BoxSizer(wx.HORIZONTAL)
self.list = widgetUtils.list(panel, u"Account", style=wx.LC_SINGLE_SEL|wx.LC_REPORT)
listSizer.Add(label, 0, wx.ALL, 5)
listSizer.Add(self.list.list, 0, wx.ALL, 5)
sizer.Add(listSizer, 0, wx.ALL, 5)
self.new = wx.Button(panel, -1, u"New account", size=wx.DefaultSize)
self.remove = wx.Button(panel, -1, _(u"Remove account"))
ok = wx.Button(panel, wx.ID_OK, size=wx.DefaultSize)
ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, size=wx.DefaultSize)
buttons = wx.BoxSizer(wx.HORIZONTAL)
buttons.Add(self.new, 0, wx.ALL, 5)
buttons.Add(ok, 0, wx.ALL, 5)
buttons.Add(cancel, 0, wx.ALL, 5)
sizer.Add(buttons, 0, wx.ALL, 5)
panel.SetSizer(sizer)
min = sizer.CalcMin()
self.SetClientSize(min)
def fill_list(self, sessionsList):
for i in sessionsList:
self.list.insert_item(False, i)
if self.list.get_count() > 0:
self.list.select_item(0)
self.list.list.SetSize(self.list.list.GetBestSize())
def ok(self, ev):
if self.list.get_count() == 0:
wx.MessageDialog(None, _(u"You need to configure an account."), _(u"Account Error"), wx.ICON_ERROR).ShowModal()
return
self.controller.do_ok()
self.EndModal(wx.ID_OK)
def new_account_dialog(self):
return wx.MessageDialog(self, _(u"The request for the required facebook authorization to continue will be opened on your browser. You only need to do it once. Would you like to autorhise a new account now?"), _(u"Authorisation"), wx.YES_NO).ShowModal()
def add_new_session_to_list(self):
total = self.list.get_count()
name = _(u"Authorised account %d") % (total+1)
self.list.insert_item(False, name)
if self.list.get_count() == 1:
self.list.select_item(0)
def remove_account_dialog(self):
return wx.MessageDialog(self, _(u"Do you really want delete this account?"), _(u"Remove account"), wx.YES_NO).ShowModal()
def get_selected(self):
return self.list.get_selected()
def remove_session(self, sessionID):
self.list.remove_item(sessionID)
class newSessionDialog(widgetUtils.BaseDialog):
def __init__(self):
super(newSessionDialog, self).__init__(parent=None, id=wx.NewId(), title=_(u"Authorise VK"))
panel = wx.Panel(self)
lbl1 = wx.StaticText(panel, -1, _(u"Email address"))
self.email = wx.TextCtrl(panel, -1)
lbl2 = wx.StaticText(panel, -1, _(u"Password"))
self.passw = wx.TextCtrl(panel, -1, style=wx.TE_PASSWORD)
sizer = wx.BoxSizer()
b1 = wx.BoxSizer(wx.HORIZONTAL)
b1.Add(lbl1, 0, wx.ALL, 5)
b1.Add(self.email, 0, wx.ALL, 5)
b2 = wx.BoxSizer(wx.HORIZONTAL)
b2.Add(lbl2, 0, wx.ALL, 5)
b2.Add(self.passw, 0, wx.ALL, 5)
sizer.Add(b1, 0, wx.ALL, 5)
sizer.Add(b2, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
btnb = wx.BoxSizer(wx.HORIZONTAL)
btnb.Add(ok, 0, wx.ALL, 5)
btnb.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnb, 0, wx.ALL, 5)
panel.SetSizer(sizer)
def get_email(self):
return self.email.GetValue()
def get_password(self):
return self.passw.GetValue()

27
src/utils.py Normal file
View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
def seconds_to_string(seconds, precision=0):
day = seconds // 86400
hour = seconds // 3600
min = (seconds // 60) % 60
sec = seconds - (hour * 3600) - (min * 60)
sec_spec = "." + str(precision) + "f"
sec_string = sec.__format__(sec_spec)
string = ""
if day == 1:
string += _(u"%d day, ") % day
elif day >= 2:
string += _(u"%d days, ") % day
if (hour == 1):
string += _(u"%d hour, ") % hour
elif (hour >= 2):
string += _("%d hours, ") % hour
if (min == 1):
string += _(u"%d minute, ") % min
elif (min >= 2):
string += _(u"%d minutes, ") % min
if sec >= 0 and sec <= 2:
string += _(u"%s second") % sec_string
else:
string += _(u"%s seconds") % sec_string
return string

10
src/vk/__init__.py Normal file
View File

@ -0,0 +1,10 @@
from vk.api import logger
from vk.api import Session, AuthSession, InteractiveSession, InteractiveAuthSession
from vk.api import VERSION
from vk.api import API
import upload
__version__ = version = VERSION
# API = OAuthAPI

177
src/vk/api.py Normal file
View File

@ -0,0 +1,177 @@
# coding=utf8
import logging
import logging.config
from vk.logs import LOGGING_CONFIG
from vk.utils import stringify_values, json_iter_parse, LoggingSession
from vk.exceptions import VkAuthError, VkAPIMethodError, CAPTCHA_IS_NEEDED, AUTHORIZATION_FAILED
from vk.mixins import AuthMixin, InteractiveMixin
VERSION = '2.0a4'
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger('vk')
class Session(object):
API_URL = 'https://api.vk.com/method/'
def __init__(self, access_token=None):
logger.debug('API.__init__(access_token=%(access_token)r)', {'access_token': access_token})
# self.api_version = api_version
# self.default_timeout = default_timeout
self.access_token = access_token
self.access_token_is_needed = False
# self.requests_session = requests.Session()
self.requests_session = LoggingSession()
self.requests_session.headers['Accept'] = 'application/json'
self.requests_session.headers['Content-Type'] = 'application/x-www-form-urlencoded'
@property
def access_token(self):
logger.debug('Check that we need new access token')
if self.access_token_is_needed:
logger.debug('We need new access token. Try to get it.')
self.access_token, self._access_token_expires_in = self.get_access_token()
logger.info('Got new access token')
logger.debug('access_token = %r, expires in %s', self.censored_access_token, self._access_token_expires_in)
return self._access_token
@access_token.setter
def access_token(self, value):
self._access_token = value
self._access_token_expires_in = None
self.access_token_is_needed = not self._access_token
@property
def censored_access_token(self):
if self._access_token:
return '{}***{}'.format(self._access_token[:4], self._access_token[-4:])
def get_user_login(self):
logger.debug('Do nothing to get user login')
def get_access_token(self):
"""
Dummy method
"""
logger.debug('API.get_access_token()')
return self._access_token, self._access_token_expires_in
def make_request(self, method_request, **method_kwargs):
logger.debug('Prepare API Method request')
response = self.send_api_request(method_request)
response.raise_for_status()
# there are may be 2 dicts in one JSON
# for example: {'error': ...}{'response': ...}
errors = []
error_codes = []
for data in json_iter_parse(response.text):
if 'error' in data:
error_data = data['error']
if error_data['error_code'] == CAPTCHA_IS_NEEDED:
return self.on_captcha_is_needed(error_data, method_request)
error_codes.append(error_data['error_code'])
errors.append(error_data)
if 'response' in data:
for error in errors:
logger.warning(str(error))
return data['response']
if AUTHORIZATION_FAILED in error_codes: # invalid access token
logger.info('Authorization failed. Access token will be dropped')
self.access_token = None
return self.make_request(method_request)
else:
raise VkAPIMethodError(errors[0])
def send_api_request(self, request):
url = self.API_URL + request._method_name
method_args = request._api._method_default_args.copy()
method_args.update(stringify_values(request._method_args))
if self.access_token:
method_args['access_token'] = self.access_token
timeout = request._api._timeout
response = self.requests_session.post(url, method_args, timeout=timeout)
return response
def on_captcha_is_needed(self, error_data, method_request):
"""
Default behavior on CAPTCHA is to raise exception
Reload this in child
"""
raise VkAPIMethodError(error_data)
def auth_code_is_needed(self, content, session):
"""
Default behavior on 2-AUTH CODE is to raise exception
Reload this in child
"""
raise VkAuthError('Authorization error (2-factor code is needed)')
def auth_captcha_is_needed(self, content, session):
"""
Default behavior on CAPTCHA is to raise exception
Reload this in child
"""
raise VkAuthError('Authorization error (captcha)')
def phone_number_is_needed(self, content, session):
"""
Default behavior on PHONE NUMBER is to raise exception
Reload this in child
"""
logger.error('Authorization error (phone number is needed)')
raise VkAuthError('Authorization error (phone number is needed)')
class API(object):
def __init__(self, session, timeout=10, **method_default_args):
self._session = session
self._timeout = timeout
self._method_default_args = method_default_args
def __getattr__(self, method_name):
return Request(self, method_name)
def __call__(self, method_name, **method_kwargs):
return getattr(self, method_name)(**method_kwargs)
class Request(object):
__slots__ = ('_api', '_method_name', '_method_args')
def __init__(self, api, method_name):
self._api = api
self._method_name = method_name
def __getattr__(self, method_name):
return Request(self._api, self._method_name + '.' + method_name)
def __call__(self, **method_args):
self._method_args = method_args
return self._api._session.make_request(self)
class AuthSession(AuthMixin, Session):
pass
class InteractiveSession(InteractiveMixin, Session):
pass
class InteractiveAuthSession(InteractiveMixin, AuthSession):
pass

30
src/vk/exceptions.py Normal file
View File

@ -0,0 +1,30 @@
# API Error Codes
AUTHORIZATION_FAILED = 5 # Invalid access token
CAPTCHA_IS_NEEDED = 14
ACCESS_DENIED = 15 # No access to call this method
class VkException(Exception):
pass
class VkAuthError(VkException):
pass
class VkAPIMethodError(VkException):
__slots__ = ['error', 'code', 'message', 'request_params', 'redirect_uri']
def __init__(self, error):
super(VkAPIMethodError, self).__init__()
self.error = error
self.code = error.get('error_code')
self.message = error.get('error_msg')
self.request_params = error.get('request_params')
self.redirect_uri = error.get('redirect_uri')
def __str__(self):
error_message = '{self.code}. {self.message}. request_params = {self.request_params}'.format(self=self)
if self.redirect_uri:
error_message += ',\nredirect_uri = "{self.redirect_uri}"'.format(self=self)
return error_message

26
src/vk/logs.py Normal file
View File

@ -0,0 +1,26 @@
import sys
LOGGING_CONFIG = {
'version': 1,
'loggers': {
'vk': {
'level': 'INFO',
'handlers': ['vk-stdout'],
'propagate': False,
},
},
'handlers': {
'vk-stdout': {
'class': 'logging.StreamHandler',
'stream': sys.stdout,
'formatter': 'vk-verbose',
},
},
'formatters': {
'vk-verbose': {
'format': '%(asctime)s %(name) -5s %(module)s:%(lineno)d %(levelname)s: %(message)s',
},
},
}

215
src/vk/mixins.py Normal file
View File

@ -0,0 +1,215 @@
# coding=utf8
import re
import logging
import requests
from vk.exceptions import VkAuthError
from vk.utils import urlparse, parse_qsl, raw_input, get_url_query, LoggingSession, get_form_action
logger = logging.getLogger('vk')
class AuthMixin(object):
LOGIN_URL = 'https://m.vk.com'
# REDIRECT_URI = 'https://oauth.vk.com/blank.html'
AUTHORIZE_URL = 'https://oauth.vk.com/authorize'
CAPTCHA_URI = 'https://m.vk.com/captcha.php'
def __init__(self, app_id=None, user_login='', user_password='', scope='offline', **kwargs):
logger.debug('AuthMixin.__init__(app_id=%(app_id)r, user_login=%(user_login)r, user_password=%(user_password)r, **kwargs=%(kwargs)s)',
dict(app_id=app_id, user_login=user_login, user_password=user_password, kwargs=kwargs))
super(AuthMixin, self).__init__(**kwargs)
self.app_id = app_id
self.user_login = user_login
self.user_password = user_password
self.scope = scope
@property
def user_login(self):
if not self._user_login:
self._user_login = self.get_user_login()
return self._user_login
@user_login.setter
def user_login(self, value):
self._user_login = value
def get_user_login(self):
return self._user_login
@property
def user_password(self):
if not self._user_password:
self._user_password = self.get_user_password()
return self._user_password
@user_password.setter
def user_password(self, value):
self._user_password = value
def get_user_password(self):
return self._user_password
def get_access_token(self):
"""
Get access token using app id and user login and password.
"""
logger.debug('AuthMixin.get_access_token()')
auth_session = LoggingSession()
with auth_session as self.auth_session:
self.auth_session = auth_session
self.login()
auth_response_url_query = self.oauth2_authorization()
if 'access_token' in auth_response_url_query:
return auth_response_url_query['access_token'], auth_response_url_query['expires_in']
else:
raise VkAuthError('OAuth2 authorization error')
def login(self):
"""
Login
"""
response = self.auth_session.get(self.LOGIN_URL)
login_form_action = get_form_action(response.text)
if not login_form_action:
raise VkAuthError('VK changed login flow')
login_form_data = {
'email': self.user_login,
'pass': self.user_password,
}
response = self.auth_session.post(login_form_action, login_form_data)
logger.debug('Cookies: %s', self.auth_session.cookies)
response_url_query = get_url_query(response.url)
if 'remixsid' in self.auth_session.cookies or 'remixsid6' in self.auth_session.cookies:
return
if 'sid' in response_url_query:
self.auth_captcha_is_needed(response, login_form_data)
elif response_url_query.get('act') == 'authcheck':
self.auth_check_is_needed(response.text)
elif 'security_check' in response_url_query:
self.phone_number_is_needed(response.text)
else:
message = 'Authorization error (incorrect password)'
logger.error(message)
raise VkAuthError(message)
def oauth2_authorization(self):
"""
OAuth2
"""
auth_data = {
'client_id': self.app_id,
'display': 'mobile',
'response_type': 'token',
'scope': self.scope,
'v': '5.28',
}
response = self.auth_session.post(self.AUTHORIZE_URL, auth_data)
response_url_query = get_url_query(response.url)
if 'access_token' in response_url_query:
return response_url_query
# Permissions is needed
logger.info('Getting permissions')
# form_action = re.findall(r'<form method="post" action="(.+?)">', auth_response.text)[0]
form_action = get_form_action(response.text)
logger.debug('Response form action: %s', form_action)
if form_action:
response = self.auth_session.get(form_action)
response_url_query = get_url_query(response.url)
return response_url_query
try:
response_json = response.json()
except ValueError: # not JSON in response
error_message = 'OAuth2 grant access error'
else:
error_message = 'VK error: [{}] {}'.format(response_json['error'], response_json['error_description'])
logger.error('Permissions obtained')
raise VkAuthError(error_message)
def auth_check_is_needed(self, html):
logger.info('User enabled 2 factors authorization. Auth check code is needed')
auth_check_form_action = get_form_action(html)
auth_check_code = self.get_auth_check_code()
auth_check_data = {
'code': auth_check_code,
'_ajax': '1',
'remember': '1'
}
response = self.auth_session.post(auth_check_form_action, data=auth_check_data)
def auth_captcha_is_needed(self, response, login_form_data):
logger.info('Captcha is needed')
response_url_dict = get_url_query(response.url)
# form_url = re.findall(r'<form method="post" action="(.+)" novalidate>', response.text)
captcha_form_action = get_form_action(response.text)
logger.debug('form_url %s', captcha_form_action)
if not captcha_form_action:
raise VkAuthError('Cannot find form url')
captcha_url = '%s?s=%s&sid=%s' % (self.CAPTCHA_URI, response_url_dict['s'], response_url_dict['sid'])
# logger.debug('Captcha url %s', captcha_url)
login_form_data['captcha_sid'] = response_url_dict['sid']
login_form_data['captcha_key'] = self.on_captcha_is_needed(captcha_url)
response = self.auth_session.post(captcha_form_action, login_form_data)
# logger.debug('Cookies %s', self.auth_session.cookies)
# if 'remixsid' not in self.auth_session.cookies and 'remixsid6' not in self.auth_session.cookies:
# raise VkAuthError('Authorization error (Bad password or captcha key)')
def phone_number_is_needed(self, text):
raise VkAuthError('Phone number is needed')
def get_auth_check_code(self):
raise VkAuthError('Auth check code is needed')
class InteractiveMixin(object):
def get_user_login(self):
user_login = raw_input('VK user login: ')
return user_login.strip()
def get_user_password(self):
import getpass
user_password = getpass.getpass('VK user password: ')
return user_password
def get_access_token(self):
logger.debug('InteractiveMixin.get_access_token()')
access_token, access_token_expires_in = super(InteractiveMixin, self).get_access_token()
if not access_token:
access_token = raw_input('VK API access token: ')
access_token_expires_in = None
return access_token, access_token_expires_in
def on_captcha_is_needed(self, url):
"""
Read CAPTCHA key from shell
"""
print('Open captcha url:', url)
captcha_key = raw_input('Enter captcha key: ')
return captcha_key
def get_auth_check_code(self):
"""
Read Auth code from shell
"""
auth_check_code = raw_input('Auth check code: ')
return auth_check_code.strip()

48
src/vk/tests.py Normal file
View File

@ -0,0 +1,48 @@
# coding=utf8
import os
import sys
import time
import unittest
import vk
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
# copy to test_props.py and fill it
USER_LOGIN = '' # user email or phone number
USER_PASSWORD = '' # user password
APP_ID = '' # aka API/Client ID
from test_props import USER_LOGIN, USER_PASSWORD, APP_ID
class VkTestCase(unittest.TestCase):
def setUp(self):
auth_session = vk.AuthSession(app_id=APP_ID, user_login=USER_LOGIN, user_password=USER_PASSWORD)
access_token, _ = auth_session.get_access_token()
session = vk.Session(access_token=access_token)
self.vk_api = vk.API(session, lang='ru')
def test_get_server_time(self):
time_1 = time.time() - 1
time_2 = time_1 + 10
server_time = self.vk_api.getServerTime()
self.assertTrue(time_1 <= server_time <= time_2)
def test_get_server_time_via_token_api(self):
time_1 = time.time() - 1
time_2 = time_1 + 10
server_time = self.vk_api.getServerTime()
self.assertTrue(time_1 <= server_time <= time_2)
def test_get_profiles_via_token(self):
profiles = self.vk_api.users.get(user_id=1)
self.assertEqual(profiles[0]['last_name'], u'Дуров')
if __name__ == '__main__':
unittest.main()

157
src/vk/upload.py Normal file
View File

@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
"""
@author: Kirill Python
@contact: https://vk.com/python273
@license Apache License, Version 2.0, see LICENSE file
Copyright (C) 2015
"""
class VkUpload(object):
def __init__(self, vk):
"""
:param vk: объект VkApi
"""
self.vk = vk
# https://vk.com/dev/upload_files
def photo(self, photos, album_id,
latitude=None, longitude=None, caption=None, description=None,
group_id=None):
""" Загрузка изображений в альбом пользователя
:param photos: список путей к изображениям, либо путь к изображению
:param album_id: идентификатор альбома
:param latitude: географическая широта, заданная в градусах
(от -90 до 90)
:param longitude: географическая долгота, заданная в градусах
(от -180 до 180)
:param caption: текст описания изображения
:param description: текст описания альбома
:param group_id: идентификатор сообщества (если загрузка идет в группу)
"""
values = {'album_id': album_id}
if group_id:
values['group_id'] = group_id
# Получаем ссылку для загрузки
url = self.vk.photos.getUploadServer(values)['upload_url']
# Загружаем
photos_files = open_photos(photos)
response = self.vk.requests_session.post(url, files=photos_files).json()
close_photos(photos_files)
# Олег Илларионов:
# это не могу к сожалению просто пофиксить
if 'album_id' not in response:
response['album_id'] = response['aid']
response.update({
'latitude': latitude,
'longitude': longitude,
'caption': caption,
'description': description
})
values.update(response)
# Сохраняем фото в альбоме
response = self.vk.photos.save(values)
return response
def photo_messages(self, photos):
""" Загрузка изображений в сообщения
:param photos: список путей к изображениям, либо путь к изображению
"""
url = self.vk.method('photos.getMessagesUploadServer')
url = url['upload_url']
photos_files = open_photos(photos)
response = self.vk.http.post(url, files=photos_files)
close_photos(photos_files)
response = self.vk.method('photos.saveMessagesPhoto', response.json())
return response
def photo_wall(self, photos, user_id=None, group_id=None):
""" Загрузка изображений на стену пользователя или в группу
:param photos: список путей к изображениям, либо путь к изображению
:param user_id: идентификатор пользователя
:param group_id: идентификатор сообщества (если загрузка идет в группу)
"""
values = {}
if user_id:
values['user_id'] = user_id
elif group_id:
values['group_id'] = group_id
response = self.vk.photos.getWallUploadServer(**values)
url = response['upload_url']
print url
photos_files = open_photos(photos)
response = self.vk._session.requests_session.post(url, files=photos_files)
close_photos(photos_files)
values.update(response.json())
print values
response = self.vk.photos.saveWallPhoto(**values)
return response
def document(self, file_path, title=None, tags=None, group_id=None):
""" Загрузка документа
:param file_path: путь к документу
:param title: название документа
:param tags: метки для поиска
:param group_id: идентификатор сообщества (если загрузка идет в группу)
"""
values = {'group_id': group_id}
url = self.vk.method('docs.getUploadServer', values)['upload_url']
with open(file_path, 'rb') as file:
response = self.vk.http.post(url, files={'file': file}).json()
response.update({
'title': title,
'tags': tags
})
response = self.vk.method('docs.save', response)
return response
def open_photos(photos_paths):
if not isinstance(photos_paths, list):
photos_paths = [photos_paths]
photos = []
for x, filename in enumerate(photos_paths):
filetype = filename.split('.')[-1]
photos.append(
('file%s' % x, ('pic.' + filetype, open(filename, 'rb')))
)
print photos
return photos
def close_photos(photos):
for photo in photos:
photo[1][1].close()

73
src/vk/utils.py Normal file
View File

@ -0,0 +1,73 @@
import logging
from collections import Iterable
import re
import requests
STRING_TYPES = (str, bytes, bytearray)
logger = logging.getLogger('vk')
try:
# Python 2
from urllib import urlencode
from urlparse import urlparse, parse_qsl
except ImportError:
# Python 3
from urllib.parse import urlparse, parse_qsl, urlencode
try:
import simplejson as json
except ImportError:
import json
try:
# Python 2
raw_input = raw_input
except NameError:
# Python 3
raw_input = input
def json_iter_parse(response_text):
decoder = json.JSONDecoder(strict=False)
idx = 0
while idx < len(response_text):
obj, idx = decoder.raw_decode(response_text, idx)
yield obj
def stringify_values(dictionary):
stringified_values_dict = {}
for key, value in dictionary.items():
if isinstance(value, Iterable) and not isinstance(value, STRING_TYPES):
value = ','.join(map(str, value))
stringified_values_dict[key] = value
return stringified_values_dict
def get_url_query(url):
parsed_url = urlparse(url)
url_query = parse_qsl(parsed_url.fragment)
# login_response_url_query can have multiple key
url_query = dict(url_query)
return url_query
def get_form_action(html):
form_action = re.findall(r'<form(?= ).* action="(.+)"', html)
if form_action:
return form_action[0]
class LoggingSession(requests.Session):
def request(self, method, url, **kwargs):
logger.debug('Request: %s %s, params=%r, data=%r', method, url, kwargs.get('params'), kwargs.get('data'))
response = super(LoggingSession, self).request(method, url, **kwargs)
logger.debug('Response: %s %s', response.status_code, response.url)
return response

View File

@ -0,0 +1,5 @@
import platform
if platform.system() == "Windows":
from wxUtils import *
#elif platform.system() == "Linux":
# from gtkUtils import *

143
src/widgetUtils/gtkUtils.py Normal file
View File

@ -0,0 +1,143 @@
from gi.repository import Gtk, Gdk
from gi.repository import GObject
toolkit = "gtk"
# Code responses for GTK +3 dialogs.
# this is when an user presses OK on a dialogue.
OK = Gtk.ResponseType.OK
# This is when an user presses cancel on a dialogue.
CANCEL = Gtk.ResponseType.CANCEL
# This is when an user closes the dialogue or an id to create the close button.
CLOSE = Gtk.ResponseType.CLOSE
# The response for a "yes" Button pressed on a dialogue.
YES = Gtk.ResponseType.YES
# This is when the user presses No on a default dialogue.
NO = Gtk.ResponseType.NO
#events
# This is raised when the application must be closed.
CLOSE_EVENT = "delete-event"
# This is activated when a button is pressed.
BUTTON_PRESSED = "clicked"
# This is activated when an user enter text on an edit box.
ENTERED_TEXT = "changed"
MENU = "activate"
#KEYPRESS = wx.EVT_CHAR_HOOK
#NOTEBOOK_PAGE_CHANGED = wx.EVT_NOTEBOOK_PAGE_CHANGED
CHECKBOX = "toggled"
def exit_application():
""" Closes the current window cleanly. """
Gtk.main_quit()
def connect_event(parent, event, func, menuitem=None, *args, **kwargs):
""" Connects an event to a function.
parent Gtk.widget: The widget that will listen for the event.
event widgetUtils.event: The event that will be listened for the parent. The event should be one of the widgetUtils events.
function func: The function that will be connected to the event."""
if menuitem == None:
return getattr(parent, "connect")(event, func, *args, **kwargs)
else:
return getattr(menuitem, "connect")(event, func, *args, **kwargs)
class list(object):
def __init__(self, *columns, **listArguments):
self.columns = columns
self.list_arguments = listArguments
self.create_list()
def create_list(self):
columns = []
[columns.append(str) for i in self.columns]
self.store = Gtk.ListStore(*columns)
self.list = Gtk.TreeView(model=self.store)
renderer = Gtk.CellRendererText()
for i in range(0, len(self.columns)):
column = Gtk.TreeViewColumn(self.columns[i], renderer, text=i)
# column.set_sort_column_id(i)
self.list.append_column(column)
def insert_item(self, reversed=False, *item):
if reversed == False:
self.store.append(row=item)
else:
self.store.insert(position=0, row=item)
def get_selected(self):
tree_selection = self.list.get_selection()
(model, pathlist) = tree_selection.get_selected_rows()
return int(pathlist[0].to_string() )
def select_item(self, item):
tree_selection = self.list.get_selection()
tree_selection.select_path(item)
def remove_item(self, item):
self.store.remove(self.store.get_iter(item))
def get_count(self):
return len(self.store)
class baseDialog(Gtk.Dialog):
def __init__(self, *args, **kwargs):
super(baseDialog, self).__init__(*args, **kwargs)
self.box = self.get_content_area()
def get_response(self):
answer = self.run()
return answer
class buffer(GObject.GObject):
name = GObject.property(type=str)
def __init__(self, obj):
super(buffer, self).__init__()
self.buffer = obj
class notebook(object):
def __init__(self):
self.store = Gtk.TreeStore(buffer.__gtype__)
self.view = Gtk.TreeView()
self.view.set_model(self.store)
column = Gtk.TreeViewColumn("Buffer")
cell = Gtk.CellRendererText()
column.pack_start(cell, True)
column.set_cell_data_func(cell, self.get_buffer)
self.view.append_column(column)
def get_current_page(self):
tree_selection = self.view.get_selection()
(model, pathlist) = tree_selection.get_selected_rows()
iter = pathlist[0]
return self.store[iter][0].buffer
def get_buffer(self, column, cell, model, iter, data):
cell.set_property('text', self.store.get_value(iter, 0).name)
def match_func(self, row, name_, account):
name = name_
account = account
iter = self.store.get_iter(row.path)
if self.store[iter][0].buffer.name == name and self.store[iter][0].buffer.account == account:
return (row.path, iter)
else:
return (None, None)
def search(self, rows, name_, account):
if not rows: return None
for row in rows:
(path, iter) = self.match_func(row, name_, account)
if iter != None:
return (path, iter)
(result_path, result_iter) = self.search(row.iterchildren(), name_, account)
if result_path: return (result_path, result_iter)
return (None, None)
class mainLoopObject(object):
def run(self):
GObject.type_register(buffer)
Gtk.main()

171
src/widgetUtils/wxUtils.py Normal file
View File

@ -0,0 +1,171 @@
import wx
import paths
import languageHandler
import sys
toolkit = "wx"
### Code responses for WX dialogs.
# this is when an user presses OK on a dialogue.
OK = wx.ID_OK
# This is when an user presses cancel on a dialogue.
CANCEL = wx.ID_CANCEL
# This is when an user closes the dialogue or an id to create the close button.
CLOSE = wx.ID_CLOSE
# The response for a "yes" Button pressed on a dialogue.
YES = wx.ID_YES
# This is when the user presses No on a default dialogue.
NO = wx.ID_NO
###events
# This is raised when the application must be closed.
CLOSE_EVENT = wx.EVT_CLOSE
# This is activated when a button is pressed.
BUTTON_PRESSED = wx.EVT_BUTTON
# This is raised when a checkbox changes its status.
CHECKBOX = wx.EVT_CHECKBOX
# This is activated when an user enter text on an edit box.
ENTERED_TEXT = wx.EVT_TEXT
# This is raised when a user activates a menu.
MENU = wx.EVT_MENU
# This is raised when a user presses any key in the control.
KEYPRESS = wx.EVT_CHAR_HOOK
# This is raised when a user releases a key in the control.
KEYUP = wx.EVT_KEY_UP
# This happens when a notebook tab is changed, It is used in Treebooks too.
NOTEBOOK_PAGE_CHANGED = wx.EVT_TREEBOOK_PAGE_CHANGED
# This happens when a radiobutton group changes its status.
RADIOBUTTON = wx.EVT_RADIOBUTTON
# Taskbar mouse clicks.
TASKBAR_RIGHT_CLICK = wx.EVT_TASKBAR_RIGHT_DOWN
TASKBAR_LEFT_CLICK = wx.EVT_TASKBAR_LEFT_DOWN
def exit_application():
""" Closes the current window cleanly. """
wx.GetApp().ExitMainLoop()
def connect_event(parent, event, func, menuitem=None, *args, **kwargs):
""" Connects an event to a function.
parent wx.window: The widget that will listen for the event.
event widgetUtils.event: The event that will be listened for the parent. The event should be one of the widgetUtils events.
function func: The function that will be connected to the event."""
if menuitem == None:
return getattr(parent, "Bind")(event, func, *args, **kwargs)
else:
return getattr(parent, "Bind")(event, func, menuitem, *args, **kwargs)
def connectExitFunction(exitFunction):
""" This connect the events in WX when an user is turning off the machine."""
wx.GetApp().Bind(wx.EVT_QUERY_END_SESSION, exitFunction)
wx.GetApp().Bind(wx.EVT_END_SESSION, exitFunction)
class BaseDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
super(BaseDialog, self).__init__(*args, **kwargs)
def get_response(self):
return self.ShowModal()
def get(self, control):
if hasattr(self, control):
control = getattr(self, control)
if hasattr(control, "GetValue"): return getattr(control, "GetValue")()
elif hasattr(control, "GetLabel"): return getattr(control, "GetLabel")()
else: return -1
else: return 0
def set(self, control, text):
if hasattr(self, control):
control = getattr(self, control)
if hasattr(control, "SetValue"): return getattr(control, "SetValue")(text)
elif hasattr(control, "SetLabel"): return getattr(control, "SetLabel")(text)
elif hasattr(control, "ChangeValue"): return getattr(control, "ChangeValue")(text)
else: return -1
else: return 0
def destroy(self):
self.Destroy()
def set_title(self, title):
self.SetTitle(title)
def get_title(self):
return self.GetTitle()
class mainLoopObject(wx.App):
def __init__(self):
self.app = wx.App()
self.lc = wx.Locale()
lang=languageHandler.getLanguage()
wxLang=self.lc.FindLanguageInfo(lang)
if not wxLang and '_' in lang:
wxLang=self.lc.FindLanguageInfo(lang.split('_')[0])
if hasattr(sys,'frozen'):
self.lc.AddCatalogLookupPathPrefix(paths.app_path("locales"))
if wxLang:
self.lc.Init(wxLang.Language)
def run(self):
self.app.MainLoop()
class list(object):
def __init__(self, parent, *columns, **listArguments):
self.columns = columns
self.listArguments = listArguments
self.create_list(parent)
def set_windows_size(self, column, characters_max):
self.list.SetColumnWidth(column, characters_max*2)
def set_size(self):
self.list.SetSize((self.list.GetBestSize()[0], 728))
def create_list(self, parent):
self.list = wx.ListCtrl(parent, -1, **self.listArguments)
for i in xrange(0, len(self.columns)):
self.list.InsertColumn(i, u"%s" % (self.columns[i]))
def insert_item(self, reversed, *item):
""" Inserts an item on the list."""
if reversed == False: items = self.list.GetItemCount()
else: items = 0
self.list.InsertStringItem(items, item[0])
for i in xrange(1, len(self.columns)):
self.list.SetStringItem(items, i, item[i])
def remove_item(self, pos):
""" Deletes an item from the list."""
if pos > 0: self.list.Focus(pos-1)
self.list.DeleteItem(pos)
def clear(self):
self.list.DeleteAllItems()
def get_selected(self):
return self.list.GetFocusedItem()
def select_item(self, pos):
self.list.Focus(pos)
def get_count(self):
selected = self.list.GetItemCount()
if selected == -1:
return 0
else:
return selected

0
src/wxUI/__init__.py Normal file
View File

Some files were not shown because too many files have changed in this diff Show More