notion-py
notion-py copied to clipboard
Adding "Discussion" and "Comment" capabilities
First of all, I wanted to say thank you for this implementation of the Notion API 🙌
It is really really useful, it is crazy all the automation opportunities it brings and shows the real potential of Notion.
I was wondering if you had explored the possibility to handle Discussion and Comment? Allowing to open a new Discussion on a block, to add a Comment to a Discussion – and even to listen to new Comments – like you manage to do it with live-update of Blocks?
Once again, thank you for this repo – it is already really amazing 👍
This would be great! It shouldn't be too hard to do, with a bit of poking around in the API in the browser's network tools to understand the endpoints, and then creating some classes and methods to serve as the notion-py interface.
Would be a great community contribution! Marking it as "help wanted" -- if anyone wants to try it, and has any questions, feel free to ask here!
I wrote my own code to handle the edits
and comments
. Please note that the code is highly customized and you need to modify it to suit your own need.
result = cv.build_query(filter=cv.get('query2')['filter']).execute()
for record in result:
record_updates = {
'name': record.name,
'project_ids': record.项目ID,
'assign': record.负责人[0].full_name if len(record.负责人)>0 else '',
'status': record.状态,
'risk': record.风险,
'comments': [],
'changes': [],
}
# fetch activities
query = {
'limit': 10,
'navigableBlockId': record.id,
'spaceId': record.get('space_id')
}
r = client.post('getActivityLog', data=query)
for id, activity in r.json()['recordMap']['activity'].items():
if (time.time() - int(activity['value']['end_time'])/1000)>delta:
continue
v = activity['value']
record_updates['type'].append(v['type'])
record_updates['time'].append(v['end_time'])
# block updates
if v['type'] == "block-edited":
try:
changes = {}
# group changes
for edit in v['edits']:
if edit['type'] in ['block-edited', 'block-changed']:
btype = edit['block_data']['before']['block_value']['type']
# prevent page change to be displayed
if btype not in ['text', 'page', 'bulleted_list']:
print('unhandled edit change type->', btype)
# only handle edits that have both "before" and "after" values
if 'properties' not in edit['block_data']['after']['block_value'] or 'properties' not in edit['block_data']['before']['block_value']:
continue
after = edit['block_data']['after']['block_value']['properties']
before = edit['block_data']['before']['block_value']['properties']
change = {k2: diffStr(v1[0][0], v2[0][0]) for k1, v1 in before.items() for k2, v2 in after.items() if v1 != v2 and k1==k2}
# change id to name
if 'block_schema' in edit:
schema = edit['block_schema']
change = {schema[k]['name']: v for k, v in change.items()}
elif edit['type'] == 'block-created':
if 'properties' not in edit['block_data']['block_value']:
continue
change = edit['block_data']['block_value']['properties']
change = {k: diffStr('', v[0][0]) for k, v in change.items()}
else:
if edit['type'] not in ['block-deleted']:
print('unhandled block change type->', edit['type'])
continue
record_updates['changes'] += changes
except KeyError as e:
print('Unhandled Key in updates:', e)
# comments
elif v['type'] == 'commented':
comments = []
for comment in v['edits']:
if comment['type'] == 'comment-changed':
before = comment['comment_data']['before']['text'][0][0]
after = comment['comment_data']['after']['text'][0][0]
comments.append(diffStr(before, after))
else:
comments.append(comment['comment_data']['text'][0][0])
record_updates['comments'] += comments
else:
print('unhandled activity type->', v['type'], 'details:\n', v)
continue
records_updated.append(record_updates)
The diffStr function is just to transfer the changes to readable format:
def diffStr(a: str, b: str):
"""Unify operations between two compared strings
seqm is a difflib.SequenceMatcher instance whose a & b are strings"""
seqm = difflib.SequenceMatcher(None, a, b)
output = []
for opcode, a0, a1, b0, b1 in seqm.get_opcodes():
if opcode == 'equal':
output.append(seqm.a[a0:a1])
elif opcode == 'insert':
output.append("<ins>" + seqm.b[b0:b1] + "</ins>")
elif opcode == 'delete':
output.append("<del>" + seqm.a[a0:a1] + "</del>")
elif opcode == 'replace':
output.append("<del>" + seqm.a[a0:a1] + "</del>")
output.append("<ins>" + seqm.b[b0:b1] + "</ins>")
else:
raise RuntimeError("unexpected opcode")
return ''.join(output)
An API for writing comments would be super useful, I'd be happy to contribute to this repo, but my Python skills are severely limited at best 😅, I did some sniffing of the web API request, and came up with this, maybe it's helpful?
{
"requestId": "...",
"transactions": [
{
"id": "...",
"spaceId": "...",
"operations": [
{
"pointer": {
"table": "comment",
"id": "..."
},
"path": [],
"command": "set",
"args": {
"parent_id": "...",
"parent_table": "discussion",
"text": [
[
"Test Comment"
]
],
"created_by_table": "notion_user",
"created_by_id": "...",
"alive": true,
"id": "...",
"version": 1
}
},
{
"pointer": {
"table": "discussion",
"id": "..."
},
"path": [
"comments"
],
"command": "listAfter",
"args": {
"id": "..."
}
},
{
"pointer": {
"table": "comment",
"id": "..."
},
"path": [
"created_time"
],
"command": "set",
"args": 1614819480000
},
{
"pointer": {
"table": "comment",
"id": "..."
},
"path": [
"last_edited_time"
],
"command": "set",
"args": 1614819480000
}
]
}
]
}
Maybe with a little bit of guidance, I could try and submit something :)
I'm working on a version for own use from discussion in this thread.
This is what I learned from the comment above https://github.com/jamalex/notion-py/issues/45#issuecomment-731703088
ret = client.post('getActivityLog', data = {'limit': <limit>, 'navigableBlockId': <record.id>, 'spaceId': <record.get('space_id'>})
All the data needed for reading the discussions are included in ret.json()
You may be able to read comments and all other stuffs from filtering this response.