jamalex / notion-py

Unofficial Python API client for Notion.so
MIT License
4.28k stars 476 forks source link

Is there a way to add people to a page through the API? #235

Open yanmendes opened 3 years ago

yanmendes commented 3 years ago

This, basically.

Screen Shot 2020-12-11 at 19 24 36

Thanks so much or maintaining this awesome repo =)

aviral-batra commented 3 years ago

Yes, there is.

If you call print(page.get()), you should get a dictionary back.

One of the keys in that dictionary is permissions.

If you edit that dictionary, it will edit the cached data type for that page (i.e. the locally stored dictionary object that represents that page on the notion server). Then you can sync this to the notion server.

Here it is in practice:

user_permissions_dict = { 
'role': 'read_and_write',
'type': 'user_permission',
'user_id': user_id # put the actual user id in here
}

# append the user permissions to the local cached dictionary object
page.get()['permissions'].append(user_permissions_dict)

# now I need to update the data on the notion server using the cached dictionary
# to do this I can call .set()
# 'path' just represents the path to the key in the dictionary
# 'value' is the dictionary object you are going to set the data on the notion server with
page.set(path=['permissions'], value=page.get()['permissions'])

I got the structure for user_permissions_dict using the get method on the page and seeing how permissions were stored in the dictionary object of the page.

The get() and set() methods are in records.py if you want to take a closer look.

N.B. This will work for anything. You can just .get() the data 'framework' of the page/collection etc., edit this dictionary, and then .set() it on the notion server.

yanmendes commented 3 years ago

Thanks for the reply! I must have missed the notification, sorry for taking too long to revisit this.

That solution works if I have the user UID, however, I actually wanted to invite users to the page on the fly, i.e. I just have their email address and they might be notion users or not. Is there a way to dynamically invite users and grab their user id on the fly to enable this solution?

aviral-batra commented 3 years ago

Thanks for the reply! I must have missed the notification, sorry for taking too long to revisit this.

No worries!

That solution works if I have the user UID, however, I actually wanted to invite users to the page on the fly, i.e. I just have their email address and they might be notion users or not. Is there a way to dynamically invite users and grab their user id on the fly to enable this solution?

Yes, there is an api call for this:

client = NotionClient("your tokenv2")
uid_from_email = client.post('findUser', data={"email": "email@gmail.com"}).json()['value']['value']['id']
print(uid_from_email)

If you remove the ['id'] from the end, you should get more information about the user.

they might be notion users or not

You might run into a problem if there is no notion user for a specific email and the script will throw errors. To prevent this, you can replace the ['value']['value']['id'] with .get('value', {}).get('value', {}).get('id', {}). Then output will be {} if the keys are not found. If you want None to be returned, or another value, just put it in place of {}.

aviral-batra commented 3 years ago

I heavily edited the above so I'll @ you: @yanmendes in case you already read it.

yanmendes commented 3 years ago

Thanks a lot, @aviral-batra, that's super clear =). Just to double-check, this would only work if they're notion users, right? Is there an API for actually inviting users to Notion?

aviral-batra commented 3 years ago

@yanmendes There might be an api endpoint for that, but in my opinion an easy way would be to just use an if id == None (or if you choose the empty dictionary to be returned if the user does not exist if id == {}) statement and then send them an email with a link to notion.

yanmendes commented 3 years ago

Let me elaborate on my use case so we're on the same page:

We have a CRM with a bunch of contacts. Whenever they're moved to a specific stage on our deal pipeline, it triggers an automation that should invite them to our (closed) community notion page.

Would this approach enable me to add them to invite them to Notion AND our community pages on the same go? If not, I'd have to periodically sweep the CRM to try and invite them to the page in case they did accept the email invite, which is doable but not ideal.

aviral-batra commented 3 years ago

Ah right - that makes sense.

In all honesty, I haven't dabbled much in this api endpoint, but I think it is createEmailUser. It looks like this is equivalent to when you invite a user using share with their email, but when they do not have a notion account. I'm not 100% sure how it works on the backend, but it seems like it creates a 'temporary' user for that email.

Here is the payload this request takes:

{
    "email": "email@gmail.com",
    "preferredLocale": "en-US",
    "preferredLocaleOrigin": "inferred_from_inviter",
    "productId": "id goes here"
}

You can get the product id and other examples for the values the payload takes by opening the console, trying to add a user without an account manually, and seeing the request payload for the api call.

This again returns an object containing, among other things, a 'temporary' user id and the email, which you can access from the dictionary keys like I showed above. This means that after getting the id, you can set the user permissions like you would if they had an account and it should work.

You can also simultaneously send them the email containing a link to the notion page, and after briefly setting up their account with the email that you set the permissions with, they should be able to access the page.

Would this approach enable me to add them to invite them to Notion AND our community pages on the same go? If not, I'd have to periodically sweep the CRM to try and invite them to the page in case they did accept the email invite, which is doable but not ideal.

Hopefully this is what you were looking for?

N.B. I haven't tested this thoroughly, so if this doesn't work, just ping me and I'll try my best with it.

jzzfs commented 3 years ago

@aviral-batra thanks a ton for pointing me in the right direction👌🏻

Here's what I ended up using to programmatically give read-only access to (new) users whether they already exist in Notion or not.


email = "xyz@mailinator.com"

uid = client.post('findUser',
                  data=dict(email=email))\
    .json()\
    .get('value', {})\
    .get('value', {})\
    .get('id', {})

if not uid:
    try:
        uid = client\
            .post('createEmailUser',
                  data=dict(
                      email=email,
                      preferredLocale="en-US",
                      preferredLocaleOrigin="inferred_from_inviter",
                      productId="prod_xyz"     # <-- find this by inspecting the network tab requests in chrome dev tools
                  )).json().get('userId', None)
        if not uid:
            raise Exception('createEmailUser failed')
    except Exception as e:
        print('tried to findUser: failure')
        print(e)

if not uid:
    raise Exception('no uid')

print('will proceed to update permissions for user %s' % uid)

user_permissions_dict = {
    'role': 'reader',        # <-- it's `reader`, not `read`!
    'type': 'user_permission',
    'user_id': uid
}

page.get()['permissions'].append(user_permissions_dict)
page.set(path=['permissions'], value=page.get()['permissions'])
BarryThrill commented 3 years ago

Hi guys! Joining the discussion abit late but is it possible to know in that case how to delete a user?

also @jzzfs in your latest example, the page is not mention anywhere in your code besides at the very bottom :(

jzzfs commented 3 years ago

@BarryThrill page is coming from the following:

from notion.client import NotionClient

client = NotionClient(email="abc", password="xyz")
page = client.get_block("https://www.notion.so/your-workspace-main-page")
aviral-batra commented 3 years ago

Hi guys! Joining the discussion abit late but is it possible to know in that case how to delete a user?

I haven't seen this in a while and haven't tested it but I would assume if you look at the first post, you can use a generator to search for the user you want to remove, then remove that index from the list. Then set it back to the notion server.

permissions = page.get()['permissions']
index_of_person_to_remove = next((i for i, item in enumerate(permissions) if item["user_id"] == user_id), None) # actual user id here
del permissions[index_of_person_to_remove]
page.set(path=['permissions'], permissions)

And I think this also throws an error if the person is not in the permissions in the first place since the default value is None so you should probably deal with that with an except too.

Edit: Just realised I overcomplicated it a bit by using enumerate and the index rather than the user object itself but oh well it still works.