Skip to content

Commit d5a8aa8

Browse files
committed
squashme: redo bot api
1 parent fb9fd46 commit d5a8aa8

File tree

3 files changed

+72
-179
lines changed

3 files changed

+72
-179
lines changed

docs/bot-api.md

Lines changed: 5 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,7 @@ zmq commands:
66

77
* [sogs.sub](#sub)
88
* [sogs.unsub](#unsub)
9-
10-
* [sogs.user.info](#user-info)
11-
* [sogs.user.send](#user-send)
12-
* [sogs.user.perm](#user-perm)
13-
* [sogs.user.ban](#user-ban)
14-
* [sogs.user.unban](#user-unban)
15-
16-
* [sogs.room.info](#room-info)
17-
* [sogs.room.send](#room-send)
18-
* [sogs.room.perm](#room-perm)
19-
20-
* [sogs.file.info](#file-info)
21-
* [sogs.file.upload](#file-upload)
22-
* [sogs.file.del](#file-del)
23-
* [sogs.file.ban](#file-ban)
24-
* [sogs.file.unban](#file-ban)
25-
26-
27-
# Objects
28-
29-
## UserInfo
30-
31-
a dict containing info on a user
32-
33-
* id - the integer id for this user
34-
* pubkey - the user's 32 byte public x25519 key, session id is 0x05 then the pubkey bytes all hex encoded
35-
* names - a dict of room token as key and displayed name in that room
36-
* mod - if the user is a moderator positive non zero otherwise zero
37-
38-
## RoomInfo
39-
40-
a dict containing info on a room
41-
42-
* id - the integer id for this room
43-
* token - the name of this room used in the url
44-
* users - a list of UserInfo of all the users in the room (optional)
45-
46-
## FileInfo
47-
48-
a dict describing a file
49-
50-
* id - internal id for referring to this file
51-
* public-url - a public url to fetch the file at
52-
* size - the size of this file in bytes
53-
* mimetype - the implied mimetpye of this file
54-
* uploaded - the time this file was uploaded at in milliseconds since unix epoch
55-
* hashes - a dict of hash algorithm name to the digest value of the file with that algorithm (currently supported algorithms: blake2 / sha2-256 / sha1 / md5 )
56-
* user - the user id of who uploaded this file
57-
58-
## PostInfo
59-
60-
a dict describing a post made to a room
61-
62-
* id - the post's id
63-
* user - the id of the user that posted the message
64-
* room - the id of the room that the message was posted in
65-
* when - the time this message was recieved in the room as milliseconds since unix epoch
66-
* text - the plaintext body of the message as a string
67-
* files - a list of file ids this message uploaded files with (optional)
68-
* cites - a post id this post is citing (optional)
9+
* [sogs.request](#request)
6910

7011

7112
# Events
@@ -112,54 +53,10 @@ subscribe to events
11253

11354
unsubscribe to events
11455

115-
## sogs.user.info <span id="user-info" />
116-
117-
fetch info of a user
118-
119-
## sogs.user.send <span id="user-send" />
120-
121-
send a whisper to a user
122-
123-
## sogs.user.perm <span id="user-perm" />
124-
125-
get/set user permissions (read/write/upload)
126-
127-
## sogs.user.ban <span id="user-ban" />
128-
129-
ban user
130-
131-
## sogs.user.unban <span id="user-unban" />
132-
133-
unban user
134-
135-
## sogs.room.info <span id="room-info" />
136-
137-
get/set room info
138-
139-
## sogs.room.send <span id="room-send" />
140-
141-
send a message to the room
142-
143-
## sogs.room.perm <span id="room-perm" />
144-
145-
get/set room permissions (read/write/upload)
146-
147-
## sogs.file.info <span id="file-info" />
148-
149-
get uploaded file info
150-
151-
## sogs.file.upload <span id="file-upload" />
152-
153-
upload a file
154-
155-
## sogs.file.del <span id="file-del" />
156-
157-
delete a file
158-
159-
## sogs.file.ban <span id="file-ban" />
56+
## sogs.request <span id="request" />
16057

161-
ban file uploads by mimetype/hash/etc optionally per room
58+
call an http api endpoint
16259

163-
## sogs.file.unban <span id="file-unban" />
60+
args: [method, path, headers_dict, optional_body]
16461

165-
unban uploads by mimetype/hash/etc optionally per room
62+
returns: [http_status, headers_dict, response_bytes]

sogs/events.py

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
from oxenmq import AuthLevel
44

5+
from . import config
56
from . import model
67
from .omq import omq
78

89
from binascii import hexlify
910

1011
from oxenc import bt_serialize as serialize
12+
from oxenc import bt_deserialize as deserialize
13+
14+
from requests import request as http_request
1115

1216
# pools for event propagation
1317
_pools = defaultdict(list)
@@ -21,7 +25,7 @@
2125

2226
def event_name_valid(eventname):
2327
""" return True if this event name is something well formed """
24-
return eventname.lower() in EVENTS
28+
return eventname in EVENTS
2529

2630

2731
def _user_from_conn(conn, automod=True):
@@ -43,72 +47,29 @@ def _sub_conn_to_event(name, conn):
4347
_pools[name].append(conn)
4448

4549

50+
def _unsub_conn_from_event(name, conn):
51+
if not event_name_valid(name):
52+
raise Exception(f"invalid event: {name}")
53+
global _pool
54+
_pools[name].remove(conn)
55+
56+
4657
def _handle_subscribe_request(msg):
4758
""" handle a request to subscribe to events as a bot"""
4859
parts = msg.data()
4960
if len(parts) == 0:
5061
raise Exception("no events selected")
51-
5262
for name in parts:
53-
name = name.decode()
54-
_sub_conn_to_event(name, msg.conn)
55-
56-
57-
def _handle_find_room(metric, query):
58-
""" find a room """
59-
if metric == 'id':
60-
room = model.Room(id=int(query))
61-
elif metric == 'token':
62-
room = model.Room(token=query)
63-
else:
64-
raise Exception("invalid query metric: {}".format(metric))
65-
reply = {a: getattr(room, a) for a in model.Room.ALL_PROPS}
66-
reply['mods'] = room.get_mods()
67-
reply['active_users'] = room.active_users()
68-
return reply
69-
70-
71-
def _handle_find_user(metric, query):
72-
""" find a user """
73-
if metric == 'id':
74-
user = model.User(id=int(query))
75-
elif metric == 'pubkey':
76-
user = model.User(session_id=query)
77-
else:
78-
raise Exception("invalid query metric: {}".format(metric))
79-
reply = {a: getattr(user, a) for a in model.User.ALL_PROPS}
80-
return reply
81-
82-
83-
def _handle_find_request(msg):
84-
""" finds a user / room by id / token / pubkey """
85-
parts = msg.data()
86-
if len(parts) != 3:
87-
raise Exception('3 arguments required: entity-kind, query-metric, query-value')
63+
_sub_conn_to_event(name.decode(), msg.conn)
8864

89-
kind = parts[0].decode('utf-8')
90-
metric = parts[1].decode('utf-8')
91-
query = parts[2].decode('utf-8')
9265

93-
_kinds = {'room': _handle_find_room, 'user': _handle_find_user}
94-
95-
if kind in _kinds:
96-
return _kinds[kind](metric, query)
97-
raise Exception("cannot find a '{}' we dont have those".format(kind))
98-
99-
100-
def _handle_mod_ban(msg):
101-
""" handle a ban user from room rqeuest """
66+
def _handle_unsubscribe_request(msg):
67+
""" unusb from events """
10268
parts = msg.data()
103-
if len(parts) < 2:
104-
raise Exception("Not enough arguments, need 2 arguments: user_id, room_id")
105-
room_id = int(parts[1])
106-
user_id = int(parts[0])
107-
room = model.Room(id=room_id)
108-
ban_user = model.User(id=user_id)
109-
bot = _user_from_conn(msg.conn)
110-
if not model.ban_user(bot, room, ban_user):
111-
raise Exception("user not banned")
69+
if len(parts) == 0:
70+
raise Exception("no events selected")
71+
for name in parts:
72+
_unsub_conn_from_event(name.decode(), msg.conn)
11273

11374

11475
def _propagate_event(eventname, *args):
@@ -121,33 +82,68 @@ def _propagate_event(eventname, *args):
12182

12283

12384
def _handle_request(handler, msg):
124-
""" safely handle a request handler and catch any exceptions that happen and propagate them to the remote connection """
85+
"""safely handle a request handler and catch any exceptions that happen and propagate them to
86+
the remote connection"""
12587
try:
12688
retval = handler(msg)
12789
if retval is None:
12890
msg.reply(status_OK)
91+
elif isinstance(retval, tuple):
92+
msg.reply(status_OK, *retval)
12993
else:
13094
msg.reply(status_OK, serialize(retval))
13195
except Exception as ex:
13296
msg.reply(status_ERR, '{}'.format(ex))
13397

13498

99+
def _handle_api_request(msg):
100+
""" do an http api request """
101+
parts = msg.parts()
102+
if len(parts) < 3:
103+
raise Exception("invalid number of parts {}".format(len(parts)))
104+
kwargs = dict()
105+
if len(parts) >= 4:
106+
kwargs['body'] = parts[3]
107+
108+
headers = deserialize(parts[2])
109+
if not isinstance(headers, dict):
110+
raise ValueError("headers is not a dict")
111+
112+
kwargs['headers'] = headers
113+
114+
path = parts[1].decode()
115+
if not path.startswith("/"):
116+
raise ValueError("invalid request path")
117+
method = parts[0].decode()
118+
resp = http_request(method, config.URL_BASE + path, **kwargs)
119+
120+
headers = dict()
121+
for k, v in resp.headers.items():
122+
headers[k] = v
123+
124+
return serialize(resp.status_code), serialize(headers), resp.content
125+
126+
135127
def setup_rpc():
136128
""" set up bot api using an existing oxenmq instance """
137129
_bot_category = omq.add_category('sogs', AuthLevel.none)
138130
_bot_category.add_request_command(
139131
'sub', lambda msg: _handle_request(_handle_subscribe_request, msg)
140132
)
141133
_bot_category.add_request_command(
142-
'find', lambda msg: _handle_request(_handle_find_request, msg)
134+
'unsub', lambda msg: _handle_request(_handle_unsubscribe_request, msg)
143135
)
144-
_bot_category.add_request_command('ban', lambda msg: _handle_request(_handle_mod_ban, msg))
136+
_bot_category.add_request_command(
137+
'request', lambda msg: _handle_request(_handle_api_request, msg)
138+
)
139+
145140

141+
class _Notify:
142+
""" Holder type for all event notification functions """
146143

147-
class Notify:
148-
""" Holder for all event notification functions """
149144

145+
notify = _Notify()
150146

151147
# set up event notifiers
152148
for ev in EVENTS:
153-
setattr(Notify, ev, lambda *args: _propagate_event(ev, *args))
149+
setattr(notify, ev, lambda *args: _propagate_event(ev, *args))

sogs/mule.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from . import cleanup
99
from . import config
1010
from . import omq as o
11-
from .events import setup_rpc, Notify
11+
from .events import setup_rpc, notify
1212

1313
# This is the uwsgi "mule" that handles things not related to serving HTTP requests:
1414
# - it holds the oxenmq instance (with its own interface into sogs)
@@ -99,32 +99,32 @@ def wrapper(*args, **kwargs):
9999

100100
@log_exceptions
101101
def message_posted(m: oxenmq.Message):
102-
Notify.message(*m.data())
102+
notify.message(*m.data())
103103

104104

105105
@log_exceptions
106106
def messages_deleted(m: oxenmq.Message):
107-
Notify.deleted(*m.data())
107+
notify.deleted(*m.data())
108108

109109

110110
@log_exceptions
111111
def user_banned(m: oxenmq.Message):
112-
Notify.banned(*m.data())
112+
notify.banned(*m.data())
113113

114114

115115
@log_exceptions
116116
def user_unbanned(m: oxenmq.Message):
117-
Notify.unbannd(*m.data())
117+
notify.unbannd(*m.data())
118118

119119

120120
@log_exceptions
121121
def user_joined(m: oxenmq.Message):
122-
Notify.joined(*m.data())
122+
notify.joined(*m.data())
123123

124124

125125
@log_exceptions
126126
def file_uploaded(m: oxenmq.Message):
127-
Notify.uploaded(*m.data())
127+
notify.uploaded(*m.data())
128128

129129

130130
@log_exceptions

0 commit comments

Comments
 (0)