This commit is contained in:
Jesús Pavón Abián
2026-01-11 20:13:56 +01:00
parent 9d9d86160d
commit 932e44a9c9
391 changed files with 120828 additions and 1090 deletions

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
#-*- coding: utf-8 -*-
from . 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.")),
("image", _("Tweet contains one or more images")),
("limit", _(u"Boundary reached.")),
("list_tweet", _(u"List updated.")),
("max_length", _(u"Too many characters.")),
("mention_received", _(u"Mention received.")),
("new_event", _(u"New event.")),
("ready", _(u"{0} is ready.").format(application.name,)),
("reply_send", _(u"Mention sent.")),
("retweet_send", _(u"Tweet retweeted.")),
("search_updated", _(u"Search buffer updated.")),
("tweet_received", _(u"Tweet received.")),
("tweet_send", _(u"Tweet sent.")),
("trends_updated", _(u"Trending topics buffer updated.")),
("tweet_timeline", _(u"New tweet in user timeline buffer.")),
("update_followers", _(u"New follower.")),
("volume_changed", _(u"Volume changed."))])

View File

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

View File

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

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
import os
import logging
from . import wx_ui
import widgetUtils
import output
import config
import languageHandler
import enchant
import paths
from . import twitterFilter
from enchant.checker import SpellChecker
from enchant.errors import DictNotFoundError
from enchant import tokenize
log = logging.getLogger("extra.SpellChecker.spellChecker")
class spellChecker(object):
def __init__(self, text, dictionary):
super(spellChecker, self).__init__()
# Set Dictionary path if not set in a previous call to this method.
# Dictionary path will be located in user config, see https://github.com/manuelcortez/twblue/issues/208
# dict_path = enchant.get_param("enchant.myspell.dictionary.path")
# if dict_path == None:
# enchant.set_param("enchant.myspell.dictionary.path", os.path.join(paths.config_path(), "dicts"))
# log.debug("Dictionary path set to %s" % (os.path.join(paths.config_path(), "dicts"),))
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.dict = enchant.DictWithPWL(languageHandler.curLang[:2], os.path.join(paths.config_path(), "wordlist.dict"))
else:
log.debug("Using language: %s" % (languageHandler.getLanguage(),))
self.dict = enchant.DictWithPWL(languageHandler.getLanguage()[:2], os.path.join(paths.config_path(), "wordlist.dict"))
except DictNotFoundError:
log.exception("Dictionary for language %s not found." % (dictionary,))
wx_ui.dict_not_found_error()
self.active = False
self.checker = SpellChecker(self.dict, filters=[twitterFilter.TwitterFilter, tokenize.EmailFilter, tokenize.URLFilter])
self.checker.set_text(text)
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)
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add)
self.check()
self.dialog.get_response()
self.fixed_text = self.checker.get_text()
def check(self):
try:
next(self.checker)
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()
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()
def add(self, ev):
self.checker.add()
self.check()

View File

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

View File

