Skip to content

HSPK/slark

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Slark

PyPI version

Simple LARK(Feishu) SDK

Usage

pip install slark
from slark import AsyncLark

lark = AsyncLark(
    app_id=xxx, app_secret=xxx, webhook=xxx
)

Spreadsheets

Read table

await lark.sheets.read(
    url: str,
    *,
    start_row: int = 0,
    start_col: int = 0,
    rows: Union[int, None] = None,
    cols: Union[int, None] = None,
    has_header: bool = True,
    dropna: bool = True,
    valueRenderOption: Union[
        Literal["ToString", "Formula", "FormattedValue", "UnformattedValue"], None
    ] = None,
    dateTimeRenderOption: Union[Literal["FormattedString"], None] = None,
    user_id_type: Union[Literal["open_id", "union_id"], None] = None,
    return_raw: bool = False,
    timeout: Union[httpx.Timeout, None] = None,
)
    """从电子表格读取数据,该接口返回数据的最大限制为 10 MB。该接口不支持获取跨表引用和数组公式的计算结果。

    Args:
        url (str): 电子表格的 URL 链接
        start_row (int, optional): 起始行数. Defaults to 0.
        start_col (int, optional): 起始列数. Defaults to 0.
        rows (Union[int, None], optional): 读取行数. Defaults to None.
        cols (Union[int, None], optional): 读取列数. Defaults to None.
        has_header (bool, optional): 是否包括标题. Defaults to True.
        dropna (bool, optional): 是否去除全为空的行/列. Defaults to True.
        valueRenderOption (Literal[ "ToString", "Formula", "FormattedValue", "UnformattedValue" ], None, optional): \
            指定单元格数据的格式。可选值如下所示。\
            当参数缺省时,默认不进行公式计算,返回公式本身,且单元格为数值格式。\
            ToString:返回纯文本的值(数值类型除外)。\
            Formula:单元格中含有公式时,返回公式本身。\
            FormattedValue:计算并格式化单元格。\
            UnformattedValue:计算但不对单元格进行格式化. Defaults to None.
        dateTimeRenderOption (Literal["FormattedString"], None, optional):\
            指定数据类型为日期、时间、或时间日期的单元格数据的格式。\
            若不传值,默认返回浮点数值,整数部分为自 1899 年 12 月 30 日以来的天数;\
            小数部分为该时间占 24 小时的份额。\
            例如:若时间为 1900 年 1 月 1 日中午 12 点,则默认返回 2.5。\
            其中,2 表示 1900 年 1 月 1 日为 1899 年12 月 30 日之后的 2 天;\
            0.5 表示 12 点占 24 小时的二分之一,即 12/24=0.5。\
            可选值为 FormattedString,此时接口将计算并对日期、时间、或时间日期类型的数据格式化并返回格式化后的字符串,但不会对数字进行格式化。. Defaults to None.
        user_id_type (Literal["open_id", "union_id"], None, optional):\
            当单元格中包含@用户等涉及用户信息的元素时,该参数可指定返回的用户 ID 类型。\
            默认为 lark_id,建议选择 open_id 或 union_id. Defaults to None.
        timeout (Union[httpx.Timeout, None], optional): _description_. Defaults to None.

    Returns:
        Union[pd.DataFrame, List[List[CellTypes]]]: 读取的数据,\
            如果 return_raw 为 True 则返回 List[List[CellTypes]],否则返回 pd.DataFrame
    """

Write table

await lark.sheets.write(url, data=df, start_row=0, start_col=2)
await lark.sheets.append(url, data=df, start_row=0, start_col=2)
await lark.sheets.prepend(url, data=df, start_row=0, start_col=2)

Webhook

Send webhook message

await lark.webhook.post_error_card(
    self,
    msg: str,
    traceback: str,
    title,
    subtitle: Union[str, None] = None,
    timeout: Union[httpx.Timeout, None] = None,
)

await lark.webhook.post_success_card(
    self,
    msg: str,
    title,
    subtitle: Union[str, None] = None,
    timeout: Union[httpx.Timeout, None] = None,
)

Bitables

  1. Read
