跳转至

permissions

feishu.drive.permissions

PermissionsNamespace

Bases: Namespace

权限接口命名空间。

通过 client.drive.permissions 访问,封装飞书云文档的权限相关接口。以协作者(member)为主资源, 提供增加、移除、列举协作者的能力;并将公共权限设置(public)作为一对方法暴露,用于读取与更新文档的 外部访问、链接分享范围、评论与复制等设置。

通常无需直接实例化,应通过 client.drive.permissions 访问。

飞书文档

权限概述

源代码位于: feishu/drive/permissions.py
Python
class PermissionsNamespace(Namespace):
    r"""
    权限接口命名空间。

    通过 `client.drive.permissions` 访问,封装飞书云文档的权限相关接口。以协作者(member)为主资源,
    提供增加、移除、列举协作者的能力;并将公共权限设置(public)作为一对方法暴露,用于读取与更新文档的
    外部访问、链接分享范围、评论与复制等设置。

    通常无需直接实例化,应通过 `client.drive.permissions` 访问。

    飞书文档:
        [权限概述](https://open.feishu.cn/document/server-docs/docs/permission/permission-member/list)
    """

    async def create(
        self, token: str, member: dict[str, Any], *, doc_type: str, need_notification: bool | None = None
    ) -> NestedDict:
        r"""
        增加协作者权限。

        为指定云文档新增一个协作者。`member` 为协作者条目,需包含 `member_type`(如 `openid`)、
        `member_id` 与 `perm`(如 `view`、`edit`、`full_access`)。`doc_type` 为云文档类型,作为查询参数
        发送,必填。

        Args:
            token: 云文档的 token。
            member: 协作者条目,形如 `{"member_type": "openid", "member_id": "ou_xxx", "perm": "view"}`。
            doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。
            need_notification: 是否向新增协作者发送通知;为空时省略该查询参数。

        Returns:
            新增结果数据,含 `member` 字段(其中含 `member_type`、`member_id`、`perm` 等)。

        Raises:
            feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

        飞书文档:
            [增加协作者权限](https://open.feishu.cn/document/server-docs/docs/permission/permission-member/create)
            参见 [feishu.drive.permissions.PermissionsNamespace.delete][]。

        Examples:
            >>> await client.drive.permissions.create(
            ...     "doxcabc",
            ...     {"member_type": "openid", "member_id": "ou_xxx", "perm": "view"},
            ...     doc_type="docx",
            ... )  # doctest:+SKIP
            {'member': {'member_type': 'openid', 'member_id': 'ou_xxx', 'perm': 'view'}}  # noqa: E501
        """
        params: dict[str, Any] = {"type": doc_type}
        if need_notification is not None:
            params["need_notification"] = need_notification
        return await self._request_data(
            "POST", f"drive/v1/permissions/{quote_segment(token)}/members", params=params, json=member
        )

    async def delete(self, token: str, member_id: str, *, doc_type: str, member_type: str | None = None) -> NestedDict:
        r"""
        移除协作者权限。

        移除指定云文档的某个协作者。`doc_type` 为云文档类型,必填。`member_type` 为协作者 ID 类型,
        留空时由 [feishu.drive.permissions.infer_member_type][] 依据 `member_id` 前缀自动推断
        (`userid` 无固定前缀,须显式传入);二者均作为查询参数发送。

        Args:
            token: 云文档的 token。
            member_id: 待移除协作者的 ID。
            doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。
            member_type: 协作者 ID 类型,例如 `openid`、`unionid`、`userid`、`openchat`、`opendepartmentid`;
                留空时按 `member_id` 前缀自动推断。

        Returns:
            移除结果数据(通常为空)。

        Raises:
            ValueError: 当 `member_type` 留空且无法从 `member_id` 前缀推断时抛出。
            feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

        飞书文档:
            [移除协作者权限](https://open.feishu.cn/document/server-docs/docs/permission/permission-member/delete)
            参见 [feishu.drive.permissions.PermissionsNamespace.create][]。

        Examples:
            >>> await client.drive.permissions.delete("doxcabc", "ou_xxx", doc_type="docx")  # doctest:+SKIP
            {}
        """
        return await self._request_data(
            "DELETE",
            f"drive/v1/permissions/{quote_segment(token)}/members/{quote_segment(member_id)}",
            params={"type": doc_type, "member_type": member_type or infer_member_type(member_id)},
        )

    async def get_public(self, token: str, *, doc_type: str) -> NestedDict:
        r"""
        获取云文档权限设置。

        获取指定云文档的公共权限设置(如外部访问、可分享、链接分享范围、评论与复制权限等)。
        `doc_type` 为云文档类型,作为查询参数发送,必填。

        Args:
            token: 云文档的 token。
            doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。

        Returns:
            权限设置数据,含 `permission_public` 字段(其中含 `external_access`、`link_share_entity`、
            `comment_entity`、`copy_entity` 等字段)。

        Raises:
            feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

        飞书文档:
            [获取云文档权限设置](https://open.feishu.cn/document/server-docs/docs/permission/permission-public/get)
            参见 [feishu.drive.permissions.PermissionsNamespace.set_public][]。

        Examples:
            >>> await client.drive.permissions.get_public(
            ...     "doxcabc", doc_type="docx"
            ... )  # doctest:+SKIP
            {'permission_public': {'external_access': True, 'link_share_entity': 'tenant_readable', ...}}  # noqa: E501
        """
        return await self._request_data(
            "GET", f"drive/v1/permissions/{quote_segment(token)}/public", params={"type": doc_type}
        )

    async def list(self, token: str, *, doc_type: str, fields: str | None = None) -> list[NestedDict]:
        r"""
        获取协作者列表。

        获取指定云文档的全部协作者。该接口不分页,一次性返回全部协作者。`doc_type` 为云文档类型,
        作为查询参数发送,必填。

        Args:
            token: 云文档的 token。
            doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。
            fields: 指定返回的协作者字段集合(如 `*`);为空时省略该查询参数。

        Returns:
            协作者数据列表,每项含 `member_type`、`member_id`、`perm`、`type` 等字段。

        Raises:
            feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

        飞书文档:
            [获取协作者列表](https://open.feishu.cn/document/server-docs/docs/permission/permission-member/list)
            参见 [feishu.drive.permissions.PermissionsNamespace.create][]。

        Examples:
            >>> await client.drive.permissions.list(
            ...     "doxcabc", doc_type="docx"
            ... )  # doctest:+SKIP
            [{'member_type': 'openid', 'member_id': 'ou_xxx', 'perm': 'view', ...}]  # noqa: E501
        """
        params = {"type": doc_type}
        if fields is not None:
            params["fields"] = fields
        envelope = await self._client.request(
            "GET", f"drive/v1/permissions/{quote_segment(token)}/members", params=params
        )
        return list(_data(envelope).get("items", []))

    async def set_public(self, token: str, settings: dict[str, Any], *, doc_type: str) -> NestedDict:
        r"""
        更新云文档权限设置。

        更新指定云文档的公共权限设置。`settings` 为待更新的设置项(仅传需修改的字段,如
        `link_share_entity`、`comment_entity`、`copy_entity` 等)。`doc_type` 为云文档类型,作为查询
        参数发送,必填。

        Args:
            token: 云文档的 token。
            settings: 待更新的权限设置项,形如 `{"link_share_entity": "tenant_readable"}`。
            doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。

        Returns:
            更新后的权限设置数据,含 `permission_public` 字段。

        Raises:
            feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

        飞书文档:
            [更新云文档权限设置](https://open.feishu.cn/document/server-docs/docs/permission/permission-public/patch)
            参见 [feishu.drive.permissions.PermissionsNamespace.get_public][]。

        Examples:
            >>> await client.drive.permissions.set_public(
            ...     "doxcabc", {"link_share_entity": "tenant_readable"}, doc_type="docx"
            ... )  # doctest:+SKIP
            {'permission_public': {'link_share_entity': 'tenant_readable', ...}}  # noqa: E501
        """
        return await self._request_data(
            "PATCH", f"drive/v1/permissions/{quote_segment(token)}/public", params={"type": doc_type}, json=settings
        )

