├── .gitignore ├── BackgroundPeerMessage.py ├── BackgroundPlugin.py ├── FileRequestPlugin.py ├── README.md ├── SitePlugin.py ├── UiWebsocketPlugin.py ├── __init__.py ├── p2putil.py └── plugin_info.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /BackgroundPeerMessage.py: -------------------------------------------------------------------------------- 1 | def module(io): 2 | scope0 = io["scope0"][0] 3 | scope0.import_(names=[("ZeroFrame", "_")], from_=None, level=None) 4 | zeroframe = scope0["_"] 5 | 6 | class PeerMessage(object): 7 | def join(self): 8 | zeroframe.cmd("channelJoin", "peerReceive") 9 | 10 | def onPeerReceive(self, callback): 11 | def func(message): 12 | callback(**message) 13 | zeroframe.on("peerReceive", func) 14 | 15 | def peerBroadcast(self, *args, **kwargs): 16 | return zeroframe.cmd("peerBroadcast", *args, **kwargs) 17 | 18 | def peerSend(self, *args, **kwargs): 19 | return zeroframe.cmd("peerSend", *args, **kwargs) 20 | 21 | def peerInvalid(self, *args, **kwargs): 22 | return zeroframe.cmd("peerInvalid", *args, **kwargs) 23 | 24 | def peerValid(self, *args, **kwargs): 25 | return zeroframe.cmd("peerValid", *args, **kwargs) 26 | 27 | return PeerMessage() 28 | -------------------------------------------------------------------------------- /BackgroundPlugin.py: -------------------------------------------------------------------------------- 1 | try: 2 | import BackgroundProcessing 3 | from .BackgroundPeerMessage import module 4 | BackgroundProcessing.addModule("PeerMessage", module) 5 | except ImportError: 6 | pass 7 | -------------------------------------------------------------------------------- /FileRequestPlugin.py: -------------------------------------------------------------------------------- 1 | from Plugin import PluginManager 2 | from Config import config 3 | from Crypt import CryptBitcoin as Crypt 4 | from util import SafeRe 5 | import json 6 | import time 7 | import gevent 8 | import hashlib 9 | from .p2putil import getWebsockets 10 | 11 | 12 | @PluginManager.registerTo("FileRequest") 13 | class FileRequestPlugin(object): 14 | # Re-broadcast to neighbour peers 15 | def actionPeerBroadcast(self, params): 16 | gevent.spawn(self.handlePeerBroadcast, params) 17 | def handlePeerBroadcast(self, params): 18 | ip = "%s:%s" % (self.connection.ip, self.connection.port) 19 | 20 | if "trace" in params: 21 | params["trace"].append(ip) 22 | 23 | raw = json.loads(params["raw"]) 24 | 25 | res, signature_address, cert, msg_hash = self.peerCheckMessage(raw, params, ip) 26 | if not res: 27 | return 28 | 29 | self.response({ 30 | "ok": "thx" 31 | }) 32 | 33 | 34 | site = self.sites.get(raw["site"]) 35 | websockets = getWebsockets(site) 36 | if websockets: 37 | # Wait for result (valid/invalid) 38 | site.p2p_result[msg_hash] = gevent.event.AsyncResult() 39 | 40 | # Send to WebSocket 41 | for ws in websockets: 42 | ws.cmd("peerReceive", { 43 | "ip": ip, 44 | "hash": msg_hash, 45 | "message": raw["message"], 46 | "signed_by": signature_address, 47 | "cert": cert, 48 | "site": raw["site"], 49 | "broadcast": True, 50 | "trace": params.get("trace"), 51 | "timestamp": raw.get("timestamp") 52 | }) 53 | 54 | 55 | # Maybe active filter will reply? 56 | if websockets: 57 | # Wait for p2p_result 58 | result = site.p2p_result[msg_hash].get() 59 | del site.p2p_result[msg_hash] 60 | if not result: 61 | self.connection.badAction(10) 62 | return 63 | 64 | # Save to cache 65 | if not websockets and raw["immediate"]: 66 | site.p2p_unread.append({ 67 | "ip": "%s:%s" % (self.connection.ip, self.connection.port), 68 | "hash": msg_hash, 69 | "message": raw["message"], 70 | "signed_by": signature_address, 71 | "cert": cert, 72 | "site": raw["site"], 73 | "broadcast": True, 74 | "trace": params.get("trace"), 75 | "timestamp": raw.get("timestamp") 76 | }) 77 | 78 | 79 | # Get peer list 80 | peers = site.getConnectedPeers() 81 | if len(peers) < raw["peer_count"]: # Add more, non-connected peers if necessary 82 | peers += site.getRecentPeers(raw["peer_count"] - len(peers)) 83 | 84 | # Send message to neighbour peers 85 | for peer in peers: 86 | gevent.spawn(peer.request, "peerBroadcast", params) 87 | 88 | 89 | # Receive by-ip messages 90 | def actionPeerSend(self, params): 91 | gevent.spawn(self.handlePeerSend, params) 92 | def handlePeerSend(self, params): 93 | ip = "%s:%s" % (self.connection.ip, self.connection.port) 94 | raw = json.loads(params["raw"]) 95 | 96 | 97 | res, signature_address, cert, msg_hash = self.peerCheckMessage(raw, params, ip) 98 | if not res: 99 | return 100 | 101 | self.response({ 102 | "ok": "thx" 103 | }) 104 | 105 | site = self.sites.get(raw["site"]) 106 | if "to" in raw: 107 | # This is a reply to peerSend 108 | site.p2p_to[raw["to"]].set({ 109 | "hash": msg_hash, 110 | "message": raw["message"], 111 | "signed_by": signature_address, 112 | "cert": cert, 113 | "timestamp": raw.get("timestamp") 114 | }) 115 | else: 116 | # Broadcast 117 | websockets = getWebsockets(site) 118 | if websockets: 119 | # Wait for result (valid/invalid) 120 | site.p2p_result[msg_hash] = gevent.event.AsyncResult() 121 | 122 | for ws in websockets: 123 | ws.cmd("peerReceive", { 124 | "ip": ip, 125 | "hash": msg_hash, 126 | "message": raw["message"], 127 | "signed_by": signature_address, 128 | "cert": cert, 129 | "site": raw["site"], 130 | "broadcast": False, 131 | "timestamp": raw.get("timestamp") 132 | }) 133 | 134 | # Maybe active filter will reply? 135 | if websockets: 136 | # Wait for p2p_result 137 | result = site.p2p_result[msg_hash].get() 138 | del site.p2p_result[msg_hash] 139 | if not result: 140 | self.connection.badAction(10) 141 | 142 | # Save to cache 143 | if not websockets and raw["immediate"]: 144 | site.p2p_unread.append({ 145 | "ip": ip, 146 | "hash": msg_hash, 147 | "message": raw["message"], 148 | "signed_by": signature_address, 149 | "cert": cert, 150 | "site": raw["site"], 151 | "broadcast": False, 152 | "timestamp": raw.get("timestamp") 153 | }) 154 | 155 | 156 | def peerCheckMessage(self, raw, params, ip): 157 | # Calculate hash from nonce 158 | msg_hash = hashlib.sha256(("%s,%s" % (params["nonce"], params["raw"])).encode("ascii")).hexdigest() 159 | 160 | # Check that p2p.json exists 161 | site = self.sites.get(raw["site"]) 162 | if not site.storage.isFile("p2p.json"): 163 | self.connection.log("Site %s doesn't support P2P messages" % raw["site"]) 164 | self.connection.badAction(5) 165 | self.response({ 166 | "error": "Site %s doesn't support P2P messages" % raw["site"] 167 | }) 168 | return False, "", None, msg_hash 169 | 170 | # Check whether P2P messages are supported 171 | p2p_json = site.storage.loadJson("p2p.json") 172 | if "filter" not in p2p_json: 173 | self.connection.log("Site %s doesn't support P2P messages" % raw["site"]) 174 | self.connection.badAction(5) 175 | self.response({ 176 | "error": "Site %s doesn't support P2P messages" % raw["site"] 177 | }) 178 | return False, "", None, msg_hash 179 | 180 | # Was the message received yet? 181 | if msg_hash in site.p2p_received: 182 | self.response({ 183 | "warning": "Already received, thanks" 184 | }) 185 | return False, "", None, msg_hash 186 | site.p2p_received.append(msg_hash) 187 | 188 | # Check whether the message matches passive filter 189 | if not SafeRe.match(p2p_json["filter"], json.dumps(raw["message"])): 190 | self.connection.log("Invalid message for site %s: %s" % (raw["site"], raw["message"])) 191 | self.connection.badAction(5) 192 | self.response({ 193 | "error": "Invalid message for site %s: %s" % (raw["site"], raw["message"]) 194 | }) 195 | return False, "", None, msg_hash 196 | 197 | # Not so fast 198 | if "freq_limit" in p2p_json and time.time() - site.p2p_last_recv.get(ip, 0) < p2p_json["freq_limit"]: 199 | self.connection.log("Too fast messages from %s" % raw["site"]) 200 | self.connection.badAction(2) 201 | self.response({ 202 | "error": "Too fast messages from %s" % raw["site"] 203 | }) 204 | return False, "", None, msg_hash 205 | site.p2p_last_recv[ip] = time.time() 206 | 207 | # Not so much 208 | if "size_limit" in p2p_json and len(json.dumps(raw["message"])) > p2p_json["size_limit"]: 209 | self.connection.log("Too big message from %s" % raw["site"]) 210 | self.connection.badAction(7) 211 | self.response({ 212 | "error": "Too big message from %s" % raw["site"] 213 | }) 214 | return False, "", None, msg_hash 215 | 216 | # Verify signature 217 | if params["signature"]: 218 | signature_address, signature = params["signature"].split("|") 219 | what = "%s|%s|%s" % (signature_address, msg_hash, params["raw"]) 220 | if not Crypt.verify(what, signature_address, signature): 221 | self.connection.log("Invalid signature") 222 | self.connection.badAction(7) 223 | self.response({ 224 | "error": "Invalid signature" 225 | }) 226 | return False, "", None, msg_hash 227 | 228 | # Now check auth providers 229 | if params.get("cert"): 230 | # Read all info 231 | cert_auth_type, cert_auth_user_name, cert_issuer, cert_sign = map( 232 | lambda b: b.decode("ascii") if isinstance(b, bytes) else b, 233 | params["cert"] 234 | ) 235 | # This is what certificate issuer signs 236 | cert_subject = "%s#%s/%s" % (signature_address, cert_auth_type, cert_auth_user_name) 237 | # Now get cert issuer address 238 | cert_signers = p2p_json.get("cert_signers", {}) 239 | cert_addresses = cert_signers.get(cert_issuer, []) 240 | # And verify it 241 | if not Crypt.verify(cert_subject, cert_addresses, cert_sign): 242 | self.connection.log("Invalid signature certificate") 243 | self.connection.badAction(7) 244 | self.response({ 245 | "error": "Invalid signature certificate" 246 | }) 247 | return False, "", None, msg_hash 248 | # And save the ID 249 | cert = "%s/%s@%s" % (cert_auth_type, cert_auth_user_name, cert_issuer) 250 | else: 251 | # Old-style sign 252 | cert = "" 253 | else: 254 | signature_address = "" 255 | cert = "" 256 | 257 | # Check that the signature address is correct 258 | if "signed_only" in p2p_json: 259 | valid = p2p_json["signed_only"] 260 | if valid is True and not signature_address: 261 | self.connection.log("Not signed message") 262 | self.connection.badAction(5) 263 | self.response({ 264 | "error": "Not signed message" 265 | }) 266 | return False, "", None, msg_hash 267 | elif isinstance(valid, str) and signature_address != valid: 268 | self.connection.log("Message signature is invalid: %s not in [%r]" % (signature_address, valid)) 269 | self.connection.badAction(5) 270 | self.response({ 271 | "error": "Message signature is invalid: %s not in [%r]" % (signature_address, valid) 272 | }) 273 | return False, "", None, msg_hash 274 | elif isinstance(valid, list) and signature_address not in valid: 275 | self.connection.log("Message signature is invalid: %s not in %r" % (signature_address, valid)) 276 | self.connection.badAction(5) 277 | self.response({ 278 | "error": "Message signature is invalid: %s not in %r" % (signature_address, valid) 279 | }) 280 | return False, "", None, msg_hash 281 | 282 | return True, signature_address, cert, msg_hash -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PeerMessage 2 | 3 | **PeerMessage** is a plugin for [ZeroNet](https://github.com/HelloZeroNet/ZeroNet), a decentralized network. It enables peer-to-peer communication via messages by extending ZeroFrame API and peer API. 4 | 5 | 6 | Read more at [wiki](https://github.com/HelloZeroNet/Plugin-PeerMessage/wiki). -------------------------------------------------------------------------------- /SitePlugin.py: -------------------------------------------------------------------------------- 1 | from Plugin import PluginManager 2 | 3 | 4 | 5 | @PluginManager.registerTo("Site") 6 | class SitePlugin(object): 7 | def __init__(self, *args, **kwargs): 8 | super(SitePlugin, self).__init__(*args, **kwargs) 9 | self.p2p_received = [] 10 | self.p2p_result = {} 11 | self.p2p_to = {} 12 | self.p2p_unread = [] 13 | self.p2p_last_recv = {} 14 | -------------------------------------------------------------------------------- /UiWebsocketPlugin.py: -------------------------------------------------------------------------------- 1 | from Plugin import PluginManager 2 | from util import SafeRe 3 | from Config import config 4 | from Crypt import CryptBitcoin as Crypt 5 | import hashlib 6 | import random 7 | import json 8 | import time 9 | import gevent 10 | from .p2putil import getWebsockets 11 | 12 | 13 | @PluginManager.registerTo("UiWebsocket") 14 | class UiWebsocketPlugin(object): 15 | def __init__(self, *args, **kwargs): 16 | super(UiWebsocketPlugin, self).__init__(*args, **kwargs) 17 | 18 | # Automatically join peerReceive and peerSend 19 | if self.site.storage.isFile("p2p.json"): 20 | self.channels.append("peerReceive") 21 | self.channels.append("peerSend") 22 | 23 | p2p_json = self.site.storage.loadJson("p2p.json") 24 | if "filter" in p2p_json: 25 | # Flush immediate messages 26 | for message in self.site.p2p_unread: 27 | self.cmd("peerReceive", message) 28 | self.site.p2p_unread = [] 29 | 30 | 31 | # Allow to broadcast to any site 32 | def hasSitePermission(self, address, cmd=None): 33 | if super(UiWebsocketPlugin, self).hasSitePermission(address, cmd=cmd): 34 | return True 35 | 36 | return cmd in ("peerBroadcast", "peerSend") 37 | 38 | 39 | # Broadcast message to other peers 40 | def actionPeerBroadcast(self, *args, **kwargs): 41 | gevent.spawn(self.handlePeerBroadcast, *args, **kwargs) 42 | def handlePeerBroadcast(self, to, message, privatekey=None, peer_count=5, immediate=False, trace=True, timestamp=False): 43 | # Check message 44 | if not self.peerCheckMessage(to, message): 45 | return 46 | 47 | # Generate message and sign it 48 | all_message = { 49 | "message": message, 50 | "peer_count": peer_count, 51 | "broadcast": True, # backward compatibility 52 | "immediate": immediate, 53 | "site": self.site.address 54 | } 55 | if timestamp: 56 | import main 57 | all_message["timestamp"] = time.time() + main.file_server.timecorrection 58 | 59 | all_message, msg_hash, cert = self.peerGenerateMessage(all_message, privatekey) 60 | 61 | peers = self.site.getConnectedPeers() 62 | if len(peers) < peer_count: # Add more, non-connected peers if necessary 63 | peers += self.site.getRecentPeers(peer_count - len(peers)) 64 | 65 | # Send message to peers 66 | for peer in peers: 67 | gevent.spawn(self.p2pBroadcast, peer, all_message, trace) 68 | 69 | # Send message to myself 70 | self.site.p2p_received.append(msg_hash) 71 | 72 | websockets = getWebsockets(self.site) 73 | for ws in websockets: 74 | ws.cmd("peerReceive", { 75 | "ip": "self", 76 | "hash": msg_hash, 77 | "message": message, 78 | "signed_by": all_message["signature"].split("|")[0] if all_message["signature"] else "", 79 | "cert": cert, 80 | "site": self.site.address, 81 | "broadcast": True, 82 | "timestamp": all_message.get("timestamp") 83 | }) 84 | 85 | if not websockets and immediate: 86 | self.site.p2p_unread.append({ 87 | "ip": "self", 88 | "hash": msg_hash, 89 | "message": message, 90 | "signed_by": all_message["signature"].split("|")[0] if all_message["signature"] else "", 91 | "cert": cert, 92 | "site": self.site.address, 93 | "broadcast": True, 94 | "timestamp": all_message.get("timestamp") 95 | }) 96 | 97 | 98 | # Reply 99 | self.response(to, { 100 | "sent": True 101 | }) 102 | 103 | # Also send the message to myself 104 | data = { 105 | "hash": msg_hash, 106 | "message": message, 107 | "signed_by": all_message["signature"].split("|")[0] if all_message["signature"] else "", 108 | "cert": cert, 109 | "site": self.site.address, 110 | "broadcast": True, 111 | "timestamp": all_message.get("timestamp") 112 | } 113 | for ws in getWebsockets(self.site): 114 | ws.cmd("peerSend", data) 115 | 116 | def p2pBroadcast(self, peer, data, trace=True): 117 | data = data.copy() 118 | if trace: 119 | data["trace"] = [] 120 | 121 | reply = peer.request("peerBroadcast", data) 122 | if reply is None: 123 | return { 124 | "ip": "%s:%s" % (peer.ip, peer.port), 125 | "reply": { 126 | "error": "Connection error" 127 | } 128 | } 129 | 130 | return { 131 | "ip": "%s:%s" % (peer.ip, peer.port), 132 | "reply": reply 133 | } 134 | 135 | # Send a message to IP 136 | def actionPeerSend(self, *args, **kwargs): 137 | gevent.spawn(self.handlePeerSend, *args, **kwargs) 138 | def handlePeerSend(self, to_, ip, message, privatekey=None, to=None, immediate=False, timestamp=False): 139 | # Check message 140 | if not self.peerCheckMessage(to_, message): 141 | return 142 | 143 | 144 | # Get peer or connect to it if it isn't cached 145 | if ip != "self": 146 | peer = self.site.peers.get(ip) 147 | if not peer: 148 | mip, mport = ip.rsplit(":", 1) 149 | peer = self.site.addPeer(mip, mport, source="peerSend") 150 | if not peer: 151 | # Couldn't connect to this IP 152 | self.response(to_, { 153 | "error": "Could not find peer %s" % ip 154 | }) 155 | return 156 | 157 | # Generate hash 158 | all_message = { 159 | "message": message, 160 | "immediate": immediate, 161 | "site": self.site.address 162 | } 163 | if to: 164 | all_message["to"] = to 165 | if timestamp: 166 | import main 167 | all_message["timestamp"] = time.time() + main.file_server.timecorrection 168 | 169 | all_message, msg_hash, cert = self.peerGenerateMessage(all_message, privatekey) 170 | 171 | # Send message 172 | self.site.p2p_to[msg_hash] = gevent.event.AsyncResult() 173 | if ip == "self": 174 | self.handlePeerSendSelf(all_message, to, msg_hash, message, cert, immediate) 175 | else: 176 | peer.request("peerSend", all_message) 177 | 178 | # Get reply 179 | reply = self.site.p2p_to[msg_hash].get() 180 | self.response(to_, reply) 181 | 182 | # Also send the message to myself 183 | data = { 184 | "ip": ip, 185 | "hash": msg_hash, 186 | "message": message, 187 | "signed_by": all_message["signature"].split("|")[0] if all_message["signature"] else "", 188 | "cert": cert, 189 | "site": self.site.address, 190 | "broadcast": False, 191 | "timestamp": all_message.get("timestamp") 192 | } 193 | for ws in getWebsockets(self.site): 194 | ws.cmd("peerSend", data) 195 | 196 | 197 | def handlePeerSendSelf(self, all_message, to, msg_hash, message, cert, immediate): 198 | signature_address = all_message["signature"].split("|")[0] if all_message["signature"] else "" 199 | 200 | if to is not None: 201 | # This is a reply to peerSend 202 | self.site.p2p_to[to].set({ 203 | "hash": msg_hash, 204 | "message": message, 205 | "signed_by": signature_address, 206 | "cert": cert, 207 | "timestamp": all_message.get("timestamp") 208 | }) 209 | else: 210 | # Broadcast 211 | websockets = getWebsockets(self.site) 212 | 213 | data = { 214 | "ip": "self", 215 | "hash": msg_hash, 216 | "message": message, 217 | "signed_by": signature_address, 218 | "cert": cert, 219 | "site": self.site.address, 220 | "broadcast": False, 221 | "timestamp": all_message.get("timestamp") 222 | } 223 | 224 | for ws in websockets: 225 | ws.cmd("peerReceive", data) 226 | 227 | # Save to cache 228 | if not websockets and immediate: 229 | self.site.p2p_unread.append(data) 230 | 231 | 232 | 233 | def p2pGetSignature(self, hash, data, privatekey): 234 | if privatekey is None: 235 | return "", None, None 236 | 237 | cert = None 238 | cert_text = "" 239 | 240 | # Get private key 241 | if privatekey == "stored": 242 | # Using site privatekey 243 | privatekey = self.user.getSiteData(self.site.address).get("privatekey") 244 | elif privatekey is False: 245 | # Using user privatekey 246 | privatekey = self.user.getAuthPrivatekey(self.site.address) 247 | cert = self.user.getCert(self.site.address) 248 | 249 | if cert: 250 | site_data = self.user.getSiteData(self.site.address, create=False) 251 | cert_issuer = site_data["cert"] 252 | 253 | p2p_json = self.site.storage.loadJson("p2p.json") 254 | if cert_issuer in p2p_json.get("cert_signers", {}): 255 | cert = [cert["auth_type"], cert["auth_user_name"], cert_issuer, cert["cert_sign"]] 256 | cert_text = "%s/%s@%s" % tuple(cert[:3]) 257 | else: 258 | cert = None 259 | 260 | # Generate signature 261 | address = Crypt.privatekeyToAddress(privatekey) 262 | return "%s|%s" % (address, Crypt.sign("%s|%s|%s" % (address, hash, data), privatekey)), cert, cert_text 263 | 264 | 265 | def actionPeerInvalid(self, to, hash): 266 | if hash in self.site.p2p_result: 267 | self.site.p2p_result[hash].set(False) 268 | def actionPeerValid(self, to, hash): 269 | if hash in self.site.p2p_result: 270 | self.site.p2p_result[hash].set(True) 271 | 272 | 273 | 274 | def peerGenerateMessage(self, all_message, privatekey=None): 275 | all_message = json.dumps(all_message) 276 | nonce = str(random.randint(0, 1000000000)) 277 | msg_hash = hashlib.sha256(("%s,%s" % (nonce, all_message)).encode("ascii")).hexdigest() 278 | signature, cert, cert_text = self.p2pGetSignature(msg_hash, all_message, privatekey) 279 | return { 280 | "raw": all_message, 281 | "signature": signature, 282 | "cert": cert, 283 | "nonce": nonce 284 | }, msg_hash, cert_text 285 | 286 | 287 | def peerCheckMessage(self, to, message): 288 | # Check whether there is p2p.json 289 | if not self.site.storage.isFile("p2p.json"): 290 | self.response(to, {"error": "Site %s doesn't support P2P messages" % self.site.address}) 291 | return False 292 | 293 | # Check whether P2P messages are supported 294 | p2p_json = self.site.storage.loadJson("p2p.json") 295 | if "filter" not in p2p_json: 296 | self.response(to, {"error": "Site %s doesn't support P2P messages" % self.site.address}) 297 | return False 298 | 299 | # Check whether the message matches passive filter 300 | if not SafeRe.match(p2p_json["filter"], json.dumps(message)): 301 | self.response(to, {"error": "Invalid message for site %s: %s" % (self.site.address, message)}) 302 | return False 303 | 304 | # Not so fast 305 | if "freq_limit" in p2p_json and time.time() - self.site.p2p_last_recv.get("self", 0) < p2p_json["freq_limit"]: 306 | self.response(to, {"error": "Too fast messages"}) 307 | return False 308 | self.site.p2p_last_recv["self"] = time.time() 309 | 310 | # Not so much 311 | if "size_limit" in p2p_json and len(json.dumps(message)) > p2p_json["size_limit"]: 312 | self.response(to, {"error": "Too big message"}) 313 | return False 314 | 315 | return True -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from . import SitePlugin 2 | from . import UiWebsocketPlugin 3 | from . import FileRequestPlugin 4 | from . import BackgroundPlugin -------------------------------------------------------------------------------- /p2putil.py: -------------------------------------------------------------------------------- 1 | try: 2 | from MergerSite import MergerSitePlugin 3 | has_merger_plugin = True 4 | except ImportError: 5 | has_merger_plugin = False 6 | 7 | from . import BackgroundPeerMessage 8 | 9 | 10 | def getWebsockets(site): 11 | # First, site's own websockets 12 | websockets = site.websockets[:] 13 | 14 | # Now merger site 15 | if has_merger_plugin: 16 | merger_sites = MergerSitePlugin.merged_to_merger.get(site.address, []) 17 | for merger_site in merger_sites: 18 | if merger_site.address == site.address: 19 | continue 20 | websockets += merger_site.websockets 21 | 22 | # Filter out sites not supporting P2P 23 | # (e.g. ZeroHello, which joins all channels automatically) 24 | return [ws for ws in websockets if "peerReceive" in ws.channels] 25 | -------------------------------------------------------------------------------- /plugin_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PeerMessage", 3 | "description": "Peer-to-peer requests library", 4 | "rev": 6 5 | } --------------------------------------------------------------------------------