├── .gitignore ├── README.md └── tox-irc-sync.py /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Tox-Sync 2 | 3 | A bot that sync messages between Freenode IRC #tox-ontopic and Tox group chat. 4 | -------------------------------------------------------------------------------- /tox-irc-sync.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | import string 4 | import select 5 | import re 6 | import pickle 7 | 8 | from pytox import Tox, ToxAV 9 | 10 | from time import sleep 11 | from os.path import exists 12 | from threading import Thread 13 | 14 | SERVER = ['54.199.139.199', 33445, '7F9C31FE850E97CEFD4C4591DF93FC757C7C12549DDD55F8EEAECC34FE76C029'] 15 | GROUP_BOT = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5' 16 | PWD = '' 17 | 18 | IRC_HOST = 'irc.freenode.net' 19 | IRC_PORT = 6667 20 | NAME = NICK = IDENT = REALNAME = 'SyncBot' 21 | 22 | CHANNEL = '#tox-ontopic' 23 | MEMORY_DB = 'memory.pickle' 24 | 25 | class AV(ToxAV): 26 | def __init__(self, core, max_calls): 27 | self.core = self.get_tox() 28 | self.cs = None 29 | self.call_type = self.TypeAudio 30 | 31 | def on_invite(self, idx): 32 | self.cs = self.get_peer_csettings(idx, 0) 33 | self.call_type = self.cs['call_type'] 34 | 35 | print('Incoming %s call from %d:%s ...' % ( 36 | 'video' if self.call_type == self.TypeVideo else 'audio', idx, 37 | self.core.get_name(self.get_peer_id(idx, 0)))) 38 | 39 | self.answer(idx, self.call_type) 40 | print('Answered, in call...') 41 | 42 | def on_start(self, idx): 43 | self.change_settings(idx, {'max_video_width': 1920, 44 | 'max_video_height': 1080}) 45 | self.prepare_transmission(idx, self.jbufdc * 2, self.VADd, 46 | True if self.call_type == self.TypeVideo else False) 47 | 48 | def on_end(self, idx): 49 | self.kill_transmission() 50 | 51 | print('Call ended') 52 | 53 | def on_peer_timeout(self, idx): 54 | self.stop_call() 55 | 56 | def on_audio_data(self, idx, size, data): 57 | sys.stdout.write('.') 58 | sys.stdout.flush() 59 | self.send_audio(idx, size, data) 60 | 61 | def on_video_data(self, idx, width, height, data): 62 | sys.stdout.write('*') 63 | sys.stdout.flush() 64 | self.send_video(idx, width, height, data) 65 | 66 | bot_toxname = 'SyncBot' 67 | 68 | class SyncBot(Tox): 69 | def __init__(self): 70 | if exists('data'): 71 | self.load_from_file('data') 72 | 73 | self.av = AV(self, 10) 74 | self.connect() 75 | self.set_name(bot_toxname) 76 | self.set_status_message("Send me a message with the word 'invite'") 77 | print('ID: %s' % self.get_address()) 78 | 79 | self.readbuffer = '' 80 | self.tox_group_id = None 81 | 82 | self.irc_init() 83 | self.memory = {} 84 | 85 | if exists(MEMORY_DB): 86 | with open(MEMORY_DB, 'r') as f: 87 | self.memory = pickle.load(f) 88 | 89 | def irc_init(self): 90 | self.irc = socket.socket() 91 | self.irc.connect((IRC_HOST, IRC_PORT)) 92 | self.irc.send('NICK %s\r\n' % NICK) 93 | self.irc.send('USER %s %s bla :%s\r\n' % (IDENT, IRC_HOST, REALNAME)) 94 | 95 | def connect(self): 96 | print('connecting...') 97 | self.bootstrap_from_address(SERVER[0], SERVER[1], SERVER[2]) 98 | 99 | def ensure_exe(self, func, args): 100 | count = 0 101 | THRESHOLD = 50 102 | 103 | while True: 104 | try: 105 | return func(*args) 106 | except: 107 | assert count < THRESHOLD 108 | count += 1 109 | for i in range(10): 110 | self.do() 111 | sleep(0.02) 112 | 113 | def loop(self): 114 | checked = False 115 | self.joined = False 116 | self.request = False 117 | 118 | try: 119 | while True: 120 | status = self.isconnected() 121 | if not checked and status: 122 | print('Connected to DHT.') 123 | checked = True 124 | try: 125 | self.bid = self.get_friend_id(GROUP_BOT) 126 | except: 127 | self.ensure_exe(self.add_friend, (GROUP_BOT, 'Hi')) 128 | self.bid = self.get_friend_id(GROUP_BOT) 129 | 130 | if checked and not status: 131 | print('Disconnected from DHT.') 132 | self.connect() 133 | checked = False 134 | 135 | readable, _, _ = select.select([self.irc], [], [], 0.01) 136 | 137 | if readable: 138 | self.readbuffer += self.irc.recv(4096) 139 | lines = self.readbuffer.split('\n') 140 | self.readbuffer = lines.pop() 141 | 142 | for line in lines: 143 | rx = re.match(r':(.*?)!.*? PRIVMSG %s :(.*?)\r' % 144 | CHANNEL, line, re.S) 145 | if rx: 146 | print('IRC> %s: %s' % rx.groups()) 147 | msg = '[%s]: %s' % rx.groups() 148 | content = rx.group(2) 149 | 150 | if content[1:].startswith('ACTION '): 151 | action = '[%s]: %s' % (rx.group(1), 152 | rx.group(2)[8:-1]) 153 | self.ensure_exe(self.group_action_send, 154 | (self.tox_group_id, action)) 155 | elif self.tox_group_id != None: 156 | self.ensure_exe(self.group_message_send, 157 | (self.tox_group_id, msg)) 158 | 159 | if content.startswith('^'): 160 | self.handle_command(content) 161 | 162 | l = line.rstrip().split() 163 | if l[0] == 'PING': 164 | self.irc_send('PONG %s\r\n' % l[1]) 165 | if l[1] == '376': 166 | self.irc.send('PRIVMSG NickServ :IDENTIFY %s %s\r\n' 167 | % (NICK, PWD)) 168 | self.irc.send('JOIN %s\r\n' % CHANNEL) 169 | 170 | self.do() 171 | except KeyboardInterrupt: 172 | self.save_to_file('data') 173 | 174 | def irc_send(self, msg): 175 | success = False 176 | while not success: 177 | try: 178 | self.irc.send(msg) 179 | success = True 180 | break 181 | except socket.error: 182 | self.irc_init() 183 | sleep(1) 184 | 185 | def on_connection_status(self, friendId, status): 186 | if not self.request and not self.joined \ 187 | and friendId == self.bid and status: 188 | print('Groupbot online, trying to join group chat.') 189 | self.request = True 190 | self.ensure_exe(self.send_message, (self.bid, 'invite')) 191 | 192 | def on_group_invite(self, friendid, type, data): 193 | if not self.joined: 194 | self.joined = True 195 | self.tox_group_id = self.join_groupchat(friendid, data) 196 | print('Joined groupchat.') 197 | 198 | def on_group_message(self, groupnumber, friendgroupnumber, message): 199 | name = self.group_peername(groupnumber, friendgroupnumber) 200 | if len(name) and name != NAME: 201 | print('TOX> %s: %s' % (name, message)) 202 | if message.startswith('>'): 203 | message = '\x0309%s\x03' % message 204 | 205 | self.irc_send('PRIVMSG %s :[%s]: %s\r\n' % 206 | (CHANNEL, name, message)) 207 | if message.startswith('^'): 208 | self.handle_command(message) 209 | 210 | def on_group_action(self, groupnumber, friendgroupnumber, action): 211 | name = self.group_peername(groupnumber, friendgroupnumber) 212 | if len(name) and name != NAME: 213 | print('TOX> %s: %s' % (name, action)) 214 | if action.startswith('>'): 215 | action = '\x0309%s\x03' % action 216 | self.irc_send('PRIVMSG %s :\x01ACTION [%s]: %s\x01\r\n' % 217 | (CHANNEL, name, action)) 218 | 219 | def on_friend_request(self, pk, message): 220 | print('Friend request from %s: %s' % (pk, message)) 221 | self.add_friend_norequest(pk) 222 | print('Accepted.') 223 | 224 | def on_friend_message(self, friendid, message): 225 | if message == 'invite': 226 | if not self.tox_group_id is None: 227 | print('Inviting %s' % self.get_name(friendid)) 228 | self.invite_friend(friendid, self.tox_group_id) 229 | return 230 | else: 231 | message = 'Waiting for GroupBot, please try again in 1 min.' 232 | 233 | self.ensure_exe(self.send_message, (friendid, message)) 234 | 235 | def send_both(self, content): 236 | self.ensure_exe(self.group_message_send, (self.tox_group_id, content)) 237 | self.irc_send('PRIVMSG %s :%s\r\n' % (CHANNEL, content)) 238 | 239 | def handle_command(self, cmd): 240 | cmd = cmd[1:] 241 | if cmd in ['syncbot', 'echobot']: 242 | self.send_both(self.get_address()) 243 | elif cmd == 'resync': 244 | sys.exit(0) 245 | elif cmd.startswith('remember '): 246 | args = cmd[9:].split(' ') 247 | subject = args[0] 248 | desc = ' '.join(args[1:]) 249 | self.memory[subject] = desc 250 | with open(MEMORY_DB, 'w') as f: 251 | pickle.dump(self.memory, f) 252 | self.send_both('Remembering ^%s: %s' % (subject, desc)) 253 | elif self.memory.has_key(cmd): 254 | self.send_both(self.memory[cmd]) 255 | 256 | 257 | t = SyncBot() 258 | t.loop() 259 | --------------------------------------------------------------------------------