create async

Python
create(token: str, member: dict[str, Any], *, doc_type: str, need_notification: bool | None = None) -> NestedDict

增加协作者权限。

为指定云文档新增一个协作者。member 为协作者条目,需包含 member_type(如 openid)、 member_idperm(如 vieweditfull_access)。doc_type 为云文档类型,作为查询参数 发送,必填。

参数:

名称 类型 描述 默认
token
str

云文档的 token。

必需
member
dict[str, Any]

协作者条目,形如 {"member_type": "openid", "member_id": "ou_xxx", "perm": "view"}

必需
doc_type
str

云文档类型,例如 docdocxsheetbitablefilefolderwiki

必需
need_notification
bool | None

是否向新增协作者发送通知;为空时省略该查询参数。

None

返回:

类型 描述
NestedDict

新增结果数据,含 member 字段(其中含 member_typemember_idperm 等)。

引发:

类型 描述
FeishuError

请求失败或返回错误码时抛出。

飞书文档

增加协作者权限 参见 feishu.drive.permissions.PermissionsNamespace.delete

示例:

Python Console Session
1
2
3
4
5
6
>>> await client.drive.permissions.create(
...     "doxcabc",
...     {"member_type": "openid", "member_id": "ou_xxx", "perm": "view"},
...     doc_type="docx",
... )
{'member': {'member_type': 'openid', 'member_id': 'ou_xxx', 'perm': 'view'}}  # noqa: E501
源代码位于: feishu/drive/permissions.py
Python
async def create(
    self, token: str, member: dict[str, Any], *, doc_type: str, need_notification: bool | None = None
) -> NestedDict:
    r"""
    增加协作者权限。

    为指定云文档新增一个协作者。`member` 为协作者条目,需包含 `member_type`(如 `openid`)、
    `member_id` 与 `perm`(如 `view`、`edit`、`full_access`)。`doc_type` 为云文档类型,作为查询参数
    发送,必填。

    Args:
        token: 云文档的 token。
        member: 协作者条目,形如 `{"member_type": "openid", "member_id": "ou_xxx", "perm": "view"}`。
        doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。
        need_notification: 是否向新增协作者发送通知;为空时省略该查询参数。

    Returns:
        新增结果数据,含 `member` 字段(其中含 `member_type`、`member_id`、`perm` 等)。

    Raises:
        feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

    飞书文档:
        [增加协作者权限](https://open.feishu.cn/document/server-docs/docs/permission/permission-member/create)
        参见 [feishu.drive.permissions.PermissionsNamespace.delete][]。

    Examples:
        >>> await client.drive.permissions.create(
        ...     "doxcabc",
        ...     {"member_type": "openid", "member_id": "ou_xxx", "perm": "view"},
        ...     doc_type="docx",
        ... )  # doctest:+SKIP
        {'member': {'member_type': 'openid', 'member_id': 'ou_xxx', 'perm': 'view'}}  # noqa: E501
    """
    params: dict[str, Any] = {"type": doc_type}
    if need_notification is not None:
        params["need_notification"] = need_notification
    return await self._request_data(
        "POST", f"drive/v1/permissions/{quote_segment(token)}/members", params=params, json=member
    )

