notion-py icon indicating copy to clipboard operation
notion-py copied to clipboard

Adding "Discussion" and "Comment" capabilities

Open HenriChabrand opened this issue 4 years ago • 5 comments

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 👍

HenriChabrand avatar Aug 20 '19 16:08 HenriChabrand

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!

jamalex avatar Jan 03 '20 15:01 jamalex

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)

callzhang avatar Nov 22 '20 05:11 callzhang

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
                }
            ]
        }
    ]
}

ashdavies avatar Mar 04 '21 01:03 ashdavies

Maybe with a little bit of guidance, I could try and submit something :)

ashdavies avatar Mar 04 '21 01:03 ashdavies

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.

akpc avatar Jun 15 '21 03:06 akpc