├── .gitignore ├── HISTORY.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── setup.py └── urlock ├── __init__.py └── urlock.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | urlock.egg-info/ 4 | *.pyc -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 0.1 2020-06-01 2 | - Initial publication -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Tlon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include HISTORY.md 2 | include README.md 3 | include LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Urlock-py 2 | 3 | Urlock-py is a python library for connecting to a running urbit ship. 4 | 5 | It consists of a single class `Urlock`. Methods are below. 6 | 7 | `__init__(url, code)` 8 | 9 | Constructor 10 | 11 | `url` - url to the http interface of the running ship 12 | 13 | `code` - the `+code` of the ship, should never be published 14 | 15 | 16 | `connect()` 17 | 18 | Connect to the running ship 19 | 20 | 21 | `poke(ship, app, mark, j)` 22 | 23 | `ship` - ship to send the poke 24 | 25 | `app` - gall application to send the poke to 26 | 27 | `mark` - mark of data sent to the gall application 28 | 29 | `j` - json poke data 30 | 31 | 32 | `ack(eventId)` 33 | 34 | Send an acknowledgment of receipt of a message so it's cleared from the ship's queue 35 | 36 | `eventId` - id of the event to acknowledge 37 | 38 | 39 | `sse_pipe()` 40 | 41 | returns the sseclient object 42 | 43 | 44 | `subscribe(ship, app, path)` 45 | 46 | `ship` - ship on which the gall application lives 47 | 48 | `app` - gall application to subscribe to 49 | 50 | `path` - path to subscribe on 51 | 52 | 53 | Follows is a simple script to send a message and subscribe to a chat channel. The specifics require an understanding of the `chat-store` and `chat-hook` interfaces. 54 | 55 | ``` 56 | #!/usr/bin/python3 57 | 58 | import urlock 59 | import baseconvert 60 | import time 61 | import random 62 | import dumper 63 | 64 | zod = urlock.Urlock("http://localhost:8080", "lidlut-tabwed-pillex-ridrup") 65 | r = zod.connect() 66 | s = zod.subscribe("zod", "chat-store", "/mailbox/~/~zod/mc") 67 | 68 | pipe = zod.sse_pipe() 69 | 70 | s = baseconvert.base(random.getrandbits(128), 10, 32, string=True).lower() 71 | uid = '0v' + '.'.join(s[i:i+5] for i in range(0, len(s), 5))[::-1] 72 | 73 | p = zod.poke("zod", "chat-hook", "json", {"message": {"path": "/~/~zod/mc", 74 | "envelope": {"uid": uid, 75 | "number": 1, 76 | "author": "~zod", 77 | "when": int(time.time() * 1000), 78 | "letter": {"text": "hello world!"}}}}) 79 | 80 | 81 | for m in pipe.events(): 82 | dumper.dump(m) 83 | dumper.dump(zod.ack(int(m.id))) 84 | ``` -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools 3 | 4 | # Utility function to read the README file. 5 | # Used for the long_description. It's nice, because now 1) we have a top level 6 | # README file and 2) it's easier to type in the README file than to put a raw 7 | # string in below ... 8 | def read(fname): 9 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 10 | 11 | setuptools.setup( 12 | name = "urlock", 13 | version = "0.1.22", 14 | author = "David Kerschner", 15 | author_email = "dkerschner@gmail.com", 16 | description = "Library for talking to a running Urbit ship", 17 | license = "MIT", 18 | keywords = "urbit urlock", 19 | url = "http://github.com/baudtack/urlock-py", 20 | packages=setuptools.find_packages(), 21 | install_requires=['requests', 'sseclient-py'], 22 | long_description=read('README.md'), 23 | long_description_content_type="text/markdown", 24 | ) -------------------------------------------------------------------------------- /urlock/__init__.py: -------------------------------------------------------------------------------- 1 | from urlock.urlock import Urlock 2 | -------------------------------------------------------------------------------- /urlock/urlock.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | import requests 4 | import sseclient 5 | 6 | class Urlock(): 7 | def __init__(self, url, code): 8 | self.uid = str(datetime.datetime.now().timestamp()).split('.', 1)[0] + '-' + random.random().hex()[2:][:-3][-6:] 9 | self.session = requests.Session() 10 | self.lastEventId = 0 11 | self.url = url 12 | self.code = code 13 | self.channelUrl = self.url + "/~/channel/" + self.uid 14 | self.sseclient = False 15 | 16 | def getEventId(self): 17 | self.lastEventId += 1 18 | return self.lastEventId 19 | 20 | def connect(self): 21 | return self.session.post(self.url + '/~/login', data = {'password': self.code}) 22 | 23 | def poke(self, ship, app, mark, j): 24 | res = self.session.put(self.channelUrl, 25 | json = [{ 'id': self.getEventId(), 26 | 'action': "poke", 27 | 'ship': ship, 28 | 'app': app, 29 | 'mark': mark, 30 | 'json': j}]) 31 | return res 32 | 33 | def ack(self, eventId): 34 | return self.session.put(self.channelUrl, json = [{ 'action': "ack", "event-id": eventId }]) 35 | 36 | def sse_pipe(self): 37 | if(not self.sseclient): 38 | self.sseClient = sseclient.SSEClient(self.session.get(self.channelUrl, stream=True)) 39 | return self.sseClient 40 | 41 | def subscribe(self, ship, app, path): 42 | json = [{'id': self.getEventId(), 43 | 'action': "subscribe", 44 | 'ship': ship, 45 | 'app': app, 46 | 'path': path }] 47 | return self.session.put(self.channelUrl, json = json) 48 | 49 | def unsubscribe(self, subscription): 50 | res = self.session.put(self.channelUrl, json = [{'id': self.getEventId(), 51 | 'action': "unsubscribe", 52 | 'subscription': subscription}]) 53 | return res 54 | 55 | def delete(self): 56 | res = self.session.put(self.channelUrl, json = [{'id': self.getEventId(), 57 | 'action': "delete"}]) 58 | return res 59 | --------------------------------------------------------------------------------