delete async

Python
delete(token: str, member_id: str, *, doc_type: str, member_type: str | None = None) -> NestedDict

移除协作者权限。

移除指定云文档的某个协作者。doc_type 为云文档类型,必填。member_type 为协作者 ID 类型, 留空时由 feishu.drive.permissions.infer_member_type 依据 member_id 前缀自动推断 (userid 无固定前缀,须显式传入);二者均作为查询参数发送。

参数:

名称 类型 描述 默认
token
str

云文档的 token。

必需
member_id
str

待移除协作者的 ID。

必需
doc_type
str

云文档类型,例如 docdocxsheetbitablefilefolderwiki

必需
member_type
str | None

协作者 ID 类型,例如 openidunioniduseridopenchatopendepartmentid; 留空时按 member_id 前缀自动推断。

None

返回:

类型 描述
NestedDict

移除结果数据(通常为空)。

引发:

类型 描述
ValueError

member_type 留空且无法从 member_id 前缀推断时抛出。

FeishuError

请求失败或返回错误码时抛出。

飞书文档

移除协作者权限 参见 feishu.drive.permissions.PermissionsNamespace.create

示例:

Python Console Session
>>> await client.drive.permissions.delete("doxcabc", "ou_xxx", doc_type="docx")
{}
源代码位于: feishu/drive/permissions.py
Python
async def delete(self, token: str, member_id: str, *, doc_type: str, member_type: str | None = None) -> NestedDict:
    r"""
    移除协作者权限。

    移除指定云文档的某个协作者。`doc_type` 为云文档类型,必填。`member_type` 为协作者 ID 类型,
    留空时由 [feishu.drive.permissions.infer_member_type][] 依据 `member_id` 前缀自动推断
    (`userid` 无固定前缀,须显式传入);二者均作为查询参数发送。

    Args:
        token: 云文档的 token。
        member_id: 待移除协作者的 ID。
        doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。
        member_type: 协作者 ID 类型,例如 `openid`、`unionid`、`userid`、`openchat`、`opendepartmentid`;
            留空时按 `member_id` 前缀自动推断。

    Returns:
        移除结果数据(通常为空)。

    Raises:
        ValueError: 当 `member_type` 留空且无法从 `member_id` 前缀推断时抛出。
        feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

    飞书文档:
        [移除协作者权限](https://open.feishu.cn/document/server-docs/docs/permission/permission-member/delete)
        参见 [feishu.drive.permissions.PermissionsNamespace.create][]。

    Examples:
        >>> await client.drive.permissions.delete("doxcabc", "ou_xxx", doc_type="docx")  # doctest:+SKIP
        {}
    """
    return await self._request_data(
        "DELETE",
        f"drive/v1/permissions/{quote_segment(token)}/members/{quote_segment(member_id)}",
        params={"type": doc_type, "member_type": member_type or infer_member_type(member_id)},
    )

