work started on connecting Tox to XMPP

master
Michał 'rysiek' Woźniak 2015-02-06 14:49:34 +01:00
parent 4fbe434b71
commit aa93da9f37
3 changed files with 252 additions and 29 deletions

17
links
View File

@ -1,20 +1,9 @@
http://spectrum.im/
http://spectrum.im/documentation/tutorials/gateway_mode.html
https://github.com/hanzz/libtransport/blob/master/plugin/python/NetworkPlugin.py
http://delx.net.au/projects/pymsnt/devel.html
http://xmpppy.sourceforge.net/
https://github.com/normanr/irc-transport/blob/master/irc.py
https://github.com/normanr/irc-transport/blob/master/IRC-Transport-Howtouse.html
http://xmpppy.sourceforge.net/apidocs/index.html
https://www.ejabberd.im/node/5134
http://xmpp.org/extensions/xep-0114.html
http://sleekxmpp.com/getting_started/component.html
https://twistedmatrix.com/trac/wiki/XMPPServerArchitecture
https://github.com/aitjcize/tox-irc-sync/blob/master/tox-irc-sync.py
https://github.com/ntoll/xmppComponent/blob/master/todo_component.py
https://wiki.tox.im/Nodes
https://blog.tox.im/running-a-bootstrap-node/
https://blog.tox.im/running-a-bootstrap-node/
https://github.com/fritzy/SleekXMPP/wiki/Stanzas:-Presence

View File

