|
@@ -15,18 +15,26 @@ import os
|
|
|
import random
|
|
|
import shelve
|
|
|
import subprocess
|
|
|
+import sys
|
|
|
import time
|
|
|
|
|
|
import dns.resolver, dns.reversename
|
|
|
import SocketServer
|
|
|
import threading
|
|
|
|
|
|
+# ensure our directory is on path (in order to load batcave module)
|
|
|
+__my_dir = os.path.dirname(__file__)
|
|
|
+if __my_dir not in sys.path:
|
|
|
+ sys.path.append(__my_dir)
|
|
|
+
|
|
|
+from batcave import BatcaveClient
|
|
|
+
|
|
|
msgserver = None
|
|
|
peers_repo = None
|
|
|
|
|
|
nodeaccess = None
|
|
|
|
|
|
-alfred_method = None
|
|
|
+__batcave = None
|
|
|
|
|
|
ffpb_resolver = dns.resolver.Resolver()
|
|
|
ffpb_resolver.nameservers = ['10.132.254.53']
|
|
@@ -80,7 +88,7 @@ class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
|
|
|
def setup(bot):
|
|
|
"""Called by willie upon loading this plugin."""
|
|
|
|
|
|
- global msgserver, peers_repo, alfred_method, nodeaccess
|
|
|
+ global __batcave, msgserver, peers_repo, nodeaccess
|
|
|
|
|
|
# signal begin of setup routine
|
|
|
bot.memory['ffpb_in_setup'] = True
|
|
@@ -120,13 +128,8 @@ def setup(bot):
|
|
|
msgserver_thread.daemon = True
|
|
|
msgserver_thread.start()
|
|
|
|
|
|
- # initially fetch ALFRED data
|
|
|
- alfred_method = bot.config.ffpb.alfred_method
|
|
|
- if not 'alfred_data' in bot.memory:
|
|
|
- bot.memory['alfred_data'] = {}
|
|
|
- if not 'alfred_update' in bot.memory:
|
|
|
- bot.memory['alfred_update'] = datetime(1970, 1, 1, 23, 42)
|
|
|
- ffpb_updatealfred(bot)
|
|
|
+ # initialize BATCAVE
|
|
|
+ __batcave = BatcaveClient(bot.config.ffpb.batcave_url)
|
|
|
|
|
|
# signal end of setup routine
|
|
|
bot.memory['ffpb_in_setup'] = False
|
|
@@ -367,7 +370,7 @@ def ffpb_ensurenodeid(nodedata):
|
|
|
return result
|
|
|
|
|
|
|
|
|
-def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
+def ffpb_findnode(name, allow_fuzzymatching=True):
|
|
|
"""helper: try to identify the node the user meant by the given name"""
|
|
|
|
|
|
# no name, no node
|
|
@@ -382,30 +385,19 @@ def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
name = name[1:-1]
|
|
|
allow_fuzzymatching = False
|
|
|
|
|
|
- names = {}
|
|
|
-
|
|
|
- if not alfred_data is None:
|
|
|
- # try to match MAC
|
|
|
- m = re.search("^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]$", name)
|
|
|
- if not m is None:
|
|
|
- mac = m.group(0).lower()
|
|
|
- if mac in alfred_data:
|
|
|
- return ffpb_ensurenodeid(alfred_data[mac])
|
|
|
-
|
|
|
- # try to find alias MAC in ALFRED data
|
|
|
- for nodeid in alfred_data:
|
|
|
- node = alfred_data[nodeid]
|
|
|
- if "network" in node:
|
|
|
- if node["network"].get("mac", "").lower() == mac:
|
|
|
- return ffpb_ensurenodeid(node)
|
|
|
- if "mesh_interfaces" in node["network"]:
|
|
|
- for mim in node["network"]["mesh_interfaces"]:
|
|
|
- if mim.lower() == mac:
|
|
|
- return ffpb_ensurenodeid(node)
|
|
|
+ # try to match MAC
|
|
|
+ m = re.search("^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]$", name)
|
|
|
+ if not m is None:
|
|
|
+ mac = m.group(0).lower()
|
|
|
+ node = __batcave.find_node_by_mac(mac)
|
|
|
+
|
|
|
+ if node is not None:
|
|
|
+ return node
|
|
|
|
|
|
+ else:
|
|
|
nodeid = mac.replace(':', '').lower()
|
|
|
return {
|
|
|
- 'nodeid': nodeid,
|
|
|
+ 'node_id': nodeid,
|
|
|
'hostname': '?-' + nodeid,
|
|
|
'network': {
|
|
|
'addresses': [mac2ipv6(mac, 'fdca:ffee:ff12:132:')],
|
|
@@ -416,15 +408,10 @@ def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
},
|
|
|
}
|
|
|
|
|
|
- # look through the ALFRED peers
|
|
|
- for nodeid in alfred_data:
|
|
|
- node = alfred_data[nodeid]
|
|
|
- if 'hostname' in node:
|
|
|
- h = node['hostname']
|
|
|
- if h.lower() == name.lower():
|
|
|
- return node
|
|
|
- else:
|
|
|
- names[h] = nodeid
|
|
|
+ # try to find by NAME
|
|
|
+ node = __batcave.find_node_by_name(name, fuzzymatch=allow_fuzzymatching)
|
|
|
+ if node is not None:
|
|
|
+ return __batcave.get_node(node['id'])
|
|
|
|
|
|
# not found in ALFRED data -> try peers_repo
|
|
|
if not peers_repo is None:
|
|
@@ -458,18 +445,6 @@ def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
},
|
|
|
}
|
|
|
|
|
|
- # do a similar name lookup in the ALFRED data
|
|
|
- if allow_fuzzymatching and not alfred_data is None:
|
|
|
- allnames = [x for x in names]
|
|
|
- possibilities = difflib.get_close_matches(name, allnames, cutoff=0.75)
|
|
|
- print('findnode: Fuzzy matching \'{0}\' got {1} entries: {2}'.format(
|
|
|
- name,
|
|
|
- len(possibilities), ', '.join(possibilities))
|
|
|
- )
|
|
|
- if len(possibilities) == 1:
|
|
|
- # if we got exactly one candidate that might be it
|
|
|
- return ffpb_ensurenodeid(alfred_data[names[possibilities[0]]])
|
|
|
-
|
|
|
# none of the above was able to identify the requested node
|
|
|
return None
|
|
|
|
|
@@ -482,14 +457,7 @@ def ffpb_findnode_from_botparam(bot, name, ensure_recent_alfreddata=True):
|
|
|
bot.reply("Grün.")
|
|
|
return None
|
|
|
|
|
|
- alfred_data = get_alfred_data(bot, ensure_recent_alfreddata)
|
|
|
- if ensure_recent_alfreddata and alfred_data is None:
|
|
|
- if not bot is None:
|
|
|
- bot.say('Informationen sind ausverkauft bzw. veraltet, ' +
|
|
|
- 'daher sage ich mal lieber nichts zu \'' + name + '\'.')
|
|
|
- return None
|
|
|
-
|
|
|
- node = ffpb_findnode(name, alfred_data)
|
|
|
+ node = ffpb_findnode(name)
|
|
|
if node is None:
|
|
|
if not bot is None:
|
|
|
bot.say("Kein Plan wer oder was mit '" + name + "' gemeint ist :(")
|
|
@@ -507,142 +475,48 @@ def mac2ipv6(mac, prefix=None):
|
|
|
return result
|
|
|
|
|
|
|
|
|
-@willie.module.interval(30)
|
|
|
-def ffpb_updatealfred(bot):
|
|
|
- """Aktualisiere ALFRED-Daten"""
|
|
|
-
|
|
|
- if alfred_method is None or alfred_method == "None":
|
|
|
+def ffpb_notify_newly_seen_nodes(bot, new):
|
|
|
+ if not isinstance(bot, dict):
|
|
|
return
|
|
|
-
|
|
|
- updated = None
|
|
|
- if alfred_method == "exec":
|
|
|
- rawdata = subprocess.check_output(['alfred-json', '-z', '-r', '158'])
|
|
|
- updated = datetime.now()
|
|
|
-
|
|
|
- elif alfred_method.startswith("http"):
|
|
|
- try:
|
|
|
- rawdata = urllib2.urlopen(alfred_method)
|
|
|
- except urllib2.URLError as err:
|
|
|
- print("Failed to download ALFRED data:" + str(err))
|
|
|
- return
|
|
|
- last_modified = rawdata.info().getdate_tz("Last-Modified")
|
|
|
- updated = datetime.fromtimestamp(mktime_tz(last_modified))
|
|
|
-
|
|
|
- else:
|
|
|
- print("Unknown ALFRED data method '{0}', cannot load new data.".format(alfred_method))
|
|
|
- alfred_data = None
|
|
|
+ if len(new) == 0 or bot.memory['ffpb_in_setup']:
|
|
|
return
|
|
|
|
|
|
- try:
|
|
|
- alfred_data = json.load(rawdata)
|
|
|
- #print("Fetched new ALFRED data:", len(alfred_data), "entries")
|
|
|
-
|
|
|
- except ValueError as err:
|
|
|
- print("Failed to parse ALFRED data: " + str(err))
|
|
|
- return
|
|
|
-
|
|
|
- bot.memory['alfred_data'] = alfred_data
|
|
|
- bot.memory['alfred_update'] = updated
|
|
|
-
|
|
|
- seen_nodes = bot.memory.get('seen_nodes', None)
|
|
|
- if not seen_nodes is None:
|
|
|
- new = []
|
|
|
- for nodeid in alfred_data:
|
|
|
- nodeid = str(nodeid)
|
|
|
- if not nodeid in seen_nodes:
|
|
|
- seen_nodes[nodeid] = updated
|
|
|
- new.append((nodeid, alfred_data[nodeid]['hostname']))
|
|
|
- print('First time seen: ' + str(nodeid))
|
|
|
- if len(new) > 0 and not bot.memory['ffpb_in_setup']:
|
|
|
- action_msg = None
|
|
|
- if len(new) == 1:
|
|
|
- action_msg = random.choice((
|
|
|
- 'bemerkt den neuen Knoten {0}',
|
|
|
- 'entdeckt {0}',
|
|
|
- 'reibt sich die Augen und erblickt einen verpackungsfrischen Knoten {0}',
|
|
|
- u'heißt {0} im Mesh willkommen',
|
|
|
- 'freut sich, dass {0} aufgetaucht ist',
|
|
|
- 'traut seinen Augen kaum. {0} sagt zum ersten Mal: Hallo Freifunk Paderborn',
|
|
|
- u'sieht die ersten Herzschläge von {0}',
|
|
|
- u'stellt einen großen Pott Heißgetränk zu {0} und fragt ob es hier Meshpartner gibt.',
|
|
|
- )).format('\'' + str(new[0][1]) + '\'')
|
|
|
-
|
|
|
- # try to fetch location from BATCAVE in order to add a geomap URL
|
|
|
- location = ffpb_get_batcave_nodefield(str.replace(new[0][0], ':', ''), 'location')
|
|
|
- if not location is None:
|
|
|
- action_msg += ' http://map.paderborn.freifunk.net/geomap.html?lat=' + location['latitude'] + '&lon=' + location['longitude']
|
|
|
- else:
|
|
|
- action_msg = random.choice((
|
|
|
- 'bemerkt die neuen Knoten {0} und {1}',
|
|
|
- 'hat {0} und {1} entdeckt',
|
|
|
- 'bewundert {0} sowie {1}',
|
|
|
- 'freut sich, dass {0} und {1} nun auch online sind',
|
|
|
- u'heißt {0} und {1} im Mesh willkommen',
|
|
|
- 'fragt sich ob die noch jungen Herzen von {0} und {1} synchron schlagen',
|
|
|
- ))
|
|
|
- all_but_last = [str(x[1]) for x in new[0:-1]]
|
|
|
- last = str(new[-1][1])
|
|
|
- action_msg = action_msg.format(
|
|
|
- '\'' + '\', \''.join(all_but_last) + '\'',
|
|
|
- '\'' + last + '\''
|
|
|
- )
|
|
|
- action_target = bot.config.ffpb.msg_target
|
|
|
- if not bot.config.ffpb.msg_target_public is None:
|
|
|
- action_target = bot.config.ffpb.msg_target_public
|
|
|
- bot.msg(action_target, '\x01ACTION %s\x01' % action_msg)
|
|
|
-
|
|
|
-
|
|
|
-def get_alfred_data(bot, ensure_not_outdated=True):
|
|
|
- """
|
|
|
- Retrieves the stored alfred_data and optionally checks
|
|
|
- that it has been updated no more than 5 minutes ago.
|
|
|
- """
|
|
|
-
|
|
|
- alfred_data = bot.memory.get('alfred_data', None)
|
|
|
- alfred_update = bot.memory.get('alfred_update', 0)
|
|
|
-
|
|
|
- if alfred_data is None:
|
|
|
- return None
|
|
|
-
|
|
|
- if ensure_not_outdated:
|
|
|
- timeout = datetime.now() - timedelta(minutes=5)
|
|
|
- is_outdated = timeout > alfred_update
|
|
|
- if is_outdated:
|
|
|
- return None
|
|
|
-
|
|
|
- return alfred_data
|
|
|
-
|
|
|
-
|
|
|
-def ffpb_get_batcave_nodefield(nodeid, field):
|
|
|
- """Query the given field for the given nodeid from the BATCAVE."""
|
|
|
-
|
|
|
- raw_data = None
|
|
|
- try:
|
|
|
- # query BATCAVE for node's field
|
|
|
- raw_data = urllib2.urlopen('http://[fdca:ffee:ff12:a255::253]:8888/node/{0}/{1}'.format(nodeid, field))
|
|
|
- except urllib2.URLError as err:
|
|
|
- print('Failed to contact BATCAVE for \'{0}\'->\'{1}\': {2}'.format(
|
|
|
- nodeid, field, err))
|
|
|
- return None
|
|
|
-
|
|
|
- try:
|
|
|
- return json.load(raw_data)
|
|
|
-
|
|
|
- except ValueError as err:
|
|
|
- print('Could not parse BATCAVE\'s response as JSON for \'{0}\'->\'{1}\':'.format(nodeid, field, err))
|
|
|
- return None
|
|
|
-
|
|
|
-
|
|
|
-@willie.module.commands('debug-alfred')
|
|
|
-def ffpb_debug_alfred(bot, trigger):
|
|
|
- """Show statistics of available ALFRED data."""
|
|
|
-
|
|
|
- alfred_data = get_alfred_data(bot)
|
|
|
- if alfred_data is None:
|
|
|
- bot.say("Keine ALFRED-Daten vorhanden.")
|
|
|
+ action_msg = None
|
|
|
+ if len(new) == 1:
|
|
|
+ action_msg = random.choice((
|
|
|
+ 'bemerkt den neuen Knoten {0}',
|
|
|
+ 'entdeckt {0}',
|
|
|
+ 'reibt sich die Augen und erblickt einen verpackungsfrischen Knoten {0}',
|
|
|
+ u'heißt {0} im Mesh willkommen',
|
|
|
+ 'freut sich, dass {0} aufgetaucht ist',
|
|
|
+ 'traut seinen Augen kaum. {0} sagt zum ersten Mal: Hallo Freifunk Paderborn',
|
|
|
+ u'sieht die ersten Herzschläge von {0}',
|
|
|
+ u'stellt einen großen Pott Heißgetränk zu {0} und fragt ob es hier Meshpartner gibt.',
|
|
|
+ )).format('\'' + str(new[0][1]) + '\'')
|
|
|
+
|
|
|
+ # try to fetch location from BATCAVE in order to add a geomap URL
|
|
|
+ location = __batcave.get_nodefield(str.replace(new[0][0], ':', ''), 'location')
|
|
|
+ if not location is None:
|
|
|
+ action_msg += ' http://map.paderborn.freifunk.net/geomap.html?lat=' + location['latitude'] + '&lon=' + location['longitude']
|
|
|
else:
|
|
|
- bot.say("ALFRED Daten: count={0} lastupdate={1}".format(
|
|
|
- len(alfred_data), bot.memory['alfred_update']))
|
|
|
+ action_msg = random.choice((
|
|
|
+ 'bemerkt die neuen Knoten {0} und {1}',
|
|
|
+ 'hat {0} und {1} entdeckt',
|
|
|
+ 'bewundert {0} sowie {1}',
|
|
|
+ 'freut sich, dass {0} und {1} nun auch online sind',
|
|
|
+ u'heißt {0} und {1} im Mesh willkommen',
|
|
|
+ 'fragt sich ob die noch jungen Herzen von {0} und {1} synchron schlagen',
|
|
|
+ ))
|
|
|
+ all_but_last = [str(x[1]) for x in new[0:-1]]
|
|
|
+ last = str(new[-1][1])
|
|
|
+ action_msg = action_msg.format(
|
|
|
+ '\'' + '\', \''.join(all_but_last) + '\'',
|
|
|
+ '\'' + last + '\''
|
|
|
+ )
|
|
|
+ action_target = bot.config.ffpb.msg_target
|
|
|
+ if not bot.config.ffpb.msg_target_public is None:
|
|
|
+ action_target = bot.config.ffpb.msg_target_public
|
|
|
+ bot.msg(action_target, '\x01ACTION %s\x01' % action_msg)
|
|
|
|
|
|
|
|
|
@willie.module.interval(60)
|
|
@@ -783,9 +657,9 @@ def ffpb_ping(bot, trigger=None, target_name=None, reply_directly=True):
|
|
|
if node is None:
|
|
|
return None
|
|
|
|
|
|
- # get the first non-linklocal address from the node
|
|
|
- target = [x for x in node["network"]["addresses"]
|
|
|
- if not x.lower().startswith("fe80:")][0]
|
|
|
+ # derive node address from MAC
|
|
|
+ node_mac = node.get('mac')
|
|
|
+ target = mac2ipv6(node_mac, 'fdca:ffee:ff12:132:')
|
|
|
target_alias = node["hostname"]
|
|
|
|
|
|
# execute the actual ping and reply the result
|
|
@@ -831,7 +705,7 @@ def ffpb_nodemesh(bot, trigger):
|
|
|
return
|
|
|
|
|
|
# query BATCAVE for node's neighbours (result is a list of MAC addresses)
|
|
|
- cave_result = ffpb_get_batcave_nodefield(nodeid, 'neighbours')
|
|
|
+ cave_result = node['neighbours']
|
|
|
if cave_result is None:
|
|
|
msg = 'Hm, scheinbar liegen zu \'{0}\' keine Daten vor. ' + \
|
|
|
'Klingt komisch, ist aber so.'
|
|
@@ -840,7 +714,7 @@ def ffpb_nodemesh(bot, trigger):
|
|
|
|
|
|
# query BATCAVE for neighbour's names
|
|
|
data = '&'.join([str(n) for n in cave_result])
|
|
|
- req = urllib2.urlopen('http://[fdca:ffee:ff12:a255::253]:8888/idmac2name', data)
|
|
|
+ req = urllib2.urlopen(bot.config.ffpb.batcave_url + 'idmac2name', data)
|
|
|
|
|
|
# filter out duplicate names
|
|
|
neighbours = set()
|
|
@@ -904,9 +778,9 @@ def ffpb_remoteexec(bot, trigger):
|
|
|
if not playitsafe(bot, trigger, via_channel=True, node=node):
|
|
|
return
|
|
|
|
|
|
- # use the node's first non-linklocal address
|
|
|
- naddrs = node["network"]["addresses"]
|
|
|
- target = [x for x in naddrs if not x.lower().startswith("fe80:")][0]
|
|
|
+ # derive target from node's MAC
|
|
|
+ node_mac = node.get('mac')
|
|
|
+ target = mac2ipv6(node_mac, 'fdca:ffee:ff12:132:')
|
|
|
target_alias = node["hostname"]
|
|
|
|
|
|
# assemble SSH command
|