await lark.bitables.read(
    url: str,
    *,
    rows: Union[int, None] = None,
    field_names: Union[List[str], None] = None,
    return_raw: bool = False,
    timezone: Union[str, None] = "Asia/Shanghai",
    timeout: Union[httpx.Timeout, None] = None,
)
    """从多维表格中读取数据

    Args:
        url (str): 多维表格分享链接
        rows (Union[int, None], optional): 读取的行数. Defaults to None.
        field_names (Union[List[str], None], optional): 读取的列名. Defaults to None.
        raw (bool, optional): 是否返回原始数据. Defaults to False.
        timezone (Union[str, None], optional): 时区,仅在raw=False时有效. Defaults to "Asia/Shanghai".

    Returns:
        Union[dict, pd.DataFrame]: 当raw=True时返回原始数据,否则返回DataFrame\
            返回的 dataframe 的 index 为对应记录的 record id
    """
  1. Append
await lark.bitables.append(
    url: str,
    *,
    data: pd.DataFrame,
    timezone: Union[str, None] = "Asia/Shanghai",
    timeout: Union[httpx.Timeout, None] = None,
)
    """向多维表格中追加数据

    Args:
        url (str): 多维表格分享链接
        data (pd.DataFrame): 要追加的数据
        timezone (Union[str, None], optional): 时区. Defaults to "Asia/Shanghai".
        timeout (Union[httpx.Timeout, None], optional): Timeout. Defaults to None.

    Returns:
        List[RecordResponseData]: 追加的数据
    """
  1. Update
await lark.bitables.update(
    url: str,
    *,
    data: pd.DataFrame,
    timezone: Union[str, None] = "Asia/Shanghai",
    timeout: Union[httpx.Timeout, None] = None,
)
    """更新多维表格中的数据

    Args:
        url (str): 多维表格分享链接
        data (pd.DataFrame): 要更新的数据,index 为更新记录的 record id
        timezone (Union[str, None], optional): 时区. Defaults to "Asia/Shanghai".
        timeout (Union[httpx.Timeout, None], optional): Timeout. Defaults to None.

    Returns:
        List[RecordResponseData]: 更新的数据
    """
  1. Delete
await lark.bitables.delete(
    url: str, *, record_ids: List[str], timeout: Union[httpx.Timeout, None] = None
)
    """删除多维表格中的数据

    Args:
        url (str): 多维表格分享链接
        record_ids (List[str]): 要删除的记录ID
        timeout (Union[httpx.Timeout, None], optional): Timeout. Defaults to None.
    """

Document

  1. Read to Markdown
await lark.docs.read_markdown(
    url: str,
    *,
    assets_path: str = "./assets/",
    timeout: Union[httpx.Timeout, None] = None,
)
    """从文档中读取 markdown 内容

    Args:
        url (str): 文档分享链接。
        assets_path (str, optional): 保存图片的目录. Defaults to "./assets/".
        timeout (Union[httpx.Timeout, None], optional): 超时时间. Defaults to None.

    Returns:
        str: markdown 内容
    """

目前支持的文档元素:

  1. 文本,格式包括超链接、粗体、斜体、下划线、删除线,不支持字体颜色、背景颜色
  2. 标题,支持 1-9 级标题
  3. 有序列表
  4. 无序列表
  5. 代码块
  6. 引用
  7. 公式
  8. 待办事项,支持勾选和未勾选状态,不支持用户
  9. 高亮块
  10. 分割线
  11. 图片
  12. 表格
  13. 画板(导出为图片)

暂不支持的元素:

  1. 指定@用户
  2. 评论
  3. 文件
  4. 多维表格
  5. 群聊卡片
  6. 链接卡片(官方不支持)
  7. 分栏布局
  8. iframe
  9. 三方应用
  10. 任务
  11. OKR
  12. Jira问题
  13. 议程

Event Callback

from dotenv import find_dotenv, load_dotenv

from slark import AsyncLark, EventManager
from slark.types.event.common import EventType, LarkEvent

load_dotenv(find_dotenv())
lark = AsyncLark()
em = EventManager(lark)


@em.register(EventType.IM_MESSAGE_RECEIVE_V1)
async def test_event(lark: AsyncLark, ev: LarkEvent):
    message = ev.event.message
    if message.message_type == "image":
        await lark.messages.get_resource(
            message.message_id, message.content.image_key, "image", "uploads"
        )
    else:
        await lark.messages.send_markdown(
            ev.event.tenant_key,
            ev.event.sender_open_id,
            f"你发送了一条消息:{message.content}",
        )


em.listen(port=54467)

Card Callback

