Added some code for starting
This commit is contained in:
parent
364472da5c
commit
0266100d66
29
src/accessible_output2/__init__.py
Normal file
29
src/accessible_output2/__init__.py
Normal 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)]
|
BIN
src/accessible_output2/lib/PCTKUSR.dll
Normal file
BIN
src/accessible_output2/lib/PCTKUSR.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/PCTKUSR64.dll
Normal file
BIN
src/accessible_output2/lib/PCTKUSR64.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/SAAPI32.dll
Normal file
BIN
src/accessible_output2/lib/SAAPI32.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/dolapi.dll
Normal file
BIN
src/accessible_output2/lib/dolapi.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/jfwapi.dll
Normal file
BIN
src/accessible_output2/lib/jfwapi.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/nvdaControllerClient32.dll
Normal file
BIN
src/accessible_output2/lib/nvdaControllerClient32.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/nvdaControllerClient64.dll
Normal file
BIN
src/accessible_output2/lib/nvdaControllerClient64.dll
Normal file
Binary file not shown.
14
src/accessible_output2/outputs/__init__.py
Normal file
14
src/accessible_output2/outputs/__init__.py
Normal 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
|
42
src/accessible_output2/outputs/auto.py
Normal file
42
src/accessible_output2/outputs/auto.py
Normal 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)
|
31
src/accessible_output2/outputs/base.py
Normal file
31
src/accessible_output2/outputs/base.py
Normal 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)
|
||||||
|
|
||||||
|
|
27
src/accessible_output2/outputs/dolphin.py
Normal file
27
src/accessible_output2/outputs/dolphin.py
Normal 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
|
33
src/accessible_output2/outputs/jaws.py
Normal file
33
src/accessible_output2/outputs/jaws.py
Normal 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
|
31
src/accessible_output2/outputs/nvda.py
Normal file
31
src/accessible_output2/outputs/nvda.py
Normal 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
|
19
src/accessible_output2/outputs/pc_talker.py
Normal file
19
src/accessible_output2/outputs/pc_talker.py
Normal 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
|
29
src/accessible_output2/outputs/speechDispatcher.py
Normal file
29
src/accessible_output2/outputs/speechDispatcher.py
Normal 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
|
18
src/accessible_output2/outputs/speechd/__init__.py
Normal file
18
src/accessible_output2/outputs/speechd/__init__.py
Normal 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 *
|
||||||
|
|
1125
src/accessible_output2/outputs/speechd/client.py
Normal file
1125
src/accessible_output2/outputs/speechd/client.py
Normal file
File diff suppressed because it is too large
Load Diff
1
src/accessible_output2/outputs/speechd/paths.py
Normal file
1
src/accessible_output2/outputs/speechd/paths.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
SPD_SPAWN_CMD = "/usr/bin/speech-dispatcher"
|
23
src/accessible_output2/outputs/system_access.py
Normal file
23
src/accessible_output2/outputs/system_access.py
Normal 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
|
23
src/accessible_output2/outputs/voiceover.py
Normal file
23
src/accessible_output2/outputs/voiceover.py
Normal 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
|
32
src/accessible_output2/outputs/window_eyes.py
Normal file
32
src/accessible_output2/outputs/window_eyes.py
Normal 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
|
4
src/app-configuration.defaults
Normal file
4
src/app-configuration.defaults
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[app-settings]
|
||||||
|
username = string(default="")
|
||||||
|
password = string(default="")
|
||||||
|
language = string(default="system")
|
16
src/config.py
Normal file
16
src/config.py
Normal 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
73
src/config_utils.py
Normal 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
|
0
src/controller/__init__.py
Normal file
0
src/controller/__init__.py
Normal file
65
src/controller/buffers.py
Normal file
65
src/controller/buffers.py
Normal 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)
|
63
src/controller/mainController.py
Normal file
63
src/controller/mainController.py
Normal 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
118
src/controller/messages.py
Normal 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
189
src/controller/posts.py
Normal 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()
|
||||||
|
|
0
src/extra/AudioUploader/__init__.py
Normal file
0
src/extra/AudioUploader/__init__.py
Normal file
186
src/extra/AudioUploader/audioUploader.py
Normal file
186
src/extra/AudioUploader/audioUploader.py
Normal 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()
|
||||||
|
|
106
src/extra/AudioUploader/transfer.py
Normal file
106
src/extra/AudioUploader/transfer.py
Normal 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()
|
42
src/extra/AudioUploader/utils.py
Normal file
42
src/extra/AudioUploader/utils.py
Normal 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
|
73
src/extra/AudioUploader/wx_transfer_dialogs.py
Normal file
73
src/extra/AudioUploader/wx_transfer_dialogs.py
Normal 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)
|
78
src/extra/AudioUploader/wx_ui.py
Normal file
78
src/extra/AudioUploader/wx_ui.py
Normal 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()
|
1
src/extra/SoundsTutorial/__init__.py
Normal file
1
src/extra/SoundsTutorial/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from soundsTutorial import soundsTutorial
|
25
src/extra/SoundsTutorial/gtk_ui.py
Normal file
25
src/extra/SoundsTutorial/gtk_ui.py
Normal 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()
|
11
src/extra/SoundsTutorial/reverse_sort.py
Normal file
11
src/extra/SoundsTutorial/reverse_sort.py
Normal 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)))
|
34
src/extra/SoundsTutorial/soundsTutorial.py
Normal file
34
src/extra/SoundsTutorial/soundsTutorial.py
Normal 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()],))
|
27
src/extra/SoundsTutorial/soundsTutorial_constants.py
Normal file
27
src/extra/SoundsTutorial/soundsTutorial_constants.py
Normal 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."))])
|
29
src/extra/SoundsTutorial/wx_ui.py
Normal file
29
src/extra/SoundsTutorial/wx_ui.py
Normal 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()
|
4
src/extra/SpellChecker/__init__.py
Normal file
4
src/extra/SpellChecker/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import spellchecker
|
||||||
|
import platform
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
from wx_ui import *
|
70
src/extra/SpellChecker/spellchecker.py
Normal file
70
src/extra/SpellChecker/spellchecker.py
Normal 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()
|
15
src/extra/SpellChecker/twitterFilter.py
Normal file
15
src/extra/SpellChecker/twitterFilter.py
Normal 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
|
79
src/extra/SpellChecker/wx_ui.py
Normal file
79
src/extra/SpellChecker/wx_ui.py
Normal 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
0
src/extra/__init__.py
Normal file
2
src/extra/autocompletionUsers/__init__.py
Normal file
2
src/extra/autocompletionUsers/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import completion, settings
|
47
src/extra/autocompletionUsers/completion.py
Normal file
47
src/extra/autocompletionUsers/completion.py
Normal 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"))
|
43
src/extra/autocompletionUsers/manage.py
Normal file
43
src/extra/autocompletionUsers/manage.py
Normal 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()
|
59
src/extra/autocompletionUsers/settings.py
Normal file
59
src/extra/autocompletionUsers/settings.py
Normal 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)
|
52
src/extra/autocompletionUsers/storage.py
Normal file
52
src/extra/autocompletionUsers/storage.py
Normal 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()
|
43
src/extra/autocompletionUsers/wx_manage.py
Normal file
43
src/extra/autocompletionUsers/wx_manage.py
Normal 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()
|
25
src/extra/autocompletionUsers/wx_menu.py
Normal file
25
src/extra/autocompletionUsers/wx_menu.py
Normal 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()
|
27
src/extra/autocompletionUsers/wx_settings.py
Normal file
27
src/extra/autocompletionUsers/wx_settings.py
Normal 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()
|
6
src/extra/translator/__init__.py
Normal file
6
src/extra/translator/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import translator
|
||||||
|
import platform
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
import wx_ui as gui
|
||||||
|
|
251
src/extra/translator/translator.py
Normal file
251
src/extra/translator/translator.py
Normal 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))
|
45
src/extra/translator/wx_ui.py
Normal file
45
src/extra/translator/wx_ui.py
Normal 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
13
src/keys/__init__.py
Normal 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"
|
7
src/libloader/__init__.py
Normal file
7
src/libloader/__init__.py
Normal 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
21
src/libloader/com.py
Normal 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
|
||||||
|
|
56
src/libloader/libloader.py
Normal file
56
src/libloader/libloader.py
Normal 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
37
src/main.py
Normal 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
0
src/mysc/__init__.py
Normal file
17
src/mysc/localization.py
Normal file
17
src/mysc/localization.py
Normal 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"
|
||||||
|
|
36
src/mysc/repeating_timer.py
Normal file
36
src/mysc/repeating_timer.py
Normal 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
11
src/mysc/restart.py
Normal 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
14
src/mysc/thread_utils.py
Normal 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
24
src/output.py
Normal 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
67
src/paths.py
Normal 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")
|
0
src/platform_utils/__init__.py
Normal file
0
src/platform_utils/__init__.py
Normal file
0
src/platform_utils/autostart/__init__.py
Normal file
0
src/platform_utils/autostart/__init__.py
Normal file
41
src/platform_utils/autostart/windows.py
Normal file
41
src/platform_utils/autostart/windows.py
Normal 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))
|
16
src/platform_utils/blackhole.py
Normal file
16
src/platform_utils/blackhole.py
Normal 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
|
||||||
|
|
51
src/platform_utils/libloader.py
Normal file
51
src/platform_utils/libloader.py
Normal 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
114
src/platform_utils/paths.py
Normal 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'
|
27
src/platform_utils/process.py
Normal file
27
src/platform_utils/process.py
Normal 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)
|
0
src/platform_utils/shell_integration/__init__.py
Normal file
0
src/platform_utils/shell_integration/__init__.py
Normal file
10
src/platform_utils/shell_integration/windows.py
Normal file
10
src/platform_utils/shell_integration/windows.py
Normal 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)
|
9
src/platform_utils/web_browser.py
Normal file
9
src/platform_utils/web_browser.py
Normal 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
6
src/session.defaults
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[vk]
|
||||||
|
user = string(default="")
|
||||||
|
password = string(default="")
|
||||||
|
token = string(default="")
|
||||||
|
[general]
|
||||||
|
reverse_timelines = boolean(default=False)
|
0
src/sessionmanager/__init__.py
Normal file
0
src/sessionmanager/__init__.py
Normal file
46
src/sessionmanager/config_utils.py
Normal file
46
src/sessionmanager/config_utils.py
Normal 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)
|
226
src/sessionmanager/session.py
Normal file
226
src/sessionmanager/session.py
Normal 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"]
|
84
src/sessionmanager/sessionManager.py
Normal file
84
src/sessionmanager/sessionManager.py
Normal 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()
|
17
src/sessionmanager/vkSessionHandler.py
Normal file
17
src/sessionmanager/vkSessionHandler.py
Normal 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)
|
92
src/sessionmanager/wxUI.py
Normal file
92
src/sessionmanager/wxUI.py
Normal 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
27
src/utils.py
Normal 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
10
src/vk/__init__.py
Normal 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
177
src/vk/api.py
Normal 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
30
src/vk/exceptions.py
Normal 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
26
src/vk/logs.py
Normal 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
215
src/vk/mixins.py
Normal 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
48
src/vk/tests.py
Normal 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
157
src/vk/upload.py
Normal 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
73
src/vk/utils.py
Normal 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
|
5
src/widgetUtils/__init__.py
Normal file
5
src/widgetUtils/__init__.py
Normal 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
143
src/widgetUtils/gtkUtils.py
Normal 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
171
src/widgetUtils/wxUtils.py
Normal 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
0
src/wxUI/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user