├── contacts.py ├── emptymachines.json ├── person.py ├── .gitignore ├── __init__.py ├── transports ├── __init__.py ├── simple_http_json.py └── basic_tcp_json.py ├── settings.json ├── machines.json ├── settingsalt.json ├── machinesalt.json ├── testit.sh ├── autoserversettings.json ├── requirements.txt ├── apps ├── basic_chat.py └── forums.py ├── settings.py ├── packet.py ├── debugprint.py ├── dprint.py ├── readme.md ├── network.py ├── machine.py ├── main.py ├── synclist.py ├── websockets_ui.py ├── hclient.html └── html_ui.py /contacts.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /emptymachines.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /person.py: -------------------------------------------------------------------------------- 1 | 2 | class Person(): 3 | name 4 | pkey 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .contacts.py.kate-swp 2 | .idea/ 3 | __pycache__/ 4 | env/ 5 | networking_tests/ 6 | transports/__pycache__/ 7 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | -------------------------------------------------------------------------------- /transports/__init__.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "machine_name": "ALPHA01", 3 | "pkey": "alphas_key", 4 | "serve_port": 9999, 5 | "machines_file": "machines.json", 6 | "ui_port":12035 7 | } -------------------------------------------------------------------------------- /machines.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"Ωmega99", 4 | "ip":"127.0.0.1", 5 | "port":9998, 6 | "pkey":"omegas_key", 7 | "pkeyhash":"omegas_key_hash" 8 | } 9 | ] -------------------------------------------------------------------------------- /settingsalt.json: -------------------------------------------------------------------------------- 1 | { 2 | "machine_name": "Ωmega99", 3 | "pkey": "omegas_key", 4 | "serve_port": 9998, 5 | "machines_file": "machinesalt.json", 6 | "ui_port":12034 7 | } -------------------------------------------------------------------------------- /machinesalt.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"ALPHA01", 4 | "ip":"127.0.0.1", 5 | "port": 9999, 6 | "pkey":"alphas_key", 7 | "pkeyhash":"alphas_key_hash" 8 | } 9 | ] -------------------------------------------------------------------------------- /testit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source env/bin/activate 3 | python main.py --test-mode --no-auto & 4 | python main.py --settingsfile settingsalt.json --test-mode 5 | 6 | kill $! 7 | -------------------------------------------------------------------------------- /autoserversettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "machine_name": "AutoServer", 3 | "pkey": "autoserver_key", 4 | "serve_port": 9999, 5 | "send_port":9998, 6 | "machines_file": "emptymachines.json", 7 | "ui_port":12035 8 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | constantly==15.1.0 2 | defopt==2.0.1 3 | docutils==0.12 4 | incremental==16.10.1 5 | pkg-resources==0.0.0 6 | pockets==0.3 7 | requests==2.11.1 8 | six==1.10.0 9 | sphinxcontrib-napoleon==0.5.3 10 | Twisted==16.5.0 11 | zope.interface==4.3.2 12 | -------------------------------------------------------------------------------- /apps/basic_chat.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | 7 | import network, machine 8 | import settings 9 | 10 | messagelist = [] 11 | 12 | 13 | def init(): 14 | network.hook_type("basic_chat", got_data) 15 | 16 | def got_data(data, machine, meta): 17 | data=data['data'] 18 | print("GOTCHAT", settings.machine_name, data, machine.name) 19 | messagelist.append(machine.name + ": " + data['message']) 20 | 21 | 22 | def send_message(message): 23 | messagelist.append(settings.machine_name + ": " + message) 24 | machine.send_all({"message": message}, {"type":"basic_chat"}) 25 | 26 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | machine_name = "unknown" 7 | pkey = "unknown" 8 | running = True 9 | ui_port = 8080 10 | auto_accept = True 11 | ext_ip = None 12 | serve_port = 9999 13 | 14 | try: 15 | from urllib.request import urlopen 16 | ext_ip = urlopen('http://ip.42.pl/raw').read() 17 | print("detected IP as", ext_ip) 18 | except: 19 | print("could not get external IP") 20 | 21 | def load_data(data): 22 | global machine_name, pkey, ui_port, serve_port 23 | machine_name = data['machine_name'] 24 | pkey = data['pkey'] 25 | ui_port = data['ui_port'] 26 | serve_port = data['serve_port'] 27 | -------------------------------------------------------------------------------- /packet.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | import json 7 | 8 | class Packet: 9 | def __init__(self): 10 | pass 11 | def from_dicts(self, data, meta): 12 | self.data_string = json.dumps(data) 13 | self.meta_string = json.dumps(meta) 14 | self.data = data 15 | self.meta = meta 16 | def from_strings(self, data, meta): 17 | self.data = json.loads(data) 18 | self.meta = json.loads(meta) 19 | self.data_string = data 20 | self.meta_string = meta 21 | 22 | 23 | data = { 24 | "anythinggoes": "in data", 25 | "title": "test" 26 | } 27 | meta = { 28 | "type": "synclistitem", 29 | "subtype": "?", 30 | "signature": "123", 31 | "hash": "456" 32 | } -------------------------------------------------------------------------------- /debugprint.py: -------------------------------------------------------------------------------- 1 | __author__ = 'chozabu' 2 | 3 | #based on http://stackoverflow.com/a/1620686/445831 4 | import sys 5 | import traceback 6 | 7 | printcount = 0 8 | 9 | class TracePrints(object): 10 | def __init__(self): 11 | self.stdout = sys.stdout 12 | self.newline = True 13 | def write(self, s): 14 | global printcount 15 | from settings import machine_name 16 | if self.newline: 17 | #self.stdout.write(s+"\n") 18 | stack = traceback.extract_stack() 19 | stackout = stack[-2]#[0:10] 20 | #self.stdout.write('--"'+machine_name+'"'+stackout[0]+'", line '+str(stackout[1])+', in '+stackout[2]+'\n') 21 | filename = stackout[0].split('/')[-1] 22 | ministack = filename + ':' + str(stackout[1]) 23 | printcount +=1 24 | prefix = "(" + machine_name + " - " + str(printcount) + ' ' + ministack + ")" 25 | prefix+= " " * (40-len(prefix)) 26 | strs = prefix + str(s)#+"\n" 27 | else: 28 | strs = s 29 | self.stdout.write(strs) 30 | if s == '\n': 31 | self.newline=True 32 | else: 33 | self.newline=False 34 | 35 | sys.stdout = TracePrints() 36 | -------------------------------------------------------------------------------- /dprint.py: -------------------------------------------------------------------------------- 1 | """dprint.py: prints with.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | from inspect import currentframe, getframeinfo 7 | import inspect 8 | 9 | 10 | 11 | def dprint(*args): 12 | print(*args) 13 | return 14 | from settings import machine_name 15 | (frame, filename, line_number, 16 | function_name, lines, index) = inspect.getouterframes(inspect.currentframe())[3] 17 | pa = "---" + machine_name + '\033[91m' + filename + ":" + str(line_number) + " " + function_name + '\x1b[0m' 18 | (frame, filename, line_number, 19 | function_name, lines, index) = inspect.getouterframes(inspect.currentframe())[2] 20 | pb = "---"+machine_name + '\033[91m' + filename + ":" + str(line_number) + " " + function_name + '\x1b[0m' 21 | (frame, filename, line_number, 22 | function_name, lines, index) = inspect.getouterframes(inspect.currentframe())[1] 23 | print() 24 | print(pa) 25 | print(pb) 26 | print(machine_name + '\033[91m' + filename + ":" + str(line_number) + " " + function_name + '\x1b[0m') 27 | print(str(args)) 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PyRock 2 | 3 | PyRock is aiming to be a high level authorised decentral networking solution 4 | 5 | ### unusably early stages 6 | 7 | ####running 8 | 9 | create a venv, activate and install requirements 10 | 11 | virtualenv -p python3 env 12 | . env/bin/activate 13 | pip install -r requirements 14 | 15 | you can now run main.py with 16 | 17 | python main.py 18 | 19 | or run two instances with ./testit.sh 20 | 21 | ###current features 22 | 23 | - basic sockets networking backend 24 | - basic HTTP POST networking backend 25 | - machine contacts (from a file) 26 | - start of synclist, high level sync/additive json store 27 | - very basic HTML UI 28 | - start of websockets interface for UI 29 | - start of forums 30 | - instant messenger (broadcast) 31 | 32 | ###todo 33 | 34 | - human contacts 35 | - keypairs 36 | - data storage 37 | - API/IPC/Plugins 38 | - Solidify protocol 39 | - global connection graph 40 | - more alternate network backends 41 | - alternate programming language implementations 42 | 43 | ###more todo 44 | - blogs 45 | - comics 46 | - file sharing 47 | - virtual LAN 48 | - turtle messaging 49 | - games 50 | - voting 51 | - www 52 | - central nameserver -------------------------------------------------------------------------------- /apps/forums.py: -------------------------------------------------------------------------------- 1 | #forum app puesdo code: 2 | 3 | import synclist 4 | import network 5 | import settings 6 | 7 | forums = [] 8 | forums_dict = {} 9 | 10 | 11 | def init(root): 12 | network.hook_type("forum", got_data) 13 | 14 | def got_data(data, machine, meta): 15 | print(settings.machine_name, data, machine.name) 16 | 17 | def create_forum(forumname): 18 | print("creating forum", forumname) 19 | if forumname in forums_dict: 20 | new_forum = forums_dict[forumname] 21 | synclist.subscribe_list(new_forum.id) 22 | else: 23 | new_forum = Forum(forumname, forumname) 24 | forums.append(new_forum) 25 | 26 | forums_dict[forumname] = new_forum 27 | 28 | class Forum(): 29 | def __init__(self, name="testforum", id="testid"): 30 | self.items = synclist.SyncList(ownid=id) 31 | self.name = name 32 | self.id = id 33 | def create_post(self, item): 34 | self.items.publish_item(item) 35 | self.items.sync_to_all() 36 | def get_posts(self): 37 | return self.items.items_by_recv_date 38 | 39 | def testing(): 40 | p1 = createPost(myforum, title="testingtitle", body="something") 41 | createPost(myforum, title="testingtitle", body="something", parent=p1) 42 | -------------------------------------------------------------------------------- /network.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | from transports import basic_tcp_json 7 | from transports import simple_http_json 8 | import settings 9 | import machine 10 | import json 11 | 12 | callbacks = {} 13 | 14 | backends = [basic_tcp_json, simple_http_json] 15 | 16 | def send_packet(HOST, PORT, data, meta): 17 | meta['pkey'] = settings.pkey 18 | packet = json.dumps({"data":data, "meta":meta}) 19 | for b in backends: 20 | ret = basic_tcp_json.send_data(HOST, PORT, packet) 21 | if ret[0]: 22 | break 23 | if not ret[0]: 24 | print("failed to send packet on all backends",(HOST,PORT, packet)) 25 | #ret = simple_http_json.send_data(HOST,PORT, packet) 26 | return ret 27 | 28 | def on_recv_packet(data, sender): 29 | meta = data['meta'] 30 | type = meta['type'] 31 | print(settings.machine_name, "got", type, str(data)) 32 | if type in callbacks: 33 | for c in callbacks[type]: 34 | c(data, sender, meta) 35 | 36 | def init(serve_port): 37 | basic_tcp_json.init(serve_port=serve_port) 38 | basic_tcp_json.on_recv_data = on_recv_packet 39 | 40 | simple_http_json.init(serve_port=serve_port+10) 41 | simple_http_json.on_recv_data = on_recv_packet 42 | 43 | def hook_type(type, callback): 44 | if type not in callbacks: 45 | callbacks[type] = [] 46 | callbacks[type].append(callback) 47 | -------------------------------------------------------------------------------- /machine.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from pprint import pprint 4 | from network import send_packet 5 | import network 6 | import settings 7 | 8 | 9 | machines = [] 10 | machines_dict = {} 11 | 12 | def get_by_ip(ip): 13 | for m in machines: 14 | if m.ip == ip: 15 | return m 16 | 17 | class Machine: 18 | def __init__(self, name="unknown", pkey="unknown", pkeyhash="unknown", ip=None, port=None, autoconnect=False): 19 | self.name = name 20 | self.pkey = pkey 21 | self.pkeyhash = pkeyhash 22 | self.ip = ip 23 | self.port = port 24 | self.autoconnect = autoconnect 25 | #def send_data(self, data): 26 | # return send_data(self.ip, self.port, data) 27 | def send_packet(self, data, meta): 28 | return send_packet(self.ip, self.port, data, meta) 29 | def connect(self): 30 | pass 31 | 32 | def send_all(data, meta): 33 | for m in machines: 34 | m.send_packet(data, meta) 35 | 36 | def loadcontacts(machinesfile='machines.json'): 37 | with open(machinesfile) as data_file: 38 | data = json.load(data_file) 39 | #pprint(data) 40 | for m in data: 41 | machine = Machine( 42 | name=m['name'], 43 | pkey=m['pkey'], 44 | pkeyhash=m['pkeyhash'], 45 | ip=m['ip'], 46 | port=m['port'], 47 | autoconnect=m.get("autoconnect", False), 48 | ) 49 | machines.append(machine) 50 | machines_dict[machine.pkey] = machine 51 | 52 | def add_machine(name, pkey, ip, port): 53 | machine = Machine( 54 | name=name, 55 | pkey=pkey, 56 | pkeyhash=str(hash(pkey)), 57 | ip=ip, 58 | port=port, 59 | ) 60 | machines.append(machine) 61 | machines_dict[machine.pkey] = machine 62 | return machine 63 | 64 | def init(root=None): 65 | network.hook_type("machine", got_data) 66 | 67 | def autoconnect(): 68 | meta = { 69 | "type": "machine", 70 | "subtype": "auto_peer_request" 71 | } 72 | data = { 73 | "name": settings.machine_name, 74 | "pkey": settings.pkey, 75 | "port": settings.serve_port, 76 | "ip": str(settings.ext_ip) 77 | } 78 | for m in machines: 79 | if m.autoconnect: 80 | print("attempting to connect to", m.name) 81 | m.send_packet(data, meta) 82 | 83 | def got_data(data, machine, meta): 84 | print(settings.machine_name, data, getattr(machine, "name", "unknown")) 85 | if meta['subtype'] == "auto_peer_request": 86 | if settings.auto_accept == True: 87 | print("accepting FR", data) 88 | mdata = data['data'] 89 | add_machine(**mdata) 90 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | import defopt 7 | import json 8 | 9 | import html_ui 10 | import websockets_ui 11 | import machine 12 | import network 13 | import settings 14 | import synclist 15 | import time 16 | from apps import basic_chat 17 | 18 | import debugprint 19 | 20 | import random 21 | 22 | inittime = time.time() 23 | 24 | def test_cb(data, sender, meta): 25 | print(settings.machine_name, "MAINCB", data, sender, sender.ip, sender.name) 26 | 27 | def main(settingsfile="settings.json", test_mode=False, no_auto=False): 28 | """Display a friendly greeting. 29 | 30 | :param str settingsfile: settings file to load 31 | :param bool test_mode: True to run some tests 32 | :param bool no_auto: True disable auto FR 33 | """ 34 | 35 | #load settings 36 | with open(settingsfile) as data_file: 37 | data = json.load(data_file) 38 | settings.load_data(data) 39 | machinesfile = data['machines_file'] 40 | 41 | #init network 42 | network.init(settings.serve_port) 43 | network.hook_type("hello", test_cb) 44 | 45 | #init synclist 46 | synclist.init() 47 | 48 | #init basic_chat 49 | basic_chat.init() 50 | 51 | #init html 52 | html_ui.init() 53 | websockets_ui.init() 54 | 55 | #load machines 56 | machine.loadcontacts(machinesfile) 57 | 58 | #load machines 59 | machine.init() 60 | if not no_auto: 61 | machine.autoconnect() 62 | 63 | #connect to all? 64 | 65 | print("Init Complete") 66 | 67 | if test_mode: 68 | run_tests() 69 | 70 | while settings.running: 71 | time.sleep(1) 72 | 73 | 74 | def run_tests(): 75 | print(settings.machine_name, "ready to run tests") 76 | while time.time() < inittime+1: 77 | time.sleep(0.01) 78 | print(settings.machine_name, "Running tests") 79 | test_sl = synclist.create_synclist("global_test") 80 | 81 | #get machine for testing 82 | tf = machine.machines[0] 83 | #print(settings.machine_name, tf) 84 | 85 | #sleep, send test packet, then sleep again 86 | time.sleep(.2) 87 | #ret = tf.send_data("test") 88 | ret = tf.send_packet({}, {"type":"hello"}) 89 | print(settings.machine_name, ret) 90 | time.sleep(.2)#+random.random()) 91 | 92 | test_sl.publish_item({"testitem":"I am "+ settings.machine_name}) 93 | 94 | time.sleep(.3) 95 | 96 | #synclist.subscribe_list("global_test") 97 | 98 | time.sleep(.3) 99 | 100 | print("resulting items:", test_sl.items) 101 | print(inittime) 102 | from transports import basic_tcp_json 103 | print("Clients", basic_tcp_json.EchoFactory.clients) 104 | 105 | 106 | 107 | 108 | 109 | if __name__ == '__main__': 110 | defopt.run(main) -------------------------------------------------------------------------------- /transports/simple_http_json.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | 7 | from twisted.internet import protocol, reactor, endpoints 8 | 9 | from threading import Semaphore, Thread 10 | from time import sleep 11 | import socket, json 12 | import sys 13 | 14 | import machine 15 | 16 | from http.server import BaseHTTPRequestHandler, HTTPServer 17 | import urllib 18 | import requests 19 | import cgi 20 | 21 | 22 | # HTTPRequestHandler class 23 | class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): 24 | # GET 25 | def do_GET(self): 26 | print("Get HTTP request made") 27 | # Send response status code 28 | self.send_response(200) 29 | 30 | # Send headers 31 | self.send_header('Content-type', 'text/html') 32 | self.end_headers() 33 | 34 | # Send message back to client 35 | message = "Hey, you should POST some json with the correct formatting to this endpoint!" 36 | # Write content as utf-8 data 37 | self.wfile.write(bytes(message, "utf8")) 38 | return 39 | def do_POST(self): 40 | length = int(self.headers['Content-Length']) 41 | rfile = self.rfile.read(length) 42 | print() 43 | jdata = json.loads(rfile.decode('utf-8')) 44 | print("POSTED", jdata) 45 | sender = machine.machines_dict.get(jdata['meta']['pkey']) 46 | on_recv_data(jdata, sender) 47 | self.send_response(200) 48 | 49 | # Send headers 50 | self.send_header('Content-type', 'text/html') 51 | self.end_headers() 52 | 53 | # Send message back to client 54 | message = "Hello POST world!" 55 | # Write content as utf-8 data 56 | self.wfile.write(bytes(message, "utf8")) 57 | return 58 | 59 | 60 | def run(port=None): 61 | print('starting server...') 62 | server_address = ('127.0.0.1', port) 63 | httpd = HTTPServer(server_address, testHTTPServer_RequestHandler) 64 | print('running server...') 65 | httpd.serve_forever() 66 | 67 | 68 | def send_data(HOST, PORT, data): 69 | PORT = PORT + 10 70 | print("sendingHTTP", HOST, PORT, data) 71 | 72 | #received = requests.post("http://localhost" + ":" + str(PORT), data=data) 73 | try: 74 | received = requests.post("http://" + HOST+":"+str(PORT), data=data) 75 | except Exception as e: 76 | print(e) 77 | print("HTTP failed to send data", HOST, PORT, data) 78 | return 0, "fail" 79 | return 1, received 80 | 81 | def on_recv_data(data, sender): 82 | pass 83 | 84 | def send_packet(HOST, PORT, data, meta): 85 | packet = json.dumps({"data":data, "meta":meta}) 86 | ret = send_data(HOST,PORT, packet) 87 | return ret 88 | 89 | 90 | def init(serve_port): 91 | t=Thread(target=run, 92 | kwargs={'port': serve_port}) 93 | t.daemon=True 94 | t.start() 95 | print("HTTP network listening on", serve_port) 96 | 97 | if __name__ == "__main__": 98 | init() 99 | while 1: 100 | sleep(60) 101 | print("Still running") 102 | -------------------------------------------------------------------------------- /synclist.py: -------------------------------------------------------------------------------- 1 | #synclist provides a high level list of dicts/json self.items 2 | #it should be able to use multiple underlying transports 3 | 4 | import machine, network, settings 5 | 6 | synclists = {} 7 | 8 | def init(): 9 | network.hook_type("synclist", on_recv_item) 10 | 11 | def create_synclist(id): 12 | return SyncList(id) 13 | 14 | def on_recv_item(data, sender, meta): 15 | print("!!!Got sync related item:", data, sender, meta['subtype']) 16 | meta = data['meta'] 17 | data = data['data'] 18 | slid = data['id'] 19 | sl = synclists.get(slid) 20 | if sl: 21 | print(sl.items) 22 | if meta['subtype'] == "subscribe": 23 | sl.subscribe(sender) 24 | sl.sync_to_contact(sender.pkey) 25 | print("XXrequesting revers subscrive") 26 | #sender.send_packet({"id":slid},{"type":"synclist", "subtype":"reverse_subscribe"}) 27 | print("requested") 28 | if meta['subtype'] == "reverse_subscribe": 29 | print("ZZGIO reverse subscrive") 30 | sl.subscribe(sender) 31 | sl.sync_to_contact(sender.pkey) 32 | if meta['subtype'] == "syncitem": 33 | print("SYNCITEM") 34 | sl.on_recv_item(sender.pkey, data) 35 | else: 36 | print("FAIL", slid, "not found") 37 | 38 | def subscribe_list(id): 39 | machine.send_all({"id":id},{"type":"synclist", "subtype":"subscribe"}) 40 | 41 | 42 | class SyncList: 43 | def __init__(self, ownid="testid"): 44 | synclists[ownid] = self 45 | self.ownid = ownid 46 | self.items_by_recv_date = [] 47 | self.items = {} 48 | self.subscribers = {} 49 | subscribe_list(ownid) 50 | #subscribers[self.ownid] = {'last_sync':0} 51 | 52 | 53 | def on_recv_item(self, cId, item): 54 | #contact = self.subscribers[cId] 55 | if item['hash'] not in self.items: 56 | self.items_by_recv_date.append(item) 57 | self.items[item['hash']] = item 58 | 59 | def publish_item(self, item): 60 | print("publishing", item) 61 | item['hash'] = hash(str(item)) 62 | self.on_recv_item(settings.pkey, item) 63 | self.sync_to_all() 64 | 65 | def subscribe(self, m): 66 | self.subscribers[m.pkey] = {'last_sync':0, "machine": m} 67 | #self.sync_to_contact(m.pkey) 68 | 69 | def _send_item(self, contact, item): 70 | item['id'] = self.ownid 71 | m = machine.machines_dict[contact] 72 | print(contact) 73 | print(item) 74 | print("sending" + str(item) + " to " + str(contact), m.name) 75 | m.send_packet(item, {"type": "synclist", "subtype": "syncitem"}) 76 | 77 | 78 | #get contact up to date with own data 79 | def sync_to_contact(self, cId): 80 | contact = self.subscribers[cId] 81 | ls = contact['last_sync'] 82 | #print(self.items_by_recv_date) 83 | for i in self.items_by_recv_date[ls:]: 84 | self._send_item(cId, i) 85 | #should confirm contact has got data, then: 86 | contact['last_sync'] = len(self.items_by_recv_date) 87 | 88 | def sync_to_all(self): 89 | for c in self.subscribers.keys(): 90 | self.sync_to_contact(c) 91 | -------------------------------------------------------------------------------- /websockets_ui.py: -------------------------------------------------------------------------------- 1 | """websockets_ui.py: Provides an interface at various levels to allow construction 2 | of a user interface or app over websockets.""" 3 | 4 | __author__ = "Alex 'Chozabu' P-B" 5 | __copyright__ = "Copyright 2016, Chozabu" 6 | 7 | from threading import Thread 8 | from time import sleep 9 | 10 | 11 | import asyncio 12 | import datetime 13 | import random 14 | import websockets 15 | import network 16 | import json 17 | 18 | 19 | import machine 20 | import settings 21 | import synclist 22 | 23 | from apps import basic_chat, forums 24 | 25 | connected = set() 26 | serve_port = 5678 27 | 28 | async def handle(websocket, path): 29 | connected.add(websocket) 30 | while True: 31 | try: 32 | data = await websocket.recv() 33 | jdata=None 34 | try: 35 | jdata = json.loads(data) 36 | except: 37 | await websocket.send("sorry, couldn't decode that json!") 38 | print("got", data) 39 | if jdata: 40 | intent = jdata.get("intent") 41 | if not intent: 42 | await websocket.send("you must give an 'intent'") 43 | else: 44 | if intent == "ping": 45 | await websocket.send("pong") 46 | elif intent == "broadcast": 47 | basic_chat.send_message(jdata['message']) 48 | await websocket.send(json.dumps({"OK":"OK"})) 49 | elif intent == "get_chat": 50 | await websocket.send(json.dumps(basic_chat.messagelist)) 51 | elif intent == "get_forums": 52 | await websocket.send(json.dumps([(f.name, f.id) for f in forums.forums])) 53 | 54 | 55 | now = datetime.datetime.utcnow().isoformat() + 'Z' + str(connected) 56 | 57 | except websockets.exceptions.ConnectionClosed as e: 58 | if websocket in connected: 59 | connected.remove(websocket) 60 | print("WS client disconnected") 61 | else: 62 | print("Unknown WS client disconnected") 63 | break 64 | 65 | 66 | def _init(): 67 | print("WSUI on port", serve_port) 68 | 69 | loop = asyncio.new_event_loop() 70 | asyncio.set_event_loop(loop) 71 | 72 | start_server = websockets.serve(handle, '127.0.0.1', serve_port) 73 | 74 | try: 75 | asyncio.get_event_loop().run_until_complete(start_server) 76 | asyncio.get_event_loop().run_forever() 77 | except Exception as e: 78 | print(e) 79 | print("websoockets api failed to start") 80 | 81 | 82 | def got_chat_msg(data, machine, meta): 83 | try: 84 | loop = asyncio.get_event_loop() 85 | except: 86 | loop = asyncio.new_event_loop() 87 | asyncio.set_event_loop(loop) 88 | try: 89 | loop.run_until_complete(pump_msg(data)) 90 | except Exception as e: 91 | print(e) 92 | print("websoockets api failed to complete message") 93 | 94 | 95 | async def pump_msg(data): 96 | remlist = [] 97 | for c in connected: 98 | try: 99 | await c.send(str(data)) 100 | except websockets.exceptions.ConnectionClosed as e: 101 | remlist.append(c) 102 | print("WS client disconnected") 103 | for r in remlist: 104 | connected.remove(r) 105 | 106 | 107 | def init(): 108 | network.hook_type("basic_chat", got_chat_msg) 109 | print("WSUI will be on port", serve_port) 110 | t=Thread( 111 | target=_init, 112 | kwargs={}) 113 | t.daemon=True 114 | t.start() 115 | -------------------------------------------------------------------------------- /transports/basic_tcp_json.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | 7 | from twisted.internet import protocol, reactor, endpoints 8 | 9 | from threading import Semaphore, Thread 10 | from time import sleep 11 | import socket, json 12 | import sys 13 | 14 | import machine 15 | 16 | csocklist = {} 17 | 18 | def send_data(HOST, PORT, data): 19 | global csocklist 20 | print("sendingTCP", HOST, PORT, data) 21 | 22 | try: 23 | # Create a socket (SOCK_STREAM means a TCP socket) 24 | id_string = str(HOST) + ':' + str(PORT) 25 | sock = csocklist.get(id_string, None) 26 | if not sock: 27 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 | 29 | sock.settimeout(1) 30 | # Connect to server and send data 31 | sock.connect((HOST, PORT)) 32 | csocklist[id_string] = sock 33 | sock.sendall(bytes(data + "\n", "utf-8")) 34 | 35 | # Receive data from the server and shut down 36 | received = str(sock.recv(1024), "utf-8") 37 | 38 | #print("Sent: {}".format(data)) 39 | #print("Received: {}".format(received)) 40 | except Exception as e: 41 | print("TWISTED failed to send data", HOST, PORT, data) 42 | print(e) 43 | return 0, "fail" 44 | return 1, received 45 | 46 | def on_recv_data(data, sender): 47 | pass 48 | 49 | def send_packet(HOST, PORT, data, meta): 50 | packet = json.dumps({"data":data, "meta":meta}) 51 | ret = send_data(HOST,PORT, packet) 52 | return ret 53 | 54 | class Echo(protocol.Protocol): 55 | def __init__(self, factory): 56 | self.factory = factory 57 | 58 | def peer_string(self): 59 | peer = self.transport.getPeer() 60 | return str(peer.host) + ':' + str(peer.port) 61 | 62 | def connectionMade(self): 63 | self.factory.clients.append(self) 64 | 65 | #def dataReceived(self, data): 66 | # for echoer in self.factory.clients: 67 | # echoer.transport.write(data) 68 | 69 | def connectionLost(self, reason): 70 | print("Connection lost!", self) 71 | self.factory.clients.remove(self) 72 | def dataReceived(self, data): 73 | #print("echoing: ", data) 74 | #print(json.loads(data.decode())) 75 | try: 76 | jdata = json.loads(data.decode()) 77 | except: 78 | self.transport.write( 79 | json.dumps( 80 | { 81 | "data":{"message": data.decode()}, 82 | "meta":{"error":"could not understand message"} 83 | } 84 | ).encode() 85 | ) 86 | return 87 | #ip, port = self.transport.client 88 | #print(ip, port) 89 | #print(str(self.transport.getPeer())) 90 | #sender = machine.get_by_ip(ip) 91 | sender = machine.machines_dict.get(jdata['meta']['pkey']) 92 | on_recv_data(jdata, sender) 93 | print("------------------", self.transport.getPeer(), self.peer_string()) 94 | 95 | self.transport.write("OK".encode('utf-8')) 96 | 97 | class EchoFactory(protocol.Factory): 98 | clients = [] 99 | def buildProtocol(self, addr): 100 | client = Echo(self) 101 | EchoFactory.clients.append(client) 102 | return client 103 | 104 | def init(serve_port): 105 | endpoints.serverFromString(reactor, "tcp:"+str(serve_port)).listen(EchoFactory()) 106 | #print("starting networking thread") 107 | t=Thread( 108 | target=reactor.run, 109 | kwargs={'installSignalHandlers':False}) 110 | t.daemon=True 111 | t.start() 112 | print("basic network listening on", serve_port) 113 | 114 | if __name__ == "__main__": 115 | init() 116 | while 1: 117 | sleep(60) 118 | print("Still running") 119 | -------------------------------------------------------------------------------- /hclient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSocket demo 5 | 6 | 7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /html_ui.py: -------------------------------------------------------------------------------- 1 | """Foobar.py: Description of what foobar does.""" 2 | 3 | __author__ = "Alex 'Chozabu' P-B" 4 | __copyright__ = "Copyright 2016, Chozabu" 5 | 6 | from threading import Thread 7 | from time import sleep 8 | 9 | from twisted.internet import reactor, endpoints 10 | from twisted.web import server, resource 11 | 12 | import machine 13 | import settings 14 | import synclist 15 | 16 | from apps import basic_chat, forums 17 | 18 | header = ''' 19 |