@@ -0,0 +1,82 @@
# -*- 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"I&gnore all"))
self.replace = wx.Button(panel, -1, _(u"&Replace"))
self.replaceAll = wx.Button(panel, -1, _(u"R&eplace all"))
self.add = wx.Button(panel, -1, _(u"&Add to personal dictionary"))
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(self.add, 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()

View File

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
""" Autocompletion users for TWBlue. This package contains all needed code to support this feature, including automatic addition of users, management and code to show the autocompletion menu when an user is composing a post. """

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
""" Module to display the user autocompletion menu in post dialogs. """
import output
from . import storage
from . import wx_menu
class autocompletionUsers(object):
def __init__(self, window, session_id):
""" Class constructor. Displays a menu with users matching the specified pattern for autocompletion.
:param window: A wx control where the menu should be displayed. Normally this is going to be the wx.TextCtrl indicating the tweet's text or direct message recipient.
:type window: wx.Dialog
:param session_id: Session ID which calls this class. We will load the users database from this session.
:type session_id: str.
"""
super(autocompletionUsers, self).__init__()
self.window = window
self.db = storage.storage(session_id)
def show_menu(self, mode="mastodon"):
""" displays a menu with possible users matching the specified pattern.
this menu can be displayed in dialogs where an username is expected. For Mastodon's post dialogs, the string should start with an at symbol (@), otherwise it won't match the pattern.
Of course, users must be already loaded in database before attempting this.
If no users are found, an error message will be spoken.
:param mode: this controls how the dialog will behave. Possible values are 'mastodon' and 'free'. In mastodon mode, the matching pattern will be @user (@ is required), while in 'free' mode the matching pattern will be anything written in the text control.
:type mode: str
"""
if mode == "mastodon":
position = self.window.text.GetInsertionPoint()
text = self.window.text.GetValue()
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.PopupMenu(menu, self.window.text.GetPosition())
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 == "free":
text = self.window.cb.GetValue()
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.PopupMenu(menu, self.window.cb.GetPosition())
menu.destroy()
else:
output.speak(_(u"There are no results in your users database"))

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
""" Management of users in the local database for autocompletion. """
import time
import widgetUtils
from wxUI import commonMessageDialogs
from . import storage, wx_manage
from .mastodon import scan as mastodon
class autocompletionManage(object):
def __init__(self, session):
""" class constructor. Manages everything related to user autocompletion.
:param session: Sessiom where the autocompletion management has been requested.
:type session: sessions.base.Session.
"""
super(autocompletionManage, self).__init__()
self.session = session
# Instantiate database so we can perform modifications on it.
self.database = storage.storage(self.session.session_id)
def show_settings(self):
""" display user management dialog and connect events associated to it. """
self.dialog = wx_manage.autocompletionManageDialog()
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):
""" update users list in management dialog. This function is normallhy used after we modify the database in any way, so we can reload all users in the autocompletion user management list. """
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):
""" Add a new username to the autocompletion database. """
usr = self.dialog.get_user()
if usr == False:
return
user_added = False
if self.session.type == "mastodon":
user_added = mastodon.add_user(session=self.session, database=self.database, user=usr)
if user_added == False:
self.dialog.show_invalid_user_error()
return
self.update_list()
def remove_user(self, *args, **kwargs):
""" Remove focused user from the autocompletion database. """
if commonMessageDialogs.delete_user_from_db() == widgetUtils.YES:
item = self.dialog.users.get_selected()
user = self.users[item]
self.database.remove_user(user[0])
self.update_list()