from typing import Optional
from typing_extensions import Literal

import slark.types.card as card


def build_docdb_save_card(
    stage: Literal["ask", "saving", "fail", "success"],
    input_value: Optional[str] = None,
    value: Optional[dict] = {},
) -> card.InteractiveCard:
    if stage == "ask" or stage == "fail":
        disabled = False
    else:
        disabled = True
    msg = {
        "ask": "是否保存至文档库",
        "saving": "保存中, 请稍后",
        "fail": "保存失败, 请重试",
        "success": "保存成功",
    }
    color = {
        "ask": card.CardTemplate.BLUE,
        "saving": card.CardTemplate.ORANGE,
        "fail": card.CardTemplate.RED,
        "success": card.CardTemplate.GREEN,
    }
    return card.InteractiveCard(
        config=card.CardConfig(update_multi=True),
        i18n_elements=card.I18nBody(
            zh_cn=[
                card.FormElement(
                    tag="form",
                    name="form_save_to_docdb",
                    elements=[
                        card.InputBoxElement(
                            name="input_save_to_docdb",
                            required=False,
                            disabled=disabled,
                            default_value=input_value,
                            placeholder=card.PlainText(tag="plain_text", content="请输入文档描述"),
                        ),
                        card.ColumnSetElement(
                            margin="10px 0px 10px 0px",
                            columns=[
                                card.ColumnElement(
                                    elements=[
                                        card.ButtonElement(
                                            type="primary",
                                            disabled=disabled,
                                            text=card.PlainText(tag="plain_text", content="保存"),
                                            form_action_type="submit",
                                            name="button_save_to_docdb",
                                            behaviors=[
                                                card.CallbackBehavior(
                                                    value={
                                                        "event": "save_to_docdb",
                                                        **value,
                                                    }
                                                )
                                            ],
                                        )
                                    ],
                                ),
                            ],
                        ),
                    ],
                )
            ],
        ),
        i18n_header=card.I18nHeader(
            zh_cn=card.I18nHeaderElement(
                title=card.PlainTextElement(content=msg[stage]),
                template=color[stage],
                ud_icon=card.IconElement(token=card.UDIconToken.FILE_LINK_DOCX_OUTLINED),
            ),
        ),
    )

server.py

import anyio
from dotenv import find_dotenv, load_dotenv
from fastapi import BackgroundTasks
from fastapi.responses import JSONResponse

from card_docdb import build_docdb_save_card
from slark import AsyncLark, EventManager
from slark.types.event import CallbackResponse, CallbackToast, EventType, LarkEvent

load_dotenv(find_dotenv())
lark = AsyncLark()
em = EventManager(lark)


@em.register(EventType.IM_MESSAGE_RECEIVE_V1)
async def test_event(lark: AsyncLark, ev: LarkEvent):
    value = {"file_key": ev.event.message.content, "file_type": "message"}
    card = build_docdb_save_card(stage="ask", value=value)
    return await lark.messages.reply_card(ev.event.message.message_id, card=card)


@em.register(EventType.CARD_ACTION_TRIGGER, run_in_thread=False)
async def card_action_trigger(lark: AsyncLark, ev: LarkEvent):
    value = ev.event.action.value
    from pprint import pprint
    pprint(value)
    if value["event"] != "save_to_docdb":
        return JSONResponse(
            content=CallbackResponse(
                toast=CallbackToast(type="info", content="未知事件"),
            ).model_dump(),
        )

    async def reply():
        input_value = ev.event.action.form_value.get("input_save_to_docdb")
        card = build_docdb_save_card(stage="saving", input_value=input_value, value=value)
        await lark.messages.edit_card(ev.event.context.open_message_id, card)
        await anyio.sleep(1)
        card = build_docdb_save_card(stage="success", input_value=input_value, value=value)
        await lark.messages.edit_card(ev.event.context.open_message_id, card)
        await anyio.sleep(1)
        card = build_docdb_save_card(stage="fail", input_value=input_value, value=value)
        await lark.messages.edit_card(ev.event.context.open_message_id, card)

    tasks = BackgroundTasks()
    tasks.add_task(reply)
    return JSONResponse(
        content=CallbackResponse(
            toast=CallbackToast(type="info", content="保存中"),
        ).model_dump(),
        background=tasks,
    )


em.listen(port=54467)

About

simple lark client

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages