diff --git a/torchat/doc/howto_second_instance.html b/torchat/doc/howto_second_instance.html index c26ad633..1601fd4e 100644 --- a/torchat/doc/howto_second_instance.html +++ b/torchat/doc/howto_second_instance.html @@ -11,6 +11,16 @@
+ + For torchat_py versions above 0.9.9.553 + this works out of box. + To start second account, add account's name to command line. + If you run portable torchat (Windows case), copy distribution + to another dir. Make sure not to copy Tor/hidden_service/ directory. + +
+This howto will not work with versions below 0.9.9.260
diff --git a/torchat/src/Tor/tor.sh b/torchat/src/Tor/tor.sh index 5ea33413..5b8dba01 100755 --- a/torchat/src/Tor/tor.sh +++ b/torchat/src/Tor/tor.sh @@ -3,5 +3,5 @@ trap 'kill -15 `cat tor.pid`' 15 export PATH=$PATH:/usr/sbin -tor -f torrc.txt --PidFile tor.pid & +tor -f torrc.temp.txt --PidFile tor.pid & wait diff --git a/torchat/src/config.py b/torchat/src/config.py index be158105..5d143e77 100644 --- a/torchat/src/config.py +++ b/torchat/src/config.py @@ -21,6 +21,7 @@ import inspect import translations import shutil +import json def isWindows(): return sys.platform.startswith("win") @@ -29,15 +30,17 @@ def isWindows(): import ctypes config_defaults = { + ("client", "tor_config") : "tor_portable", ("tor", "tor_server") : "127.0.0.1", ("tor", "tor_server_socks_port") : 9050, ("tor", "tor_server_control_port") : 9051, ("tor_portable", "tor_server") : "127.0.0.1", - ("tor_portable", "tor_server_socks_port") : 11109, + ("tor_portable", "tor_server_socks_port") : 0, ("tor_portable", "tor_server_control_port") : 11119, ("client", "own_hostname") : "0000000000000000", ("client", "listen_interface") : "127.0.0.1", - ("client", "listen_port") : 11009, + ("client", "listen_port") : 0, + ("client", "buddy-list") : '[]', ("logging", "log_file") : "", ("logging", "log_level") : 0, ("files", "temp_files_in_data_dir") : 1, @@ -66,6 +69,7 @@ def isWindows(): ("branding", "support_name") : "Bernd, author of TorChat", ("profile", "name") : "", ("profile", "text") : "", + ("plugin", "enabled_plugins") : '', } LOCALE_ENC = locale.getpreferredencoding() @@ -493,4 +497,26 @@ def main(): #now switch to the configured translation importLanguage() + if not json.loads(get('client', 'buddy-list')): + # backward compatibility with buddy-list.txt + filename = os.path.join(getDataDir(), "buddy-list.txt") + + if os.path.exists(filename): + f = open(filename) + buddy_list = [] + for line in f: + line = line.rstrip().decode("UTF-8") + if len(line) > 15: + address = line[0:16] + if len(line) > 17: + name = line[17:] + else: + name = u"" + buddy = {'address': address, + 'name': name.encode("UTF-8"), + 'profile_name': ''} + buddy_list.append(buddy) + f.close() + set('client', 'buddy-list', json.dumps(buddy_list)) + main() diff --git a/torchat/src/dlg.py b/torchat/src/dlg.py index 1abfb92e..37dda055 100644 --- a/torchat/src/dlg.py +++ b/torchat/src/dlg.py @@ -92,7 +92,8 @@ def getDefault(self, default): return default def setEnabled(self, enabled): - self.wx_label.Enable(enabled) + if hasattr(self, 'wx_label'): + self.wx_label.Enable(enabled) if self.wx_ctrl: self.wx_ctrl.Enable(enabled) diff --git a/torchat/src/dlg_settings.py b/torchat/src/dlg_settings.py index 422f7a6b..bc05aa44 100644 --- a/torchat/src/dlg_settings.py +++ b/torchat/src/dlg_settings.py @@ -17,7 +17,6 @@ import wx import dlg import config -import tc_client import translations lang = translations.lang_en @@ -29,16 +28,16 @@ def __init__(self, main_window): #1 outer panel and vertical sizer self.outer_panel = wx.Panel(self) - outer_sizer = wx.BoxSizer(wx.VERTICAL) - self.outer_panel.SetSizer(outer_sizer) + self.outer_sizer = wx.BoxSizer(wx.VERTICAL) + self.outer_panel.SetSizer(self.outer_sizer) #1.1 the notebook on the top self.notebook = wx.Notebook(self.outer_panel) - outer_sizer.Add(self.notebook, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5) + self.outer_sizer.Add(self.notebook, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5) #1.2 the button_sizer at the bottom button_sizer = wx.BoxSizer(wx.HORIZONTAL) - outer_sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, border=5) + self.outer_sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, border=5) #1.2.1 cancel button btn_cancel = wx.Button(self.outer_panel, wx.ID_CANCEL, lang.BTN_CANCEL) @@ -57,6 +56,9 @@ def __init__(self, main_window): self.p1 = dlg.Panel(self.notebook) self.notebook.AddPage(self.p1, lang.DSET_NET_TITLE) + portable = (self.mw.buddy_list.tor_config == "tor_portable") + self.tor_portable = dlg.Check(self.p1, lang.DSET_GUI_TOR_PORTABLE, int(portable)) + self.s_tor_portable = dlg.Separator(self.p1, "Tor portable") dlg.Text(self.p1, lang.DSET_NET_TOR_ADDRESS, ("tor_portable", "tor_server"), True) dlg.Text(self.p1, lang.DSET_NET_TOR_SOCKS, ("tor_portable", "tor_server_socks_port")) @@ -69,9 +71,7 @@ def __init__(self, main_window): dlg.Separator(self.p1, "Client") dlg.Text(self.p1, lang.DSET_NET_LISTEN_INTERFACE, ("client", "listen_interface"), True) dlg.Text(self.p1, lang.DSET_NET_LISTEN_PORT, ("client", "listen_port")) - self.p1.fit() - portable = (tc_client.TOR_CONFIG == "tor_portable") if portable: self.s_tor.setEnabled(False) else: @@ -96,13 +96,34 @@ def __init__(self, main_window): self.dir_tmp = dlg.Dir(self.p3, lang.DSET_MISC_TEMP_CUSTOM_DIR, ("files", "temp_files_custom_dir")) self.dir_tmp.setEnabled(not self.chk_tmp.getValue()) self.chk_tmp.wx_ctrl.Bind(wx.EVT_CHECKBOX, self.onChkTmp) - + + #3.4 plugins + self.p4 = dlg.Panel(self.notebook) + self.notebook.AddPage(self.p4, lang.DSET_PLUGINS_TITLE) + self.plugins = {} + enabled_plugins = set(config.get('plugin', 'enabled_plugins').split(',')) + import torchat + for plugin_name in sorted(torchat.PLUGINS.keys()): + plugin_dscr = getattr(lang, 'DSET_PLUGIN_' + plugin_name.upper(), plugin_name) + enabled = int(bool(plugin_name in enabled_plugins)) + self.plugins[plugin_name] = dlg.Check(self.p4, plugin_dscr, enabled) + + # add plugins' settings + self.addPluginSettings(main_window) + #4 fit the sizers - outer_sizer.Fit(self) - + self.p1.fit() + self.p2.fit() + self.p3.fit() + self.p4.fit() + self.outer_sizer.Fit(self) + + def addPluginSettings(self, main_window): + pass + def onChkTmp(self, evt): self.dir_tmp.setEnabled(not self.chk_tmp.getValue()) - + def onCancel(self, evt): evt.Skip() #let the frame now process the Cancel event @@ -110,6 +131,17 @@ def onOk(self, evt): self.p1.saveAllData() self.p2.saveAllData() self.p3.saveAllData() + #enabled_plugins = set(config.get('plugin', 'enabled_plugins').split(',')) + import torchat + if self.tor_portable.getValue() == 1: + config.set('client', 'tor_config', 'tor_portable') + else: + config.set('client', 'tor_config', 'tor') + enabled_plugins = [] + for plugin_name in torchat.PLUGINS: + if self.plugins[plugin_name].getValue(): + enabled_plugins.append(plugin_name) + config.set('plugin', 'enabled_plugins', ','.join(enabled_plugins)) if self.lang.getValue() != self.lang_old: config.importLanguage() evt.Skip() #let the frame now process the Ok event diff --git a/torchat/src/make_debian_package.py b/torchat/src/make_debian_package.py index 94e5e78c..96ca2ae5 100755 --- a/torchat/src/make_debian_package.py +++ b/torchat/src/make_debian_package.py @@ -86,11 +86,13 @@ "usr/lib/torchat", "usr/lib/torchat/SocksiPy", "usr/lib/torchat/translations", + "usr/lib/torchat/plugins", "usr/lib/torchat/Tor", ] files = [("translations/*.py", "usr/lib/torchat/translations"), ("translations/*.txt", "usr/lib/torchat/translations"), + ("plugins/*", "usr/lib/torchat/plugins"), ("icons/*", "usr/share/pixmaps/torchat"), ("SocksiPy/__init__.py", "usr/lib/torchat/SocksiPy"), ("SocksiPy/socks.py", "usr/lib/torchat/SocksiPy"), diff --git a/torchat/src/make_release_zip.py b/torchat/src/make_release_zip.py index 5a1acdb7..8831ff7c 100644 --- a/torchat/src/make_release_zip.py +++ b/torchat/src/make_release_zip.py @@ -95,6 +95,8 @@ def zipSrc(zipfile_name): "src\\translations\\insert_missing.py", "src\\translations\\__init__.py", + "src\\plugins\\*", + "src\\SocksiPy\\*.py", "src\\SocksiPy\\BUGS", "src\\SocksiPy\\LICENSE", @@ -130,6 +132,7 @@ def clean(folder): def cleanSrc(): clean(".") clean("translations") + clean("plugins") clean("SocksiPy") # ------------------ diff --git a/torchat/src/plugins/add_password.py b/torchat/src/plugins/add_password.py new file mode 100644 index 00000000..22c97730 --- /dev/null +++ b/torchat/src/plugins/add_password.py @@ -0,0 +1,60 @@ +# -*- coding: UTF-8 -*- + +NAME_en = u'Password protection against new contacts' +NAME_ru = u'Защита паролем от новых контактов' + +def load(torchat): + def get(option): + return torchat.config.get("password", option) + + torchat.config.config_defaults['password', 'password'] = '' + torchat.config.config_defaults['password', 'password_tip'] = '' + + _message_execute = torchat.tc_client.ProtocolMsg_message.execute + def message_execute(self): + if self.buddy and self.buddy not in self.bl.list and get('password'): + if get('password') in self.text: + self.buddy.sendChatMessage('Good password') + self.__class__ = torchat.tc_client.ProtocolMsg_add_me + _add_me_execute(self) + else: + self.buddy.sendChatMessage('Bad password') + return # to suppress warning sent by torchat and password passing + _message_execute(self) + torchat.tc_client.ProtocolMsg_message.execute = message_execute + + _add_me_execute = torchat.tc_client.ProtocolMsg_add_me.execute + def add_me_execute(self): + if self.buddy and self.buddy not in self.bl.list and get('password'): + message = u'Enter password' + if get('password_tip'): + message += u'. Password tip: %s' % get('password_tip') + self.buddy.sendChatMessage(message) + else: + _add_me_execute(self) + torchat.tc_client.ProtocolMsg_add_me.execute = add_me_execute + + def set_tr(lang, option, translation): + setattr(torchat.TRANSLATIONS[lang], + 'DSET_PASSWORD_' + option.upper(), translation) + set_tr('en', 'password', u'Password for new contacts') + set_tr('ru', 'password', u'Пароль для новых контактов') + set_tr('en', 'password_tip', u'Password tip') + set_tr('ru', 'password_tip', u'Подсказка о пароле') + torchat.config.importLanguage() + + _addPluginSettings = torchat.dlg_settings.Dialog.addPluginSettings + def addPluginSettings(self, main_window): + _addPluginSettings(self, main_window) + def tr(option): + attr_name = 'DSET_PASSWORD_' + option.upper() + if hasattr(torchat.dlg_settings.lang, attr_name): + return getattr(torchat.dlg_settings.lang, attr_name) + else: + return option + def text(self, option): + torchat.dlg.Text(self.p3, tr(option), + ("password", option), expand=True) + text(self, 'password') + text(self, 'password_tip') + torchat.dlg_settings.Dialog.addPluginSettings = addPluginSettings diff --git a/torchat/src/plugins/conference.py b/torchat/src/plugins/conference.py new file mode 100644 index 00000000..d0e03c10 --- /dev/null +++ b/torchat/src/plugins/conference.py @@ -0,0 +1,591 @@ +# -*- coding: UTF-8 -*- + +import os +import wx +import json + +NAME_en = u'Turn this account into a conference' +NAME_ru = u'Превращает аккаунт в конференцию' + +NOBODY = set(['help']) +GUEST = NOBODY | set(['read_actions', 'read', 'list', 'ignore', 'unignore']) +USER = GUEST | set(['write', 'pm']) +MODER = USER | set(['mute', 'unmute', 'kick', 'invite', 'ban', 'unban', + 'topic', 'description', 'set_avatar']) +ADMIN = MODER | set(['role', 'prefer_nicks', 'receiver_status', + 'allow_list', 'allow_pm', 'list_status', 'list_role', 'default_role', + 'show_admin_actions', 'show_enter_leave', 'welcome_help', + 'password', 'password_tip']) +OWNER = ADMIN | set(['add_admin', 'remove_admin']) + +ROLES = {'banned': set(), 'nobody': NOBODY, 'guest': GUEST, 'user': USER, + 'moder': MODER, 'admin': ADMIN, 'owner': OWNER} + +HELP = {} +HELP['banned'] = 'You are banned' +HELP['nobody'] = '''[help] +!help get help +''' +HELP['guest'] = HELP['nobody'] + ''' +You can refer nick or torchat id, with or without % and @. +You can refer part of nick. + +!list get list of room members +!ignore nick ignore messages from nick +!unignore nick undo ignore +''' +HELP['user'] = HELP['guest'] + ''' +You can send messages to chat. Just send regular messages! +To prevent !smth from interpreting as a command, prepend it with "_ ". + +!pm nick message private message to nick +''' +HELP['moder'] = HELP['user'] + ''' +!mute nick mute nick (role: user->guest) +!unmute nick undo mute (role: guest,nobody->user) +!kick nick kick nick from room +!invite nick invite nick to room +!ban nick ban nick +!unban nick undo ban +!topic text change topic of room +!description text change description of room +!set_avatar nick set avatar of nick as room's avatar +''' +HELP['admin'] = HELP['moder'] + ''' +!role [nobody|guest|user|moder] nick get or set role of nick +!prefer_nicks [yes|no] get or set prefer_nicks +!receiver_status [offline,][handshake,][online,][away,][busy] + get or set list of allowed receiver statuses +!allow_list [yes|no] get or set allow_list +!allow_pm [yes|no] get or set allow_pm +!list_status [yes|no] get or set list_status +!list_role [yes|no] get or set list_role +!default_role [banned|nobody|guest|user] get or set default_role +!show_admin_actions [yes|no] get or set show_admin_actions +!show_enter_leave [yes|no] get or set show_enter_leave +!welcome_help [yes|no] get or set welcome_help +!password [password] get or set room password + (needs add_password plugin) +!password_tip [password tip] get or set room password tip + (needs add_password plugin) + +To make closed conference, set role of each user to 'user' explicitly +and then set default_role=banned. +''' +HELP['owner'] = HELP['admin'] + ''' +!add_admin nick set nick's role to admin +!remove_admin nick set nick's role to user +''' +WELCOME = '''%s +Topic: %s + +Description: %s''' + +def load(torchat): + torchat.config.config_defaults['conference', 'prefer_nicks'] = 1 + torchat.config.config_defaults['conference', 'receiver_status'] = 'online,away' + torchat.config.config_defaults['conference', 'no_gui'] = 0 + torchat.config.config_defaults['conference', 'roles'] = '{}' + torchat.config.config_defaults['conference', 'default_role'] = 'user' + torchat.config.config_defaults['conference', 'allow_list'] = 1 + torchat.config.config_defaults['conference', 'allow_pm'] = 1 + torchat.config.config_defaults['conference', 'list_status'] = 1 + torchat.config.config_defaults['conference', 'list_role'] = 1 + torchat.config.config_defaults['conference', 'show_admin_actions'] = 1 + torchat.config.config_defaults['conference', 'show_enter_leave'] = 1 + torchat.config.config_defaults['conference', 'welcome_help'] = 1 + torchat.config.config_defaults['conference', 'ignored'] = '{"who-bywho":1}' + + def sstatus(status): + if status == torchat.tc_client.STATUS_OFFLINE: + return 'offline' + if status == torchat.tc_client.STATUS_HANDSHAKE: + return 'handshake' + if status == torchat.tc_client.STATUS_ONLINE: + return 'online' + if status == torchat.tc_client.STATUS_AWAY: + return 'away' + if status == torchat.tc_client.STATUS_XA: + return 'busy' + STATUSES = set(['offline', 'handshake', 'online', 'away', 'busy']) + def buddy_list(): + return torchat.app.mw.buddy_list + def get(option): + return torchat.config.get("conference", option) + def set_option(option, value): + torchat.config.set("conference", option, value) + roles = json.loads(get("roles")) + def role_of(torchat_id): + if torchat_id == buddy_list().own_buddy.address: + return 'owner' + default_role = get("default_role") + return roles.get(torchat_id, default_role) + def set_role(torchat_id, role): + roles[torchat_id] = role + set_option("roles", json.dumps(roles)) + ignored = json.loads(get("ignored")) + def is_ignored(who, bywho): + return ('%s-%s' % (who, bywho)) in ignored + def ignore(who, bywho): + ignored[('%s-%s' % (who, bywho))] = 1 + set_option("ignored", json.dumps(ignored)) + def unignore(who, bywho): + ignored.pop(('%s-%s' % (who, bywho)), None) + set_option("ignored", json.dumps(ignored)) + def is_moder(torchat_id): + return role_of(torchat_id) in ('moder', 'admin', 'owner') + def can(torchat_id, action): + role = role_of(torchat_id) + return role in ROLES and action in ROLES[role] + def buddy_from_nick(nick, me): + if nick == 'me': + return me + for buddy in buddy_list().list: + if buddy.address == nick or buddy.profile_name == nick: + return buddy + if nick.startswith('@') or nick.startswith('%'): + nick = nick[1:] + for buddy in buddy_list().list: + if buddy.address == nick or buddy.profile_name == nick: + return buddy + i_am_moder = is_moder(me.address) + for buddy in buddy_list().list: + if nick in nick_repr(buddy, i_am_moder): + return buddy + def nick_repr(buddy, moder=False): + nick = buddy.address + if int(get("prefer_nicks")) == 1 \ + and buddy.profile_name \ + and not torchat.tc_client.isValidAddress(buddy.profile_name): + nick = buddy.getSaneProfileName() + if moder or int(get('list_role')) == 1: + buddy_role = role_of(buddy.address) + if buddy_role in ('admin', 'owner'): + nick = '@' + nick + if buddy_role == 'moder': + nick = '%' + nick + return nick + def announce(text, moder): + prefix = '[admin]' if moder else '[user]' + text = '%s %s' % (prefix, text) + for buddy in buddy_list().list: + if not can(buddy.address, 'read'): + continue + if not is_moder(buddy.address): + if moder and int(get("show_admin_actions")) != 1: + continue + if not moder and int(get("show_enter_leave")) != 1: + continue + if sstatus(buddy.status) not in get('receiver_status'): + continue + buddy.sendChatMessage(text) + def splitLine(line): + return torchat.tc_client.splitLine(line) + + def do_help(me, _): + topic = buddy_list().own_buddy.profile_name + description = buddy_list().own_buddy.profile_text + text = WELCOME % (HELP[role_of(me.address)], topic, description) + me.sendChatMessage(text) + def do_list(me, _): + if int(get('allow_list')) != 1 and not is_moder(me.address): + me.sendChatMessage('[room] Action not allowed') + return + nicks = [] + shown = set() + for buddy in buddy_list().list: + shown.add(buddy.address) + nick = nick_repr(buddy, is_moder(me.address)) + if buddy.address not in nick and is_moder(me.address): + nick += ' (%s)' % buddy.address + if buddy.status != torchat.tc_client.STATUS_ONLINE: + if int(get('list_status')) == 1 or is_moder(me.address): + nick += ' [%s]' % sstatus(buddy.status) + if int(get('list_role')) == 1 or is_moder(me.address): + nick += ' /%s/' % role_of(buddy.address) + nicks.append(nick) + text = '[room]\n' + '\n'.join(nicks) + if is_moder(me.address): + not_in_room = [] + for address, role in roles.items(): + if address not in shown: + not_in_room.append('%s /%s/' % (address, role)) + if not_in_room: + text += '\n\nNot in room:\n' + '\n'.join(not_in_room) + me.sendChatMessage(text) + def do_ignore(me, nick): + buddy = buddy_from_nick(nick, me) + if buddy: + if is_moder(buddy.address): + me.sendChatMessage('[room] Can not ignore moderator') + else: + ignore(buddy.address, me.address) + else: + me.sendChatMessage('[room] Unknown nick') + def do_unignore(me, nick): + buddy = buddy_from_nick(nick, me) + if buddy: + unignore(buddy.address, me.address) + else: + me.sendChatMessage('[room] Unknown nick') + def do_pm(me, argument): + if int(get('allow_pm')) != 1: + me.sendChatMessage('[room] Private messages disabled') + return + dest, message = splitLine(argument) + dest = buddy_from_nick(dest, me) + if not dest: + me.sendChatMessage('[room] Unknown nick') + return + if is_ignored(me.address, dest.address): + me.sendChatMessage('[room] Buddy ignores you') + return + my_nick = nick_repr(me, is_moder(dest.address)) + dest.sendChatMessage('[private] %s: %s' % (my_nick, message)) + def do_mute(me, nick): + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + if role_of(buddy.address) != 'user': + me.sendChatMessage('[room] You can mute only users') + return + set_role(buddy.address, 'guest') + announce('%s muted %s' % (nick_repr(me), nick_repr(buddy)), True) + def do_unmute(me, nick): + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + if role_of(buddy.address) not in ('nobody', 'guest'): + me.sendChatMessage('[room] You can unmute nobody,guest') + return + announce('%s unmuted %s' % (nick_repr(me), nick_repr(buddy)), True) + set_role(buddy.address, 'user') + def do_kick(me, nick): + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + if role_of(buddy.address) not in ('nobody', 'guest', 'user'): + me.sendChatMessage('[room] You can kick nobody,guest,user') + return + buddy_list().removeBuddy(buddy, disconnect=False) + announce('%s kicked %s' % (nick_repr(me), nick_repr(buddy)), True) + def do_invite(me, address): + if not torchat.tc_client.isValidAddress(address): + me.sendChatMessage('[room] Bad address') + return + buddy = buddy_from_nick(address, me) + if buddy: + me.sendChatMessage('[room] Already in room') + return + buddy = torchat.tc_client.Buddy(address, buddy_list(), '') + buddy_list().addBuddy(buddy) + if role_of(address) in ('banned', 'nobody', 'guest'): + set_role(address, 'user') + announce('%s invited %s' % (nick_repr(me), address), True) + def do_ban(me, nick): + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + if role_of(buddy.address) not in ('nobody', 'guest', 'user'): + me.sendChatMessage('[room] You can ban nobody,guest,user') + return + set_role(buddy.address, 'banned') + buddy_list().removeBuddy(buddy, disconnect=False) + announce('%s banned %s' % (nick_repr(me), nick_repr(buddy)), True) + def do_unban(me, address): + if role_of(address) != 'banned': + me.sendChatMessage('[room] The address is not banned') + return + set_role(address, 'user') + announce('%s unbanned %s' % (nick_repr(me), address), True) + def do_topic(me, topic): + torchat.config.set("profile", "name", topic) + for buddy in buddy_list().list: + buddy.sendProfile() + announce('%s changed the topic to %s' % (nick_repr(me), topic), True) + def do_description(me, topic): + torchat.config.set("profile", "text", topic) + for buddy in buddy_list().list: + buddy.sendProfile() + announce('%s changed the description to %s' % (nick_repr(me), topic), True) + def do_set_avatar(me, nick): + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + alpha = buddy.profile_avatar_data_alpha + avatar = buddy.profile_avatar_data + buddy_list().own_avatar_data_alpha = alpha + buddy_list().own_avatar_data = avatar + file_name = os.path.join(torchat.config.getDataDir(), "avatar.png") + if not avatar: + torchat.tc_client.wipeFile(file_name) + else: + image = wx.ImageFromData(64, 64, avatar) + if alpha: + image.SetAlphaData(alpha) + image.SaveFile(file_name, wx.BITMAP_TYPE_PNG) + for buddy in buddy_list().list: + buddy.sendAvatar(send_empty=True) + announce('%s changed image of the room' % nick_repr(me), True) + def do_role(me, argument): + arg1, arg2 = splitLine(argument) + if arg2: + # set + new_role, nick = arg1, arg2 + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + if role_of(buddy.address) in ('admin', 'owner'): + me.sendChatMessage('[room] Can not change role of admin,owner') + return + if new_role not in ('nobody', 'guest', 'user', 'moder'): + me.sendChatMessage('[room] Can change role to nobody,guest,user,moder') + return + set_role(buddy.address, new_role) + announce('%s set role of %s to %s' % + (nick_repr(me), nick_repr(buddy), new_role), True) + else : + # get + nick = arg1 + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + me.sendChatMessage('[room] role(%s)=%s' % + (nick, role_of(buddy.address))) + def yes_no_command(me, option, yes_no): + if yes_no in ('yes', 'no'): + # set + set_option(option, int(yes_no == 'yes')) + announce('%s set %s to %s' % + (nick_repr(me), option, yes_no), True) + elif not yes_no: + # get + value = 'yes' if int(get(option)) == 1 else 'no' + me.sendChatMessage('[room] %s=%s' % (option, value)) + else: + me.sendChatMessage('[room] Bad argument') + def do_prefer_nicks(me, yes_no): + yes_no_command(me, 'prefer_nicks', yes_no) + def do_allow_list(me, yes_no): + yes_no_command(me, 'allow_list', yes_no) + def do_allow_pm(me, yes_no): + yes_no_command(me, 'allow_pm', yes_no) + def do_list_status(me, yes_no): + yes_no_command(me, 'list_status', yes_no) + def do_list_role(me, yes_no): + yes_no_command(me, 'list_role', yes_no) + def do_show_admin_actions(me, yes_no): + yes_no_command(me, 'show_admin_actions', yes_no) + def do_show_enter_leave(me, yes_no): + yes_no_command(me, 'show_enter_leave', yes_no) + def do_welcome_help(me, yes_no): + yes_no_command(me, 'welcome_help', yes_no) + def do_default_role(me, role): + if role in ('banned', 'nobody', 'guest', 'user'): + # set + set_option('default_role', role) + announce('%s set default role to %s' % + (nick_repr(me), role), True) + elif not role: + # get + me.sendChatMessage('[room] default_role=%s' % get('default_role')) + def do_receiver_status(me, statuses): + if statuses: + # set + statuses = set(statuses.split(',')) + if not all(status in STATUSES for status in statuses): + me.sendChatMessage('[room] Bad status') + return + set_option('receiver_status', ','.join(statuses)) + announce('%s set receiver statuses to %s' % + (nick_repr(me), ','.join(statuses)), True) + else: + # get + me.sendChatMessage('[room] receiver_status=%s' % get('receiver_status')) + def do_password(me, password): + if password: + # set + torchat.config.set('password', 'password', password) + announce('%s set password to %s' % (nick_repr(me), password), True) + else: + # get + password = torchat.config.get('password', 'password') + me.sendChatMessage('[room] password=%s' % password) + def do_password_tip(me, password_tip): + if password_tip: + # set + torchat.config.set('password', 'password_tip', password_tip) + announce('%s set password tip to %s' % + (nick_repr(me), password_tip), True) + else: + # get + password_tip = torchat.config.get('password', 'password_tip') + me.sendChatMessage('[room] password_tip=%s' % password_tip) + def do_add_admin(me, nick): + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + if role_of(buddy.address) in ('admin', 'owner'): + me.sendChatMessage('[room] This user is already admin or owner') + return + set_role(buddy.address, 'admin') + announce("%s set %s's role to admin" % + (nick_repr(me), nick_repr(buddy)), True) + def do_remove_admin(me, nick): + buddy = buddy_from_nick(nick, me) + if not buddy: + me.sendChatMessage('[room] Unknown nick') + return + if role_of(buddy.address) != 'admin': + me.sendChatMessage('[room] This user is not admin') + return + set_role(buddy.address, 'user') + announce("%s set %s's role to user" % + (nick_repr(me), nick_repr(buddy)), True) + def resend(me, text): + if not can(me.address, 'write'): + me.sendChatMessage('[room] You can not send messages') + return + for buddy in buddy_list().list: + if not can(buddy.address, 'read'): + continue + if buddy == me: + continue + if buddy == buddy_list().own_buddy: + continue + if me == buddy_list().own_buddy \ + and text.startswith('['): # [room] [delayed] [private] etc + continue + if is_ignored(me.address, buddy.address): + continue + if sstatus(buddy.status) not in get('receiver_status'): + continue + sender_nick = nick_repr(me, is_moder(buddy.address)) + resent_message = '%s: %s' % (sender_nick, text) + buddy.sendChatMessage(resent_message) + + load_vars = vars() + + _message_execute = torchat.tc_client.ProtocolMsg_message.execute + def message_execute(self): + goood_message = self.buddy and self.buddy in self.bl.list + if int(get("no_gui")) == 0 or not goood_message: + _message_execute(self) + if goood_message: + me = self.buddy + if self.text.startswith('!'): + command, argument = splitLine(self.text) + command = command[1:] # pop "!" + if command not in OWNER: + me.sendChatMessage('[room] Unknown command') + return + if not can(me.address, command): + me.sendChatMessage('[room] Action not allowed') + return + func_name = "do_%s" % command + if func_name in load_vars: + func = load_vars[func_name] + func(me, argument) + else: + me.sendChatMessage('[room] Not implemented') + return + else: + # resend message + resend(me, self.text) + torchat.tc_client.ProtocolMsg_message.execute = message_execute + + _add_me_execute = torchat.tc_client.ProtocolMsg_add_me.execute + def add_me_execute(self): + welcome = self.buddy not in buddy_list().list + if role_of(self.buddy.address) == 'banned': + self.buddy.sendChatMessage('[room] You are banned') + return + _add_me_execute(self) + if int(get('welcome_help')) == 1 and welcome: + do_help(self.buddy, None) + nick = nick_repr(self.buddy) + announce('%s entered room' % nick, False) + torchat.tc_client.ProtocolMsg_add_me.execute = add_me_execute + + _remove_me_execute = torchat.tc_client.ProtocolMsg_remove_me.execute + def remove_me_execute(self): + user_left = self.buddy and self.buddy in buddy_list().list + nick = nick_repr(self.buddy) + _remove_me_execute(self) + announce('%s left room' % nick, False) + torchat.tc_client.ProtocolMsg_remove_me.execute = remove_me_execute + + def set_tr(lang, option, translation): + setattr(torchat.TRANSLATIONS[lang], + 'DSET_CONFERENCE_' + option.upper(), translation) + set_tr('en', 'title', u'Conference') + set_tr('ru', 'title', u'Конференция') + set_tr('en', 'no_gui', u'Do not reflect new messages in GUI') + set_tr('ru', 'no_gui', u'Не отображать новые сообщения в графическом интерфейсе') + set_tr('en', 'prefer_nicks', u'Show torchat nick if available instead of id to conference members') + set_tr('ru', 'prefer_nicks', u'Показывать ник отправителя вместо id, если ник выставлен') + set_tr('en', 'default_role', u'Default role for new members (banned|nobody|guest|user)') + set_tr('ru', 'default_role', u'Роль по умолчанию для новых членов (banned|nobody|guest|user)') + set_tr('en', 'allow_list', u'Regular users can get list of users') + set_tr('ru', 'allow_list', u'Обычные пользователи могут смотреть список пользователей') + set_tr('en', 'allow_pm', u'Allow private messages') + set_tr('ru', 'allow_pm', u'Разрешить личные сообщения') + set_tr('en', 'list_status', u'Regular users view status of users') + set_tr('ru', 'list_status', u'Обычные пользователи видят статус пользователей') + set_tr('en', 'list_role', u'Regular users view role of users') + set_tr('ru', 'list_role', u'Обычные пользователи видят роль пользователей') + set_tr('en', 'show_admin_actions', u"Regular users view administrators' actions") + set_tr('ru', 'show_admin_actions', u'Обычные пользователи видят действия администраторов') + set_tr('en', 'show_enter_leave', u'Regular users view somebody enter or leave room') + set_tr('ru', 'show_enter_leave', u'Обычные пользователи видят, когда кто-то входит или выходит') + set_tr('en', 'welcome_help', u'Welcome new users with help message') + set_tr('ru', 'welcome_help', u'Приветствовать новых пользователей справкой') + set_tr('en', 'receiver_status', u'Statuses of receivers [offline,][handshake,][online,][away,][busy]') + set_tr('ru', 'receiver_status', u'Статусы получателей [offline,][handshake,][online,][away,][busy]') + torchat.config.importLanguage() + + _addPluginSettings = torchat.dlg_settings.Dialog.addPluginSettings + def addPluginSettings(self, main_window): + _addPluginSettings(self, main_window) + self.p_conference = torchat.dlg.Panel(self.notebook) + self.notebook.AddPage(self.p_conference, + torchat.dlg_settings.lang.DSET_CONFERENCE_TITLE) + def tr(option): + attr_name = 'DSET_CONFERENCE_' + option.upper() + if hasattr(torchat.dlg_settings.lang, attr_name): + return getattr(torchat.dlg_settings.lang, attr_name) + else: + return option + def check(self, option): + torchat.dlg.Check(self.p_conference, tr(option), + ("conference", option)) + def text(self, option): + torchat.dlg.Text(self.p_conference, tr(option), + ("conference", option), expand=True) + check(self, 'no_gui') + check(self, 'prefer_nicks') + check(self, 'allow_list') + check(self, 'allow_pm') + check(self, 'list_status') + check(self, 'list_role') + check(self, 'show_admin_actions') + check(self, 'show_enter_leave') + check(self, 'welcome_help') + text(self, 'default_role') + text(self, 'receiver_status') + self.p_conference.fit() + torchat.dlg_settings.Dialog.addPluginSettings = addPluginSettings + + _dlg_settings_onOk = torchat.dlg_settings.Dialog.onOk + def dlg_settings_onOk(self, evt): + _dlg_settings_onOk(self, evt) + self.p_conference.saveAllData() + torchat.dlg_settings.Dialog.onOk = dlg_settings_onOk diff --git a/torchat/src/plugins/disable_echo.py b/torchat/src/plugins/disable_echo.py new file mode 100644 index 00000000..db50b743 --- /dev/null +++ b/torchat/src/plugins/disable_echo.py @@ -0,0 +1,24 @@ +# -*- coding: UTF-8 -*- + +import os + +NAME_en = u'Do not show self-echo messages' +NAME_ru = u'Не отображать эхо-ответы от самого себя' + +text_last = None + +def load(torchat): + _onSend = torchat.tc_gui.ChatWindow.onSend + def onSend(self, evt): + global text_last + text_last = self.txt_out.GetValue().rstrip().lstrip().replace("\x0b", os.linesep) + _onSend(self, evt) + torchat.tc_gui.ChatWindow.onSend = onSend + + _onChatMessage = torchat.tc_client.Buddy.onChatMessage + def onChatMessage(self, text): + global text_last + if self == self.bl.own_buddy and text == text_last: + return + _onChatMessage(self, text) + torchat.tc_client.Buddy.onChatMessage = onChatMessage diff --git a/torchat/src/plugins/fake_user_agent.py b/torchat/src/plugins/fake_user_agent.py new file mode 100644 index 00000000..76e58c57 --- /dev/null +++ b/torchat/src/plugins/fake_user_agent.py @@ -0,0 +1,59 @@ +# -*- coding: UTF-8 -*- + +NAME_en = u'Fake client and version' +NAME_ru = u'Подделывает отсылаемый клиент и версию' + +def load(torchat): + def get(option): + return torchat.config.get("fake_user_agent", option) + + torchat.config.config_defaults['fake_user_agent', 'client'] = \ + torchat.version.NAME + torchat.config.config_defaults['fake_user_agent', 'version'] = \ + torchat.version.VERSION + + _sendVersion = torchat.tc_client.Buddy.sendVersion + def sendVersion(self): + if self.isAlreadyPonged(): + msg = torchat.tc_client.ProtocolMsg_client(self, get('client')) + msg.send() + msg = torchat.tc_client.ProtocolMsg_version(self, get('version')) + msg.send() + else: + _sendVersion(self) + torchat.tc_client.Buddy.sendVersion = sendVersion + + def set_tr(lang, option, translation): + setattr(torchat.TRANSLATIONS[lang], + 'DSET_FAKEUA_' + option.upper(), translation) + set_tr('en', 'client', u'Client reported') + set_tr('ru', 'client', u'Отсылаемое название клиента') + set_tr('en', 'version', u'Version reported') + set_tr('ru', 'version', u'Отсылаемая версия клиента') + torchat.config.importLanguage() + + _addPluginSettings = torchat.dlg_settings.Dialog.addPluginSettings + def addPluginSettings(self, main_window): + _addPluginSettings(self, main_window) + def tr(option): + attr_name = 'DSET_FAKEUA_' + option.upper() + if hasattr(torchat.dlg_settings.lang, attr_name): + return getattr(torchat.dlg_settings.lang, attr_name) + else: + return option + def text(self, option): + torchat.dlg.Text(self.p3, tr(option), + ("fake_user_agent", option), expand=True) + text(self, 'client') + text(self, 'version') + torchat.dlg_settings.Dialog.addPluginSettings = addPluginSettings + + _dlg_settings_onOk = torchat.dlg_settings.Dialog.onOk + def dlg_settings_onOk(self, evt): + old_client = get("client") + old_version = get("version") + _dlg_settings_onOk(self, evt) + if get("client") != old_client or get("version") != old_version: + for buddy in self.mw.buddy_list.list: + buddy.sendVersion() + torchat.dlg_settings.Dialog.onOk = dlg_settings_onOk diff --git a/torchat/src/plugins/ping.py b/torchat/src/plugins/ping.py new file mode 100644 index 00000000..3bffcf8c --- /dev/null +++ b/torchat/src/plugins/ping.py @@ -0,0 +1,44 @@ +# -*- coding: UTF-8 -*- + +from functools import partial +from datetime import datetime + +import wx + +NAME_en = u'Add "Ping" item to popup menu' +NAME_ru = u'Добавляет пункт "Ping" в контекстное меню' + +def load(torchat): + def set_tr(lang, option, translation): + setattr(torchat.TRANSLATIONS[lang], + 'PING_' + option.upper(), translation) + set_tr('en', 'ping', u'Ping') + torchat.config.importLanguage() + + def onPing(chat_window, evt): + chat_window.last_ping = datetime.now() + chat_window.buddy.sendPing() + + _ChatWindow_PopupMenu = torchat.tc_gui.ChatWindow.PopupMenu + def ChatWindow_PopupMenu(self, menu): + # add Ping item + id = wx.NewId() + item = wx.MenuItem(menu, id, torchat.dlg_settings.lang.PING_PING) + self.Bind(wx.EVT_MENU, partial(onPing, self), id=id) + menu.AppendItem(item) + # show + _ChatWindow_PopupMenu(self, menu) + torchat.tc_gui.ChatWindow.PopupMenu = ChatWindow_PopupMenu + + _Buddy_onInConnectionFound = torchat.tc_client.Buddy.onInConnectionFound + def Buddy_onInConnectionFound(self, conn): + _Buddy_onInConnectionFound(self, conn) + for window in torchat.app.mw.chat_windows: + if window.buddy == self and hasattr(window, 'last_ping'): + delay = datetime.now() - window.last_ping + del window.last_ping + ms = delay.seconds * 1000 + delay.microseconds // 1000 + wx.CallAfter(torchat.tc_gui.ChatWindow.writeHintLine, + window, 'time=%i ms' % ms) + torchat.tc_client.Buddy.onInConnectionFound = Buddy_onInConnectionFound + diff --git a/torchat/src/plugins/rps.py b/torchat/src/plugins/rps.py new file mode 100644 index 00000000..2b5b5edd --- /dev/null +++ b/torchat/src/plugins/rps.py @@ -0,0 +1,175 @@ +# -*- coding: UTF-8 -*- + +import os +import re +import random +from hashlib import sha1 +from functools import partial + +import wx + +NAME_en = u'Rock-paper-scissors (see popup menu)' +NAME_ru = u'Камень-ножницы-бумага (см. контекстное меню)' + +FULL = { + 'r': 'rock', + 'p': 'paper', + 's': 'scissors', +} + +HASH_RE = r'\b[0-9a-f]{40}$' +CLEARTEXT_RE = r'\b\S{10,}-[rps]$' + +def winner_of(a, b): + if a == b: + return 'draw' + if a == 'rock' and b == 'scissors': + return a + if a == 'scissors' and b == 'paper': + return a + if a == 'paper' and b == 'rock': + return a + return b + +def load(torchat): + def get_lang(): + return torchat.dlg_settings.lang + def set_tr(lang, option, translation): + setattr(torchat.TRANSLATIONS[lang], + 'RPS_' + option.upper(), translation) + def tr(option): + return getattr(get_lang(), 'RPS_' + option.upper(), option) + set_tr('en', 'rps', u'Rock-paper-scissors') + set_tr('ru', 'rps', u'Камень-ножницы-бумага') + set_tr('en', 'rock', u'Rock') + set_tr('ru', 'rock', u'Камень') + set_tr('en', 'paper', u'Paper') + set_tr('ru', 'paper', u'Бумага') + set_tr('en', 'scissors', u'Scissors') + set_tr('ru', 'scissors', u'Ножницы') + set_tr('en', 'random', u'Random') + set_tr('ru', 'random', u'Наугад') + set_tr('en', 'reset', u'Reset') + set_tr('ru', 'reset', u'Сброс') + set_tr('en', 'draw', u'Draw') + set_tr('ru', 'draw', u'Ничья') + set_tr('en', 'hash_sent', u'Hash sent') + set_tr('ru', 'hash_sent', u'Хеш отправлен') + set_tr('en', 'cleartext_sent', u'Cleartext sent') + set_tr('ru', 'cleartext_sent', u'Открытый текст отправлен') + set_tr('en', 'you_won', u'You won') + set_tr('ru', 'you_won', u'Вы победили') + set_tr('en', 'opponent_won', u'Opponent won') + set_tr('ru', 'opponent_won', u'Соперник победил') + set_tr('en', 'cheat', u'Hash mismatch. Cheat!') + set_tr('ru', 'cheat', u'Хеш не совпадает. Жулик!') + set_tr('en', 'unknown_choice', u'Unknown choice') + set_tr('ru', 'unknown_choice', u'Неизвестный выбор') + torchat.config.importLanguage() + + def make_cleartext(salt, choice): + return '%s-%s' % (salt, choice[0]) + + def write_hint(window, hint): + wx.CallAfter(torchat.tc_gui.ChatWindow.writeHintLine, window, hint) + + def send_hash(window): + if hasattr(window, 'rps_hash_sent') and window.rps_hash_sent: + return + window.rps_hash_sent = True + cleartext = make_cleartext(window.rps_salt, window.rps_choice) + hash = sha1(cleartext).hexdigest() + window.buddy.sendChatMessage(hash) + write_hint(window, tr('hash_sent')) + + def send_cleartext(window): + if hasattr(window, 'rps_cleartext_sent') and window.rps_cleartext_sent: + return + window.rps_cleartext_sent = True + cleartext = make_cleartext(window.rps_salt, window.rps_choice) + window.buddy.sendChatMessage(cleartext) + write_hint(window, tr('cleartext_sent')) + + def reset(window): + window.rps_choice = '' + window.rps_salt = '' + window.rps_cleartext_sent = '' + window.rps_hash_sent = '' + + def onRps(window, what, evt): + reset(window) + if what == 'reset': + return + if what == 'random': + what = random.choice(['rock', 'paper', 'scissors']) + window.rps_salt = sha1(str(random.getrandbits(256))).hexdigest() + window.rps_choice = what + write_hint(window, tr(window.rps_choice)) + cleartext = make_cleartext(window.rps_salt, window.rps_choice) + write_hint(window, cleartext) + send_hash(window) + + _ChatWindow_PopupMenu = torchat.tc_gui.ChatWindow.PopupMenu + def ChatWindow_PopupMenu(self, menu): + # rps submenu + self.rps_submenu = wx.Menu() + menu.AppendMenu(-1, tr('rps'), self.rps_submenu) + def add_item(what): + id = wx.NewId() + item = wx.MenuItem(self.rps_submenu, id, tr(what)) + self.Bind(wx.EVT_MENU, partial(onRps, self, what), id=id) + self.rps_submenu.AppendItem(item) + add_item('rock') + add_item('paper') + add_item('scissors') + add_item('random') + add_item('reset') + # show + _ChatWindow_PopupMenu(self, menu) + torchat.tc_gui.ChatWindow.PopupMenu = ChatWindow_PopupMenu + + _message_execute = torchat.tc_client.ProtocolMsg_message.execute + def message_execute(self): + _message_execute(self) + goood_message = self.buddy and self.buddy in self.bl.list + if goood_message: + hash_match = re.search(HASH_RE, self.text) + if hash_match: + hash = hash_match.group() + for window in torchat.app.mw.chat_windows: + if window.buddy == self.buddy: + window.rps_opponent_hash = hash + if hasattr(window, 'rps_choice') and window.rps_choice: + send_cleartext(window) + cleartext_match = re.search(CLEARTEXT_RE, self.text) + if cleartext_match: + cleartext = cleartext_match.group() + for window in torchat.app.mw.chat_windows: + if window.buddy == self.buddy \ + and hasattr(window, 'rps_opponent_hash'): + send_cleartext(window) + hash = window.rps_opponent_hash + if sha1(cleartext).hexdigest() != hash: + self.buddy.sendChatMessage('Hash mismatch. Cheat!') + write_hint(window, tr('cheat')) + return + choice = cleartext.split('-')[-1] + if choice not in 'rps': + self.buddy.sendChatMessage('Unknown choice') + write_hint(window, tr('cheat')) + return + choice_full = FULL[choice] + winner = winner_of(choice_full, window.rps_choice) + tr_my = tr(window.rps_choice).lower() + tr_his = tr(choice_full).lower() + if winner == 'draw': + write_hint(window, tr('draw')) + elif winner == choice_full: + write_hint(window, tr('opponent_won') + + ': %s > %s' % (tr_his, tr_my)) + elif winner == window.rps_choice: + write_hint(window, tr('you_won') + + ': %s > %s' % (tr_my, tr_his)) + reset(window) + torchat.tc_client.ProtocolMsg_message.execute = message_execute + diff --git a/torchat/src/plugins/test_plugin.py b/torchat/src/plugins/test_plugin.py new file mode 100644 index 00000000..baa46b6c --- /dev/null +++ b/torchat/src/plugins/test_plugin.py @@ -0,0 +1,25 @@ +# -*- coding: UTF-8 -*- + +NAME_en = u'Test plugin, replaces all sent messages with "test"' +NAME_ru = u'Проверочный плагин, заменяет все отправляемые сообщения на "test"' + +def load(torchat): + _sendChatMessage = torchat.tc_client.Buddy.sendChatMessage + def sendChatMessage(self, text): + text = torchat.config.get("test_plugin", "text") + _sendChatMessage(self, text) + torchat.tc_client.Buddy.sendChatMessage = sendChatMessage + + torchat.config.config_defaults['test_plugin', 'text'] = 'test' + + torchat.TRANSLATIONS['en'].DSET_TEST_PLUGIN_TEXT = u'Replace any message with text' + torchat.TRANSLATIONS['ru'].DSET_TEST_PLUGIN_TEXT = u'Заменять сообщения текстом' + torchat.config.importLanguage() + + _addPluginSettings = torchat.dlg_settings.Dialog.addPluginSettings + def addPluginSettings(self, main_window): + _addPluginSettings(self, main_window) + torchat.dlg.Text(self.p3, torchat.dlg_settings.lang.DSET_TEST_PLUGIN_TEXT, + ("test_plugin", "text")) + torchat.dlg_settings.Dialog.addPluginSettings = addPluginSettings + diff --git a/torchat/src/tc_client.py b/torchat/src/tc_client.py index bbcf63ea..e05f1637 100644 --- a/torchat/src/tc_client.py +++ b/torchat/src/tc_client.py @@ -30,9 +30,12 @@ import hashlib import config import version +import re +import json +from functools import partial TORCHAT_PORT = 11009 #do NOT change this. -TOR_CONFIG = "tor" #the name of the active section in the .ini file + STATUS_OFFLINE = 0 STATUS_HANDSHAKE = 1 STATUS_ONLINE = 2 @@ -50,9 +53,6 @@ tb = config.tb # the traceback function has moved to config tb1 = config.tb1 -tor_pid = None -tor_proc = None -tor_timer = None def splitLine(text): @@ -282,9 +282,7 @@ def onStatus(self, status): def onProfileName(self, name): print "(2) %s.onProfile" % self.address self.profile_name = name - if self.name == "" and name <> "": - self.name = name - self.bl.save() + self.bl.save() self.bl.gui(CB_TYPE_PROFILE, self) def onProfileText(self, text): @@ -353,18 +351,6 @@ def sendOfflineMessages(self): print "(2) could not send offline messages, not fully connected." pass - def getDisplayNameOrAddress(self): - if self.name == "": - return self.address - else: - return self.name - - def getAddressAndDisplayName(self): - if self.name == "": - return self.address - else: - return self.address + " (" + self.name + ")" - def sendFile(self, filename, gui_callback): sender = FileSender(self, filename, gui_callback) return sender @@ -518,13 +504,23 @@ def sendVersion(self): else: print "(2) not connected, not sending version to %s" % self.address - def getDisplayName(self): - if self.name != "": - line = "%s (%s)" % (self.address, self.name) + def getSaneProfileName(self): + return self.profile_name.replace('\n', ' ').replace('\r', ' ')[:100] + + def getShortDisplayName(self): + if self.name: + return self.name + elif self.getSaneProfileName(): + return self.getSaneProfileName() else: - line = self.address - return line + return self.address + def getDisplayName(self): + short_name = self.getShortDisplayName() + if short_name != self.address: + return "%s (%s)" % (self.address, short_name) + else: + return self.address class BuddyList(object): """the BuddyList object is the central API of the client. @@ -541,8 +537,17 @@ class BuddyList(object): def __init__(self, callback, socket=None): print "(1) initializing buddy list" self.gui = callback - - startPortableTor() + self.tor_pid = None + self.tor_proc = None + self.tor_timer = None + self.tor_config = config.get('client', 'tor_config') + self.tor_server_socks_port = config.getint(self.tor_config, "tor_server_socks_port") + tor_interface = config.get("client", "listen_interface") + if self.tor_server_socks_port == 0: + self.tor_server_socks_port = getFreePort(tor_interface) + if self.tor_config == 'tor_portable' and \ + not isPortFree(tor_interface, self.tor_server_socks_port): + self.tor_server_socks_port = getFreePort(tor_interface) self.file_sender = {} self.file_receiver = {} @@ -555,26 +560,15 @@ def __init__(self, callback, socket=None): self.listener = Listener(self, socket) self.own_status = STATUS_ONLINE - filename = os.path.join(config.getDataDir(), "buddy-list.txt") + if self.tor_config == 'tor_portable': + self.startPortableTor() - #create empty buddy list file if it does not already exist - f = open(filename, "a") - f.close() - - f = open(filename, "r") - l = f.read().replace("\r", "\n").replace("\n\n", "\n").split("\n") - f.close self.list = [] - for line in l: - line = line.rstrip().decode("UTF-8") - if len(line) > 15: - address = line[0:16] - if len(line) > 17: - name = line[17:] - else: - name = u"" - buddy = Buddy(address, self, name) - self.list.append(buddy) + + for buddy_dict in json.loads(config.get('client', 'buddy-list')): + buddy = Buddy(buddy_dict['address'], self, buddy_dict['name']) + buddy.profile_name = buddy_dict['profile_name'] + self.list.append(buddy) found = False for buddy in self.list: @@ -603,11 +597,12 @@ def __init__(self, callback, socket=None): print "(1) BuddList initialized" def save(self): - f = open(os.path.join(config.getDataDir(), "buddy-list.txt"), "w") + buddy_list = [] for buddy in self.list: - line = ("%s %s\r\n" % (buddy.address, buddy.name.rstrip())).encode("UTF-8") - f.write(line) - f.close() + buddy_list.append({'address': buddy.address, + 'name': buddy.name.encode("UTF-8"), + 'profile_name': buddy.profile_name.encode("UTF-8")}) + config.set('client', 'buddy-list', json.dumps(buddy_list)) print "(2) buddy list saved" # this is the optimal spot to notify the GUI to redraw the list @@ -739,11 +734,124 @@ def onConnected(self, connection): connection.buddy.onOutConnectionSuccess() def stopClient(self): - stopPortableTor() + self.stopPortableTor() for buddy in self.list + self.incoming_buddies: buddy.disconnect() self.listener.close() #FIXME: does this really work? + def startPortableTor(self): + print "(1) entering function startPortableTor()" + old_dir = os.getcwd() + print "(1) current working directory is %s" % os.getcwd() + try: + print "(1) changing working directory" + os.chdir(config.getDataDir()) + os.chdir("Tor") + print "(1) current working directory is %s" % os.getcwd() + torrc = open('torrc.txt').read() + torrc = re.sub(r'SocksPort \d+', 'SocksPort %i' % + self.tor_server_socks_port, torrc) + torrc = re.sub(r'HiddenServicePort %i 127.0.0.1:\d+' % TORCHAT_PORT, + 'HiddenServicePort %i 127.0.0.1:%i' % + (TORCHAT_PORT, self.listener.port), torrc) + temp_torrc = open('torrc.temp.txt', 'w') + temp_torrc.write(torrc) + temp_torrc.close() + # completely remove all cache files from the previous run + #for root, dirs, files in os.walk("tor_data", topdown=False): + # for name in files: + # os.remove(os.path.join(root, name)) + # for name in dirs: + # os.rmdir(os.path.join(root, name)) + + # now start tor with the supplied config file + print "(1) trying to start Tor" + + if config.isWindows(): + if os.path.exists("tor.exe"): + #start the process without opening a console window + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= 1 #STARTF_USESHOWWINDOW + self.tor_proc = subprocess.Popen("tor.exe -f torrc.temp.txt".split(), startupinfo=startupinfo) + self.tor_pid = self.tor_proc.pid + else: + print "(1) there is no portable tor.exe" + self.tor_pid = False + else: + if os.path.exists("tor.sh"): + #let our shell script start a tor instance + os.system("chmod 0700 tor.sh") + self.tor_proc = subprocess.Popen("./tor.sh".split()) + self.tor_pid = self.tor_proc.pid + print "(1) tor pid is %i" % self.tor_pid + else: + print "(1) there is no Tor starter script (tor.sh)" + self.tor_pid = False + + if self.tor_pid: + #tor = subprocess.Popen("tor.exe -f torrc.txt".split(), creationflags=0x08000000) + print "(1) successfully started Tor (pid=%i)" % self.tor_pid + + # we now assume the existence of our hostname file + # it WILL be created after the first start + # if not, something must be totally wrong. + cnt = 0 + found = False + while cnt <= 20: + try: + print "(1) trying to read hostname file (try %i of 20)" % (cnt + 1) + f = open(os.path.join("hidden_service", "hostname"), "r") + hostname = f.read().rstrip()[:-6] + print "(1) found hostname: %s" % hostname + print "(1) writing own_hostname to torchat.ini" + config.set("client", "own_hostname", hostname) + found = True + f.close() + break + except: + # we wait 20 seconds for the file to appear + time.sleep(1) + cnt += 1 + + if not found: + print "(0) very strange: portable tor started but hostname could not be read" + print "(0) will use section [tor] and not [tor_portable]" + else: + #in portable mode we run Tor on some non-standard ports: + #so we switch to the other set of config-options + print "(1) switching active config section from [tor] to [tor_portable]" + self.tor_config = "tor_portable" + #start the timer that will periodically check that tor is still running + self.startPortableTorTimer() + else: + print "(1) no own Tor instance. Settings in [tor] will be used" + + except: + print "(1) an error occured while starting tor, see traceback:" + tb(1) + + print "(1) changing working directory back to %s" % old_dir + os.chdir(old_dir) + print "(1) current working directory is %s" % os.getcwd() + + def stopPortableTor(self): + if not self.tor_pid: + return + else: + print "(1) tor has pid %i, terminating." % self.tor_pid + config.killProcess(self.tor_pid) + + def startPortableTorTimer(self): + self.tor_timer = threading.Timer(10, + partial(BuddyList.onPortableTorTimer, self)) + self.tor_timer.start() + + def onPortableTorTimer(self): + if self.tor_proc.poll() != None: + print "(1) Tor stopped running. Will restart it now" + self.startPortableTor() + else: + self.startPortableTorTimer() class FileSender(threading.Thread): def __init__(self, buddy, file_name, callback): @@ -1241,6 +1349,13 @@ def execute(self): if self.buddy: print "(2) %s says it can't handle '%s'" % (self.buddy.address, self.offending_command) +def isValidAddress(address): + if len(address) <> 16: + return False + for c in address: + if not c in "234567abcdefghijklmnopqrstuvwxyz": # base32 + return False + return True class ProtocolMsg_ping(ProtocolMsg): """a ping message consists of sender address and a random string (cookie). @@ -1250,12 +1365,7 @@ def parse(self): self.address, self.answer = splitLine(self.blob) def isValidAddress(self): - if len(self.address) <> 16: - return False - for c in self.address: - if not c in "234567abcdefghijklmnopqrstuvwxyz": # base32 - return False - return True + return isValidAddress(self.address) def execute(self): print "(2) <<< PING %s" % self.address @@ -1357,16 +1467,15 @@ def execute(self): self.buddy.count_unanswered_pings = 0 self.buddy.sendPing() + #now we can finally put our answer into the send queue + print "(2) PONG >>> %s" % self.address + answer = ProtocolMsg_pong(self.buddy, self.answer) + answer.send() if self.buddy.isAlreadyPonged(): #but we don't need to send more than one pong on the same conn_out #only if this is also a new conn_out because the last one failed print "(2) not sending another pong over same connection" return - - #now we can finally put our answer into the send queue - print "(2) PONG >>> %s" % self.address - answer = ProtocolMsg_pong(self.buddy, self.answer) - answer.send() self.buddy.conn_out.pong_sent = True self.buddy.sendVersion() @@ -1426,7 +1535,7 @@ def execute(self): class ProtocolMsg_client(ProtocolMsg): """transmits the name of the client software. Usually sent after the pong""" def parse(self): - self.client = self.blob + self.client = self.blob.decode('UTF-8') def execute(self): if self.buddy: @@ -1437,7 +1546,7 @@ def execute(self): class ProtocolMsg_version(ProtocolMsg): """transmits the version number of the client software. Usually sent after the 'client' message""" def parse(self): - self.version = self.blob + self.version = self.blob.decode('UTF-8') def execute(self): if self.buddy: @@ -1554,7 +1663,6 @@ def execute(self): print "(2) add me from %s" % self.buddy.address if not self.buddy in self.bl.list: print "(2) received add_me from new buddy %s" % self.buddy.address - self.buddy.name = self.buddy.profile_name self.bl.addBuddy(self.buddy) msg = "<- has added you" self.buddy.onChatMessage(msg) @@ -1896,9 +2004,10 @@ def run(self): self.running = True try: self.socket = socks.socksocket() - self.socket.setproxy(socks.PROXY_TYPE_SOCKS4, - config.get(TOR_CONFIG, "tor_server"), - config.getint(TOR_CONFIG, "tor_server_socks_port")) + socks_ip = config.get(self.bl.tor_config, "tor_server") + socks_port = self.bl.tor_server_socks_port + print "(2) using socks5 proxy '%s:%i'" % (socks_ip, socks_port) + self.socket.setproxy(socks.PROXY_TYPE_SOCKS5, socks_ip, socks_port) print "(2) trying to connect '%s'" % self.address self.socket.connect((str(self.address), TORCHAT_PORT)) print "(2) connected to %s" % self.address @@ -1955,16 +2064,19 @@ def __init__(self, buddy_list, socket=None): self.buddy_list = buddy_list self.conns = [] self.socket = socket + self.port = config.getint("client", "listen_port") + if not self.socket: + interface = config.get("client", "listen_interface") + self.socket = tryBindPort(interface, self.port) + if not self.socket: + self.socket = tryBindPort(interface, 0) + self.port = self.socket.getsockname()[1] + self.socket.listen(5) self.start() self.startTimer() def run(self): self.running = True - if not self.socket: - interface = config.get("client", "listen_interface") - port = config.getint("client", "listen_port") - self.socket = tryBindPort(interface, port) - self.socket.listen(5) while self.running: try: conn, address = self.socket.accept() @@ -1982,7 +2094,7 @@ def close(self): try: print "(2) closing listening socket %s:%s" \ % (config.get("client", "listen_interface"), - config.get("client", "listen_port")) + self.port) self.socket.close() print "(2) success" except: @@ -2019,111 +2131,27 @@ def tryBindPort(interface, port): tb() return False -def startPortableTor(): - print "(1) entering function startPortableTor()" - global tor_in, tor_out - global TOR_CONFIG - global tor_pid - global tor_proc - old_dir = os.getcwd() - print "(1) current working directory is %s" % os.getcwd() +def isPortFree(interface, port): try: - print "(1) changing working directory" - os.chdir(config.getDataDir()) - os.chdir("Tor") - print "(1) current working directory is %s" % os.getcwd() - # completely remove all cache files from the previous run - #for root, dirs, files in os.walk("tor_data", topdown=False): - # for name in files: - # os.remove(os.path.join(root, name)) - # for name in dirs: - # os.rmdir(os.path.join(root, name)) - - # now start tor with the supplied config file - print "(1) trying to start Tor" - - if config.isWindows(): - if os.path.exists("tor.exe"): - #start the process without opening a console window - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= 1 #STARTF_USESHOWWINDOW - tor_proc = subprocess.Popen("tor.exe -f torrc.txt".split(), startupinfo=startupinfo) - tor_pid = tor_proc.pid - else: - print "(1) there is no portable tor.exe" - tor_pid = False - else: - if os.path.exists("tor.sh"): - #let our shell script start a tor instance - os.system("chmod 0700 tor.sh") - tor_proc = subprocess.Popen("./tor.sh".split()) - tor_pid = tor_proc.pid - print "(1) tor pid is %i" % tor_pid - else: - print "(1) there is no Tor starter script (tor.sh)" - tor_pid = False - - if tor_pid: - #tor = subprocess.Popen("tor.exe -f torrc.txt".split(), creationflags=0x08000000) - print "(1) successfully started Tor (pid=%i)" % tor_pid - - # we now assume the existence of our hostname file - # it WILL be created after the first start - # if not, something must be totally wrong. - cnt = 0 - found = False - while cnt <= 20: - try: - print "(1) trying to read hostname file (try %i of 20)" % (cnt + 1) - f = open(os.path.join("hidden_service", "hostname"), "r") - hostname = f.read().rstrip()[:-6] - print "(1) found hostname: %s" % hostname - print "(1) writing own_hostname to torchat.ini" - config.set("client", "own_hostname", hostname) - found = True - f.close() - break - except: - # we wait 20 seconds for the file to appear - time.sleep(1) - cnt += 1 - - if not found: - print "(0) very strange: portable tor started but hostname could not be read" - print "(0) will use section [tor] and not [tor_portable]" - else: - #in portable mode we run Tor on some non-standard ports: - #so we switch to the other set of config-options - print "(1) switching active config section from [tor] to [tor_portable]" - TOR_CONFIG = "tor_portable" - #start the timer that will periodically check that tor is still running - startPortableTorTimer() - else: - print "(1) no own Tor instance. Settings in [tor] will be used" - + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((interface, port)) + s.listen(5) + s.close() + return True except: - print "(1) an error occured while starting tor, see traceback:" - tb(1) + return False - print "(1) changing working directory back to %s" % old_dir - os.chdir(old_dir) - print "(1) current working directory is %s" % os.getcwd() +def getFreePort(interface): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((interface, 0)) + s.listen(5) + port = s.getsockname()[1] + s.close() + return port + except: + tb() + return False -def stopPortableTor(): - if not tor_pid: - return - else: - print "(1) tor has pid %i, terminating." % tor_pid - config.killProcess(tor_pid) - -def startPortableTorTimer(): - global tor_timer - tor_timer = threading.Timer(10, onPortableTorTimer) - tor_timer.start() - -def onPortableTorTimer(): - if tor_proc.poll() != None: - print "(1) Tor stopped running. Will restart it now" - startPortableTor() - else: - startPortableTorTimer() diff --git a/torchat/src/tc_gui.py b/torchat/src/tc_gui.py index e79433ed..21c39fa8 100644 --- a/torchat/src/tc_gui.py +++ b/torchat/src/tc_gui.py @@ -308,7 +308,7 @@ def __init__(self, main_window, type): self.Bind(wx.EVT_MENU, self.onQuit, item) def onSendFile(self, evt): - title = lang.DFT_FILE_OPEN_TITLE % self.buddy.getAddressAndDisplayName() + title = lang.DFT_FILE_OPEN_TITLE % self.buddy.getDisplayName() dialog = wx.FileDialog(self.mw, title, style=wx.OPEN) dialog.SetDirectory(config.getHomeDir()) if dialog.ShowModal() == wx.ID_OK: @@ -320,7 +320,8 @@ def onEdit(self, evt): dialog.ShowModal() def onDelete(self, evt): - answer = wx.MessageBox(lang.D_CONFIRM_DELETE_MESSAGE % (self.buddy.address, self.buddy.name), + answer = wx.MessageBox(lang.D_CONFIRM_DELETE_MESSAGE % + (self.buddy.getDisplayName(), ''), lang.D_CONFIRM_DELETE_TITLE, wx.YES_NO|wx.NO_DEFAULT) if answer == wx.YES: @@ -913,12 +914,16 @@ def onBuddyProfileChanged(self, buddy): def onBuddyAvatarChanged(self, buddy): print "(2) converting %s avatar data into wx.Bitmap" % buddy.address try: - image = wx.ImageFromData(64, 64, buddy.profile_avatar_data) - if buddy.profile_avatar_data_alpha: - print "(2) %s avatar has alpha channel" % buddy.address - image.SetAlphaData(buddy.profile_avatar_data_alpha) - buddy.profile_avatar_object = wx.BitmapFromImage(image) - + if buddy.profile_avatar_data: + image = wx.ImageFromData(64, 64, buddy.profile_avatar_data) + if buddy.profile_avatar_data_alpha: + print "(2) %s avatar has alpha channel" % buddy.address + image.SetAlphaData(buddy.profile_avatar_data_alpha) + buddy.profile_avatar_object = wx.BitmapFromImage(image) + else: + torchat_png = os.path.join(config.ICON_DIR, "torchat.png") + bitmap = wx.Bitmap(torchat_png, wx.BITMAP_TYPE_PNG) + buddy.profile_avatar_object = bitmap except: print "(2) could not convert %s avatar data to wx.Bitmap" % buddy.address tb() @@ -1005,11 +1010,11 @@ def __init__(self, list, index): self.avatar = wx.StaticBitmap(self.panel, -1, bitmap) sizer.Add(self.avatar, 0, wx.ALL, 5) - name = self.buddy.name - if self.buddy.profile_name <> u"": - name = self.buddy.profile_name - - text = "%s\n%s" % (self.buddy.address, name) + text = self.buddy.address + if self.buddy.getSaneProfileName(): + text += "\n" + self.buddy.getSaneProfileName() + if self.buddy.name: + text += "\n" + self.buddy.name if self.buddy.profile_text <> u"": text = "%s\n\n%s" % (text, textwrap.fill(self.buddy.profile_text, 30)) @@ -1257,9 +1262,7 @@ def updateTitle(self): else: title = "" - title += self.buddy.address - if self.buddy.name != "": - title += " (%s)" % self.buddy.name + title += self.buddy.getDisplayName() self.SetTitle(title + " %s" % config.getProfileLongName()) @@ -1325,10 +1328,7 @@ def notifyOfflineSent(self): def process(self, message): #message must be unicode - if self.buddy.name != "": - name = self.buddy.name - else: - name = self.buddy.address + name = self.buddy.getShortDisplayName() self.writeColored(config.get("gui", "color_nick_buddy"), name, message) self.notify(name, message) @@ -1453,7 +1453,7 @@ def onCopy(self, evt): wx.TheClipboard.Close() def onSendFile(self, evt): - title = lang.DFT_FILE_OPEN_TITLE % self.buddy.getAddressAndDisplayName() + title = lang.DFT_FILE_OPEN_TITLE % self.buddy.getDisplayName() dialog = wx.FileDialog(self, title, style=wx.OPEN) dialog.SetDirectory(config.getHomeDir()) if dialog.ShowModal() == wx.ID_OK: @@ -1677,9 +1677,7 @@ def updateOutput(self): self.bytes_complete = 0 percent = 100.0 * self.bytes_complete / self.bytes_total - peer_name = self.buddy.address - if self.buddy.name != "": - peer_name += " (%s)" % self.buddy.name + peer_name = self.buddy.getDisplayName() title = "%04.1f%% - %s" % (percent, os.path.basename(self.file_name)) self.SetTitle(title) self.progress_bar.SetValue(percent) @@ -1754,7 +1752,7 @@ def onCancel(self, evt): self.Close() def onSave(self, evt): - title = lang.DFT_FILE_SAVE_TITLE % self.buddy.getAddressAndDisplayName() + title = lang.DFT_FILE_SAVE_TITLE % self.buddy.getDisplayName() dialog = wx.FileDialog(self, title, defaultFile=self.file_name, style=wx.SAVE) if config.isPortable(): dialog.SetDirectory(config.getDataDir()) diff --git a/torchat/src/torchat.py b/torchat/src/torchat.py index d25e984b..41c99022 100755 --- a/torchat/src/torchat.py +++ b/torchat/src/torchat.py @@ -39,11 +39,58 @@ print "(2) wxversion screwed up, this is harmless, ignoring it." import wx +import re import os +import sys + import tc_client import tc_gui - +import dlg +import dlg_settings +import translations +import version + +# xx to module +TRANSLATIONS = {} +translations_dir = os.path.join(os.path.dirname(__file__), 'translations') +for translation_file in os.listdir(translations_dir): + if re.match(r'^lang_..\.py$', translation_file): + translation_name = translation_file[:-3] + xx = translation_name[-2:] + translation_path = os.path.join(translations_dir, translation_file) + TRANSLATIONS[xx] = getattr(translations, translation_name) + +PLUGINS = {} # shortname to python modules +# plugin module must have NAME_en member (string, long name) +# plugin module must have load(torchat) member, torchat is this module +# plugin module should have NAME_