View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
""" Scanning code for autocompletion feature on TWBlue. This module can retrieve user objects from the selected Mastodon account automatically. """
import time
import wx
import widgetUtils
import output
from pubsub import pub
from . import wx_scan
from extra.autocompletionUsers import manage, storage
class autocompletionScan(object):
def __init__(self, config, buffer, window):
""" Class constructor. This class will take care of scanning the selected Mastodon account to populate the database with users automatically upon request.
:param config: Config for the session that will be scanned in search for users.
:type config: dict
:param buffer: home buffer for the focused session.
:type buffer: controller.buffers.mastodon.base.baseBuffer
:param window: Main Window of TWBlue.
:type window:wx.Frame
"""
super(autocompletionScan, self).__init__()
self.config = config
self.buffer = buffer
self.window = window
def show_dialog(self):
""" displays a dialog to confirm which buffers should be scanned (followers or following users). """
self.dialog = wx_scan.autocompletionScanDialog()
self.dialog.set("friends", self.config["mysc"]["save_friends_in_autocompletion_db"])
self.dialog.set("followers", self.config["mysc"]["save_followers_in_autocompletion_db"])
if self.dialog.get_response() == widgetUtils.OK:
confirmation = wx_scan.confirm()
return confirmation
def prepare_progress_dialog(self):
self.progress_dialog = wx_scan.autocompletionScanProgressDialog()
# connect method to update progress dialog
pub.subscribe(self.on_update_progress, "on-update-progress")
self.progress_dialog.Show()
def on_update_progress(self):
wx.CallAfter(self.progress_dialog.progress_bar.Pulse)
def scan(self):
""" Attempts to add all users selected by current user to the autocomplete database. """
self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends")
self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers")
output.speak(_("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)
percent = 0
users = []
if self.dialog.get("friends") == True:
first_page = self.buffer.session.api.account_following(id=self.buffer.session.db["user_id"], limit=80)
pub.sendMessage("on-update-progress")
if first_page != None:
for user in first_page:
users.append(user)
next_page = first_page
while next_page != None:
next_page = self.buffer.session.api.fetch_next(next_page)
pub.sendMessage("on-update-progress")
if next_page == None:
break
for user in next_page:
users.append(user)
# same step, but for followers.
if self.dialog.get("followers") == True:
first_page = self.buffer.session.api.account_followers(id=self.buffer.session.db["user_id"], limit=80)
pub.sendMessage("on-update-progress")
if first_page != None:
for user in first_page:
if user not in users:
users.append(user)
next_page = first_page
while next_page != None:
next_page = self.buffer.session.api.fetch_next(next_page)
pub.sendMessage("on-update-progress")
if next_page == None:
break
for user in next_page:
if user not in users:
users.append(user)
# except TweepyException:
# wx.CallAfter(wx_scan.show_error)
# return self.done()
for user in users:
name = user.display_name if user.display_name != None and user.display_name != "" else user.username
database.set_user(user.acct, name, 1)
wx.CallAfter(wx_scan .show_success, len(users))
self.done()
def done(self):
wx.CallAfter(self.progress_dialog.Destroy)
wx.CallAfter(self.dialog.Destroy)
pub.unsubscribe(self.on_update_progress, "on-update-progress")
def add_user(session, database, user):
""" Adds an user to the database. """
user = session.api.account_lookup(user)
if user != None:
name = user.display_name if user.display_name != None and user.display_name != "" else user.username
database.set_user(user.acct, name, 1)

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
import application
class autocompletionScanDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionScanDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.followers = wx.CheckBox(panel, -1, _("Add &followers to database"))
self.friends = wx.CheckBox(panel, -1, _("Add f&ollowing to database"))
sizer.Add(self.followers, 0, wx.ALL, 5)
sizer.Add(self.friends, 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())
class autocompletionScanProgressDialog(widgetUtils.BaseDialog):
def __init__(self, *args, **kwargs):
super(autocompletionScanProgressDialog, self).__init__(parent=None, id=wx.ID_ANY, title=_("Updating autocompletion database"), *args, **kwargs)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.progress_bar = wx.Gauge(parent=panel)
sizer.Add(self.progress_bar)
panel.SetSizerAndFit(sizer)
def confirm():
with wx.MessageDialog(None, _("This process will retrieve the users you selected from your Mastodon account, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. If this process ends with no error, you will be redirected back to the account settings dialog. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result:
if result.ShowModal() == wx.ID_YES:
return True
return False
def show_success(users):
with wx.MessageDialog(None, _("TWBlue has imported {} users successfully.").format(users), _("Done")) as dlg:
dlg.ShowModal()
def show_error():
with wx.MessageDialog(None, _("Error adding users from Mastodon. Please try again in about 15 minutes."), _("Error"), style=wx.ICON_ERROR) as dlg:
dlg.ShowModal()

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import os, sqlite3, paths
class storage(object):
def __init__(self, session_id):
self.connection = sqlite3.connect(os.path.join(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 UPPER(user) LIKE :term OR UPPER(name) LIKE :term""", {"term": "%{}%".format(term.upper())})
return self.cursor.fetchall()
def set_user(self, screen_name, user_name, from_a_buffer):
self.cursor.execute("""insert or ignore into users values(?, ?, ?)""", (screen_name, user_name, from_a_buffer))
self.connection.commit()
def remove_user(self, user):
self.cursor.execute("""DELETE FROM users WHERE user = ?""", (user,))
self.connection.commit()
return self.cursor.fetchone()
def remove_by_buffer(self, bufferType):
""" Removes all users saved on a buffer. BufferType is 0 for no buffer, 1 for friends and 2 for followers"""
self.cursor.execute("""DELETE FROM users WHERE from_a_buffer = ?""", (bufferType,))
self.connection.commit()
return self.cursor.fetchone()
def create_table(self):
self.cursor.execute("""
create table users(
user TEXT UNIQUE,
name TEXT,
from_a_buffer INTEGER
)""")
def __del__(self):
self.cursor.close()
self.connection.close()

View File

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

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import wx
class menu(wx.Menu):
def __init__(self, window, pattern, mode):
super(menu, self).__init__()
self.window = window
self.pattern = pattern
self.mode = mode
def append_options(self, options):
for i in options:
item = wx.MenuItem(self, wx.ID_ANY, "%s (@%s)" % (i[1], i[0]))
self.Append(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 == "mastodon":
self.window.ChangeValue(self.window.GetValue().replace("@"+self.pattern, "@"+text+" "))
elif self.mode == "free":
self.window.SetValue(self.window.GetValue().replace(self.pattern, text))
self.window.SetInsertionPointEnd()
def destroy(self):
self.Destroy()

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
""" original module taken and modified from https://github.com/ctoth/cloudOCR"""
from __future__ import unicode_literals
from builtins import object
import requests
translatable_langs = [_(u"Detect automatically"), _(u"Danish"), _(u"Dutch"), _(u"English"), _(u"Finnish"), _(u"French"), _(u"German"), _(u"Hungarian"), _(u"Korean"), _(u"Italian"), _(u"Japanese"), _(u"Polish"), _(u"Portuguese"), _(u"Russian"), _(u"Spanish"), _(u"Turkish")]
short_langs = ["", "da", "du", "en", "fi", "fr", "de", "hu", "ko", "it", "ja", "pl", "pt", "ru", "es", "tr"]
OcrLangs = ["", "dan", "dut", "eng", "fin", "fre", "ger", "hun", "kor", "ita", "jpn", "pol", "por", "rus", "spa", "tur"]
class APIError(Exception):
pass
class OCRSpaceAPI(object):
def __init__(self, key="4e72ae996f88957", url='https://api.ocr.space/parse/image'):
self.key = key
self.url = url
def OCR_URL(self, url, overlay=False, lang=None):
payload = {
'url': url,
'isOverlayRequired': overlay,
'apikey': self.key,
}
if lang != None:
payload.update(language=lang)
r = requests.post(self.url, data=payload)
result = r.json()['ParsedResults'][0]
if result['ErrorMessage']:
raise APIError(result['ErrorMessage'])
return result
def OCR_file(self, fileobj, overlay=False):
payload = {
'isOverlayRequired': overlay,
'apikey': self.key,
'lang': 'es',
}
r = requests.post(self.url, data=payload, files={'file': fileobj})
results = r.json()['ParsedResults']
if results[0]['ErrorMessage']:
raise APIError(results[0]['ErrorMessage'])
return results

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
# -*- coding: utf-8 -*-
from . import OCRSpace

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from .translator import TranslatorController

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import config
from deepl import Translator
def translate(text: str, target_language: str) -> str:
key = config.app["translator"]["deepl_api_key"]
t = Translator(key)
return t.translate_text(text, target_lang=target_language).text
def languages():
key = config.app["translator"]["deepl_api_key"]
t = Translator(key)
langs = t.get_target_languages()
return langs

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
""" Modified Libretranslatepy module which adds an user agent for making requests against more instances. """
import json
from typing import Any, Dict
from urllib import request, parse
from libretranslatepy import LibreTranslateAPI
class CustomLibreTranslateAPI(LibreTranslateAPI):
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
def _create_request(self, url: str, method: str, data: Dict[str, str]) -> request.Request:
url_params = parse.urlencode(data)
req = request.Request(url, method=method, data=url_params.encode())
req.add_header("User-Agent", self.USER_AGENT)
return req
def translate(self, q: str, source: str = "en", target: str = "es", timeout: int | None = None) -> Any:
url = self.url + "translate"
params: Dict[str, str] = {"q": q, "source": source, "target": target}
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="POST", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)["translatedText"]
def detect(self, q: str, timeout: int | None = None) -> Any:
url = self.url + "detect"
params: Dict[str, str] = {"q": q}
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="POST", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)
def languages(self, timeout: int | None = None) -> Any:
url = self.url + "languages"
params: Dict[str, str] = dict()
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="GET", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
import logging
import threading
import wx
import config
from pubsub import pub
from . engines import libre_translate, deep_l
from .wx_ui import translateDialog
log = logging.getLogger("extras.translator")
class TranslatorController(object):
def __init__(self, text):
super(TranslatorController, self).__init__()
self.text = text
self.languages = []
self.response = False
self.dialog = translateDialog()
pub.subscribe(self.on_engine_changed, "translator.engine_changed")
if config.app["translator"]["engine"] == "LibreTranslate":
self.dialog.engine_select.SetSelection(0)
elif config.app["translator"]["engine"] == "DeepL":
self.dialog.engine_select.SetSelection(1)
threading.Thread(target=self.load_languages).start()
if self.dialog.ShowModal() == wx.ID_OK:
self.response = True
for k in self.language_dict:
if self.language_dict[k] == self.dialog.dest_lang.GetStringSelection():
self.target_language= k
pub.unsubscribe(self.on_engine_changed, "translator.engine_changed")
def load_languages(self):
self.language_dict = self.get_languages()
self.languages = [self.language_dict[k] for k in self.language_dict]
self.dialog.set_languages(self.languages)
def on_engine_changed(self, engine):
config.app["translator"]["engine"] = engine
config.app.write()
threading.Thread(target=self.load_languages).start()
def translate(self):
log.debug("Received translation request for language %s, text=%s" % (self.target_language, self.text))
if config.app["translator"].get("engine") == "LibreTranslate":
translator = libre_translate.CustomLibreTranslateAPI(config.app["translator"]["lt_api_url"], config.app["translator"]["lt_api_key"])
vars = dict(q=self.text, target=self.target_language)
return translator.translate(**vars)
elif config.app["translator"]["engine"] == "DeepL" and config.app["translator"]["deepl_api_key"] != "":
return deep_l.translate(text=self.text, target_language=self.target_language)
def get_languages(self):
languages = {}
if config.app["translator"].get("engine") == "LibreTranslate":
translator = libre_translate.CustomLibreTranslateAPI(config.app["translator"]["lt_api_url"], config.app["translator"]["lt_api_key"])
languages = {l.get("code"): l.get("name") for l in translator.languages()}
elif config.app["translator"]["engine"] == "DeepL" and config.app["translator"]["deepl_api_key"] != "":
languages = {language.code: language.name for language in deep_l.languages()}
return dict(sorted(languages.items(), key=lambda x: x[1]))

View File

@@ -0,0 +1,56 @@
# -*- 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
from pubsub import pub
from wxUI.dialogs import baseDialog
class translateDialog(baseDialog.BaseWXDialog):
def __init__(self):
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
self.engines = ["LibreTranslate", "DeepL"]
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
staticEngine = wx.StaticText(panel, -1, _(u"Translation engine"))
self.engine_select = wx.ComboBox(panel, -1, choices=self.engines, style=wx.CB_READONLY)
self.engine_select.Bind(wx.EVT_COMBOBOX, lambda event: pub.sendMessage("translator.engine_changed", engine=self.engine_select.GetValue()))
staticDest = wx.StaticText(panel, -1, _(u"Target language"))
self.dest_lang = wx.ComboBox(panel, -1, style = wx.CB_READONLY)
self.dest_lang.SetFocus()
self.dest_lang.SetSelection(0)
engineSizer = wx.BoxSizer(wx.HORIZONTAL)
engineSizer.Add(staticEngine)
engineSizer.Add(self.engine_select)
listSizer = wx.BoxSizer(wx.HORIZONTAL)
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)
sizer.Add(engineSizer, 0, wx.EXPAND | wx.ALL, 5)
sizer.Add(listSizer, 0, wx.EXPAND | wx.ALL, 5)
sizer.Add(ok, 0, wx.ALIGN_CENTER | wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
panel.SetSizer(sizer)
def set_languages(self, languages):
wx.CallAfter(self.dest_lang.SetItems, languages)
def get(self, control):
return getattr(self, control).GetSelection()