get_public async

Python
get_public(token: str, *, doc_type: str) -> NestedDict

获取云文档权限设置。

获取指定云文档的公共权限设置(如外部访问、可分享、链接分享范围、评论与复制权限等)。 doc_type 为云文档类型,作为查询参数发送,必填。

参数:

名称 类型 描述 默认
token
str

云文档的 token。

必需
doc_type
str

云文档类型,例如 docdocxsheetbitablefilefolderwiki

必需

返回:

类型 描述
NestedDict

权限设置数据,含 permission_public 字段(其中含 external_accesslink_share_entity

NestedDict

comment_entitycopy_entity 等字段)。

引发:

类型 描述
FeishuError

请求失败或返回错误码时抛出。

飞书文档

获取云文档权限设置 参见 feishu.drive.permissions.PermissionsNamespace.set_public

示例:

Python Console Session
1
2
3
4
>>> await client.drive.permissions.get_public(
...     "doxcabc", doc_type="docx"
... )
{'permission_public': {'external_access': True, 'link_share_entity': 'tenant_readable', ...}}  # noqa: E501
源代码位于: feishu/drive/permissions.py
Python
async def get_public(self, token: str, *, doc_type: str) -> NestedDict:
    r"""
    获取云文档权限设置。

    获取指定云文档的公共权限设置(如外部访问、可分享、链接分享范围、评论与复制权限等)。
    `doc_type` 为云文档类型,作为查询参数发送,必填。

    Args:
        token: 云文档的 token。
        doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。

    Returns:
        权限设置数据,含 `permission_public` 字段(其中含 `external_access`、`link_share_entity`、
        `comment_entity`、`copy_entity` 等字段)。

    Raises:
        feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

    飞书文档:
        [获取云文档权限设置](https://open.feishu.cn/document/server-docs/docs/permission/permission-public/get)
        参见 [feishu.drive.permissions.PermissionsNamespace.set_public][]。

    Examples:
        >>> await client.drive.permissions.get_public(
        ...     "doxcabc", doc_type="docx"
        ... )  # doctest:+SKIP
        {'permission_public': {'external_access': True, 'link_share_entity': 'tenant_readable', ...}}  # noqa: E501
    """
    return await self._request_data(
        "GET", f"drive/v1/permissions/{quote_segment(token)}/public", params={"type": doc_type}
    )