PyRock

20 | home 21 | chat 22 | forums 23 | quit 24 | ''' 25 | 26 | 27 | class Simple(resource.Resource): 28 | isLeaf = True 29 | def render_GET(self, request): 30 | request.setHeader("Content-Type", "text/HTML; charset=utf-8") 31 | 32 | retstr = "" 33 | retstr += header 34 | retstr += "

Home

" 35 | retstr += "

This machine is: " + settings.machine_name + "

" 36 | retstr += 'Quit' 37 | 38 | retstr += "

Friends

" 39 | retstr += "" 43 | 44 | retstr += "

Synclists

" 45 | retstr += "" 49 | 50 | retstr += "" 51 | #print(retstr) 52 | return retstr.encode() 53 | 54 | class Chat(resource.Resource): 55 | isLeaf = True 56 | 57 | def render_GET(self, request): 58 | request.setHeader("Content-Type", "text/HTML; charset=utf-8") 59 | 60 | if b'newmessage' in request.args: 61 | print(str(request.args[b'newmessage'][0])) 62 | basic_chat.send_message(request.args[b'newmessage'][0].decode()) 63 | request.redirect("/chat") 64 | #request.finish() 65 | return b"no" 66 | 67 | retstr = "" 68 | retstr += header 69 | retstr += "

Chat

" 70 | retstr += "

This machine is: " + settings.machine_name + "

" 71 | 72 | retstr += ''' 73 |
74 | First name:
75 |
76 | 77 |
78 | ''' 79 | 80 | retstr += "

messages

" 81 | retstr += "" 85 | 86 | retstr += "" 87 | #print(retstr) 88 | return retstr.encode() 89 | class Forums(resource.Resource): 90 | isLeaf = True 91 | 92 | def render_GET(self, request): 93 | 94 | print(request.args) 95 | 96 | if b'newforum' in request.args: 97 | forumid = request.args[b'newforum'][0].decode() 98 | print(forumid) 99 | forums.create_forum(forumid) 100 | request.redirect("/forums") 101 | #request.finish() 102 | return b"no" 103 | 104 | if b'newpost' in request.args: 105 | forumid = request.args[b'forumid'][0].decode() 106 | newpost = request.args[b'newpost'][0].decode() 107 | forum = forums.forums_dict[forumid] 108 | forum.create_post({"message": newpost}) 109 | request.redirect("/forums/?view="+forumid) 110 | #request.finish() 111 | return b"no" 112 | 113 | retstr = "" 114 | retstr += header 115 | retstr += "

Forums

" 116 | retstr += "

This machine is: " + settings.machine_name + "

" 117 | 118 | request.setHeader("Content-Type", "text/HTML; charset=utf-8") 119 | if not request.args:#b'view' not in request.args: 120 | retstr += "" 124 | 125 | retstr += ''' 126 |
127 |
128 | 129 |
130 | ''' 131 | retstr += "" 132 | return retstr.encode() 133 | 134 | if b'view' in request.args:#not request.args: 135 | forumid = request.args[b'view'][0].decode() 136 | forum = forums.forums_dict[forumid] 137 | retstr += "

" + forum.name + "

" 138 | retstr += "" 142 | 143 | retstr += "" 147 | 148 | retstr += ''' 149 |
150 | 151 |
152 | 153 |
154 | ''' 155 | retstr += "" 156 | return retstr.encode() 157 | 158 | 159 | retstr += ''' 160 |
161 | First name:
162 |
163 | 164 |
165 | ''' 166 | 167 | retstr += "

messages

" 168 | retstr += "" 172 | 173 | retstr += "" 174 | #print(retstr) 175 | return retstr.encode() 176 | class Quit(resource.Resource): 177 | isLeaf = True 178 | 179 | def render_GET(self, request): 180 | request.setHeader("Content-Type", "text/HTML; charset=utf-8") 181 | settings.running = False 182 | 183 | retstr = "" 184 | retstr += "

PyRock Quitting

" 185 | retstr += 'Goodbye' 186 | retstr += "" 187 | #print(retstr) 188 | return retstr.encode() 189 | #site = server.Site(Simple()) 190 | #reactor.listenTCP(8080, site) 191 | #reactor.run() 192 | 193 | 194 | def init(serve_port=None): 195 | if not serve_port: 196 | serve_port = settings.ui_port 197 | root = resource.Resource() 198 | root.putChild(b"", Simple()) 199 | root.putChild(b"chat", Chat()) 200 | root.putChild(b"forums", Forums()) 201 | root.putChild(b"quit", Quit()) 202 | #root.putChild(b"book", Book()) 203 | site = server.Site(root) 204 | endpoints.serverFromString(reactor, "tcp:"+str(serve_port)).listen(site) 205 | #print("starting networking thread") 206 | t=Thread( 207 | target=reactor.run, 208 | kwargs={'installSignalHandlers':False}) 209 | t.daemon=True 210 | print("UI on port", serve_port) 211 | #t.start() 212 | #print("running") 213 | 214 | if __name__ == "__main__": 215 | init() 216 | while 1: 217 | sleep(60) 218 | print("Still running") 219 | --------------------------------------------------------------------------------