@ -46,7 +46,7 @@ else:
def tdbg(txt):
print ("TOXMPP :: %s" % txt)
class EchoComponent(ComponentXMPP):
class ToXMPPComponent(ComponentXMPP):
"""
A simple SleekXMPP component that echoes messages.
"""
@ -77,7 +77,54 @@ class EchoComponent(ComponentXMPP):
self.add_event_handler('presence_subscribed', self.handle_presence_subscribed)
self.add_event_handler('presence_unsubscribe', self.handle_presence_unsubscribe)
self.add_event_handler('presence_unsubscribed', self.handle_presence_unsubscribed)
# log bound jid
tdbg('+-- bound jid is: %s' % self.boundjid.bare)
def _verify_toxid(self, toxid):
"""
Checking if a given string is a valid ToxID, i.e. is 76 chars
long hex (i.e. A-Fa-f0-9) string
https://libtoxcore.so/core_concepts.html
TODO: verifying the checksum?
"""
try:
# hex?
int(toxid, 16)
# 76 chars?
if len(toxid) != 76:
return False;
# doesn't contain 0x in front?
if s.lower()[0:2] == '0x':
return False;
# AOK
return True
except ValueError:
# apparently, not hex
return False
def _verify_jid(self, jid):
"""
Checking if a given (local) JID is correct, i.e. is either
self.boundjid.bare, or <valid-ToxID>@<self.boundjid.bare>.
"""
# this component's address?
if jid == self.boundjid.bare:
return True
# nah, <something>@<something>; let's make sure it's
# <valid-ToxID>@<self.boundjid.bare>!
if not self._verify_toxid(jid.user):
return False
if jid.domain != self.boundjid.bare:
return False
# we're done here.
return True
def message(self, msg):
"""
@ -98,38 +145,43 @@ class EchoComponent(ComponentXMPP):
# outgoing reply's 'from' JID.
msg.reply("Thanks for sending\n%(body)s" % msg).send()
# DEBUG
# handle a contact getting online
def handle_got_online(self, presence):
tdbg('got online: %s for %s' % (presence['from'], presence['to']))
roster_item = self.roster[presence['to']][presence['from']]
tdbg('+-- subscription: %s' % roster_item['subscription'])
# debug
tdbg('+-- subscription: %s' % roster_item['subscription'])
tdbg('roster\n%s\n' % self.roster);
# DEBUG
# handle a contact getting offline
def handle_got_offline(self, presence):
tdbg('got offline: %s for %s' % (presence['from'], presence['to']))
#roster_item = self.roster[presence['to']][presence['from']]
# debug
tdbg('roster\n%s\n' % self.roster);
# Why doesn't this work?
# handle a presence probe
def handle_presence_probe(self, presence):
tdbg('a presence probe from %s to %s; NOT replying...' % (presence['from'], presence['to']))
#roster_item = self.roster[presence['to']][presence['from']]
# debug
tdbg('roster\n%s\n' % self.roster);
# Populate the presence reply with the agent's current status.
#self.sendPresence(pto=presence['from'], pfrom=presence['to'], pstatus="Busy studying XMPP", pshow="chat")
# handle subscription
# handle subscription notification
def handle_presence_subscribe(self, presence):
tdbg('a subscription request from %s for %s' % (presence['from'], presence['to']))
roster_item = self.roster[presence['to']][presence['from']]
# is the "to" JID sane?
# it should either be a <valid ToxID>@<self.boundjid.bare>
if not self._verify_jid(presence['to']):
tdbg('+-- jid fail! unauthorizing...')
roster_item.unauthorize()
return False;
# If the subscription request is accepted.
tdbg('+-- accepting...')
roster_item.authorize()
@ -149,9 +201,17 @@ class EchoComponent(ComponentXMPP):
tdbg('+-- subscription: %s' % roster_item['subscription'])
# we want to be subscribed to all JIDs that allow us to
# as long as they're subscribing to sane JIDs on our side
# so, reality check
if not self._verify_jid(presence['to']):
tdbg('+-- jid fail! unauthorizing...')
roster_item.unauthorize()
return False;
# we're here, "our" JID is sane, let's roll
if roster_item['subscription'] not in ('to', 'both'):
roster_item.subscribe()
tdbg('+-- subscription: %s' % roster_item['subscription'])
roster_item.subscribe()
tdbg('+-- subscribing to: %s' % roster_item['subscription'])
# pretty simple, we accept all and show our status to all
roster_item.send_presence()
@ -234,11 +294,11 @@ if __name__ == '__main__':
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
# Setup the EchoComponent and register plugins. Note that while plugins
# Setup the ToXMPPComponent and register plugins. Note that while plugins
# may have interdependencies, the order in which you register them does
# not matter.
xmpp = EchoComponent(opts.jid, opts.password, opts.server, opts.port)
xmpp.register_plugin('YAMLRoster', module=yaml_roster) # YAML Roster backend
xmpp = ToXMPPComponent(opts.jid, opts.password, opts.server, opts.port)
xmpp.register_plugin('YAMLRoster', module=yaml_roster) # YAML Roster flat-file backend
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0060') # PubSub

174
toxmpp/data/toxnode.py Normal file
View File

@ -0,0 +1,174 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# @file echo-no-av.py
# @author Michał "rysiek" Woźniak <rysiek@hackerspace.pl>
# @author Wei-Ning Huang (AZ) <aitjcize@gmail.com>
#
# Copyright (C) 2015 Michał "rysiek" Woźniak <rysiek@hackerspace.pl>
# Copyright (C) 2013 - 2014 Wei-Ning Huang (AZ) <aitjcize@gmail.com>
"""
ToXMPP: The Tox<->XMPP Transport/Gateway SleekXMPP-based Component
Copyright (C) 2015 Michał "rysiek" Woźniak <rysiek@hackerspace.pl>
ToxID: 3FA2E5273F0C368576FE120B374664E3B41E2CDF21639AFED3DC301490FFB01FAAA47B78D5F4
This file is part of ToXMPP.
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 3 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/>.
"""
from __future__ import print_function
import sys
from pytox import Tox
from time import sleep
from os.path import exists
import random
# local debug server when running under Docker
DEBUG_SERVER = [
"172.17.42.1",
33445,
"3FA2E5273F0C368576FE120B374664E3B41E2CDF21639AFED3DC301490FFB01FAAA47B78D5F4" # change to your ID when testing!
],
class ToxNode(Tox):
# where should we save Tox data to?
datafile='echo.data'
# servers
servers= [
# public Tox bootstraping servers
# IDs here are 64 characters long as they are pure public keys, without nospam+checksum parts:
# https://libtoxcore.so/core_concepts.html
# https://wiki.tox.im/Servers
[
"80.232.246.79",
33445,
"0B8DCEAA7BDDC44BB11173F987CAE3566A2D7057D8DD3CC642BD472B9391002A"
],
[
"178.21.112.187",
33445,
"4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057"
],
[
"80.232.246.79",
33445,
"0B8DCEAA7BDDC44BB11173F987CAE3566A2D7057D8DD3CC642BD472B9391002A"
],
]
# has the connection status been checked lately
checked = False
# init
def __init__(self, datafile='echo.data', server=False, name="EchoBot"):
# let parent do its thing
Tox.__init__()
# set the datafile name
self.datafile = datafile
# get the data, if the datafile exists
if exists(self.datafile):
self.load_from_file(self.datafile)
# do we have a server kwarg?
if server:
# use that
self.servers = [server]
# yay, a name
self.set_name(name)
# debug/info
print('ID: %s' % self.get_address())
self.connect()
# we don't want audio-video for now
#self.av = AV(self, 1)
# handle object teardown
def __del__(self):
# just save the data
self.save_to_file(self.datafile)
# make the connection
def connect(self):
print('connecting...')
# use random from the available servers
# if a single server has been provided, we'll use that anyway
server = random.choice(self.servers)
# bootstrap!
self.bootstrap_from_address(server[0], server[1], server[2])
# this has to be called ~20 times per second
def do(self):
# check the status
status = self.isconnected()
# should we inform about the fact that we're connected?
if not checked and status:
print('Connected to DHT.')
checked = True
# should we connect, and inform about the fact that we're disconnected?
if checked and not status:
print('Disconnected from DHT.')
self.connect()
checked = False
# run the underlying Tox routine
self.do()
# handling friend request
def on_friend_request(self, pk, message):
print('Friend request from %s: %s' % (pk, message))
self.add_friend_norequest(pk)
print('Accepted.')
# handling a message
def on_friend_message(self, friendId, message):
name = self.get_name(friendId)
print('%s: %s' % (name, message))
print('EchoBot: %s' % message)
self.send_message(friendId, message)
if __name__ == '__main__':
# Setup the command line arguments.
# get the datafile from cmdline args, if available
if len(sys.argv) == 2:
t = EchoBot(datafile=sys.argv[1])
else:
t = EchoBot()
# run
while True:
try:
t.do()
sleep(0.01)
except KeyboardInterrupt:
del t