list async

Python
list(token: str, *, doc_type: str, fields: str | None = None) -> list[NestedDict]

获取协作者列表。

获取指定云文档的全部协作者。该接口不分页,一次性返回全部协作者。doc_type 为云文档类型, 作为查询参数发送,必填。

参数:

名称 类型 描述 默认
token
str

云文档的 token。

必需
doc_type
str

云文档类型,例如 docdocxsheetbitablefilefolderwiki

必需
fields
str | None

指定返回的协作者字段集合(如 *);为空时省略该查询参数。

None

返回:

类型 描述
list[NestedDict]

协作者数据列表,每项含 member_typemember_idpermtype 等字段。

引发:

类型 描述
FeishuError

请求失败或返回错误码时抛出。

飞书文档

获取协作者列表 参见 feishu.drive.permissions.PermissionsNamespace.create

示例:

Python Console Session
1
2
3
4
>>> await client.drive.permissions.list(
...     "doxcabc", doc_type="docx"
... )
[{'member_type': 'openid', 'member_id': 'ou_xxx', 'perm': 'view', ...}]  # noqa: E501
源代码位于: feishu/drive/permissions.py
Python
async def list(self, token: str, *, doc_type: str, fields: str | None = None) -> list[NestedDict]:
    r"""
    获取协作者列表。

    获取指定云文档的全部协作者。该接口不分页,一次性返回全部协作者。`doc_type` 为云文档类型,
    作为查询参数发送,必填。

    Args:
        token: 云文档的 token。
        doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。
        fields: 指定返回的协作者字段集合(如 `*`);为空时省略该查询参数。

    Returns:
        协作者数据列表,每项含 `member_type`、`member_id`、`perm`、`type` 等字段。

    Raises:
        feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

    飞书文档:
        [获取协作者列表](https://open.feishu.cn/document/server-docs/docs/permission/permission-member/list)
        参见 [feishu.drive.permissions.PermissionsNamespace.create][]。

    Examples:
        >>> await client.drive.permissions.list(
        ...     "doxcabc", doc_type="docx"
        ... )  # doctest:+SKIP
        [{'member_type': 'openid', 'member_id': 'ou_xxx', 'perm': 'view', ...}]  # noqa: E501
    """
    params = {"type": doc_type}
    if fields is not None:
        params["fields"] = fields
    envelope = await self._client.request(
        "GET", f"drive/v1/permissions/{quote_segment(token)}/members", params=params
    )
    return list(_data(envelope).get("items", []))

set_public async

Python
set_public(token: str, settings: dict[str, Any], *, doc_type: str) -> NestedDict

更新云文档权限设置。

更新指定云文档的公共权限设置。settings 为待更新的设置项(仅传需修改的字段,如 link_share_entitycomment_entitycopy_entity 等)。doc_type 为云文档类型,作为查询 参数发送,必填。

参数:

名称 类型 描述 默认
token
str

云文档的 token。

必需
settings
dict[str, Any]

待更新的权限设置项,形如 {"link_share_entity": "tenant_readable"}

必需
doc_type
str

云文档类型,例如 docdocxsheetbitablefilefolderwiki

必需

返回:

类型 描述
NestedDict

更新后的权限设置数据,含 permission_public 字段。

引发:

类型 描述
FeishuError

请求失败或返回错误码时抛出。

飞书文档

更新云文档权限设置 参见 feishu.drive.permissions.PermissionsNamespace.get_public

示例:

Python Console Session
1
2
3
4
>>> await client.drive.permissions.set_public(
...     "doxcabc", {"link_share_entity": "tenant_readable"}, doc_type="docx"
... )
{'permission_public': {'link_share_entity': 'tenant_readable', ...}}  # noqa: E501
源代码位于: feishu/drive/permissions.py
Python
async def set_public(self, token: str, settings: dict[str, Any], *, doc_type: str) -> NestedDict:
    r"""
    更新云文档权限设置。

    更新指定云文档的公共权限设置。`settings` 为待更新的设置项(仅传需修改的字段,如
    `link_share_entity`、`comment_entity`、`copy_entity` 等)。`doc_type` 为云文档类型,作为查询
    参数发送,必填。

    Args:
        token: 云文档的 token。
        settings: 待更新的权限设置项,形如 `{"link_share_entity": "tenant_readable"}`。
        doc_type: 云文档类型,例如 `doc`、`docx`、`sheet`、`bitable`、`file`、`folder`、`wiki`。

    Returns:
        更新后的权限设置数据,含 `permission_public` 字段。

    Raises:
        feishu.errors.FeishuError: 请求失败或返回错误码时抛出。

    飞书文档:
        [更新云文档权限设置](https://open.feishu.cn/document/server-docs/docs/permission/permission-public/patch)
        参见 [feishu.drive.permissions.PermissionsNamespace.get_public][]。

    Examples:
        >>> await client.drive.permissions.set_public(
        ...     "doxcabc", {"link_share_entity": "tenant_readable"}, doc_type="docx"
        ... )  # doctest:+SKIP
        {'permission_public': {'link_share_entity': 'tenant_readable', ...}}  # noqa: E501
    """
    return await self._request_data(
        "PATCH", f"drive/v1/permissions/{quote_segment(token)}/public", params={"type": doc_type}, json=settings
    )

infer_member_type

Python
infer_member_type(member_id: str) -> str

根据协作者 ID 的前缀推断其 member_type

依据可靠前缀推断权限协作者的 ID 类型,便于在增删协作者时省略 member_type

前缀 推断结果
ou_ 开头 openid
on_ 开头 unionid
oc_ 开头 openchat
od- / od_ 开头 opendepartmentid

userid 没有固定前缀,无法可靠识别,因此当 member_id 不匹配上述任一规则时抛出 ValueError,请显式传入 member_type

参数:

名称 类型 描述 默认

member_id

str

协作者 ID(用户 open/union ID、群 ID 或部门 open ID)。

必需

返回:

类型 描述
str

推断出的 member_type 字符串。

引发:

类型 描述
ValueError

当无法从 member_id 前缀可靠推断类型时抛出。

示例:

Python Console Session
1
2
3
4
>>> infer_member_type("ou_abc")
'openid'
>>> infer_member_type("oc_abc")
'openchat'
源代码位于: feishu/drive/permissions.py
Python
def infer_member_type(member_id: str) -> str:
    r"""
    根据协作者 ID 的前缀推断其 `member_type`。

    依据可靠前缀推断权限协作者的 ID 类型,便于在增删协作者时省略 `member_type`:

    | 前缀 | 推断结果 |
    |------|----------|
    | `ou_` 开头 | `openid` |
    | `on_` 开头 | `unionid` |
    | `oc_` 开头 | `openchat` |
    | `od-` / `od_` 开头 | `opendepartmentid` |

    `userid` 没有固定前缀,无法可靠识别,因此当 `member_id` 不匹配上述任一规则时抛出
    `ValueError`,请显式传入 `member_type`。

    Args:
        member_id: 协作者 ID(用户 open/union ID、群 ID 或部门 open ID)。

    Returns:
        推断出的 `member_type` 字符串。

    Raises:
        ValueError: 当无法从 `member_id` 前缀可靠推断类型时抛出。

    Examples:
        >>> infer_member_type("ou_abc")
        'openid'
        >>> infer_member_type("oc_abc")
        'openchat'
    """
    if member_id.startswith("ou_"):
        return "openid"
    if member_id.startswith("on_"):
        return "unionid"
    if member_id.startswith("oc_"):
        return "openchat"
    if member_id.startswith(("od-", "od_")):
        return "opendepartmentid"
    raise ValueError(
        f"cannot infer member_type from {member_id!r}; pass member_type explicitly " "(userid has no reliable prefix)"
    )