521 lines
17 KiB
Python
521 lines
17 KiB
Python
# coding: utf-8
|
||
|
||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||
|
||
from enum import Enum
|
||
from typing import Any, List
|
||
|
||
import attr
|
||
from six import string_types
|
||
|
||
from utils.feishu.dt_help import to_json_decorator
|
||
from utils.feishu.exception import LarkInvalidArguments
|
||
|
||
|
||
def join_range(sheet_id, range):
|
||
if not range or not isinstance(range, string_types):
|
||
raise LarkInvalidArguments(msg='empty range')
|
||
for i in [sheet_id, sheet_id + '!']:
|
||
if range.startswith(i):
|
||
range = range[len(i):]
|
||
|
||
return sheet_id + '!' + range
|
||
|
||
|
||
# 文档类型,支持:"doc", "sheet", "slide", "bitable", "mindnote", "file", "wiki"
|
||
class DriveFileType(Enum):
|
||
doc = 'doc' # doc
|
||
sheet = 'sheet' # sheet
|
||
bitable = 'bitable' # bitable
|
||
folder = 'folder' # folder
|
||
slide = 'slide'
|
||
mindnote = 'mindnote'
|
||
file = 'file'
|
||
wiki = 'wiki'
|
||
|
||
|
||
class DriveDeleteFlag(Enum):
|
||
# 删除标志,0表示正常访问未删除,1表示在回收站,2表示已经彻底删除
|
||
normal = 0
|
||
in_recycle = 1
|
||
complete_deletion = 2
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveSheetCellURL(object):
|
||
"""有文本的url
|
||
"""
|
||
title = attr.ib(type=str, default='')
|
||
url = attr.ib(type=str, default='')
|
||
|
||
def as_sheet_dict(self):
|
||
return {'text': self.title, 'link': self.url, 'type': 'url'}
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveSheetCellAt(object):
|
||
"""@人名
|
||
|
||
个人邮箱,只支持同租户@,notify 为是否发送 Lark 消息
|
||
"""
|
||
email = attr.ib(type=str, default='')
|
||
notify = attr.ib(type=bool, default=False)
|
||
|
||
def as_sheet_dict(self):
|
||
return {'type': 'mention', 'text': self.email, 'notify': self.notify}
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveFileToken(object):
|
||
"""表示一个文件, token + type
|
||
"""
|
||
token = attr.ib(type=str, default='')
|
||
type = attr.ib(type=DriveFileType, default=None)
|
||
name = attr.ib(type=str, default='')
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveFolderMeta(object):
|
||
"""文件夹元信息
|
||
"""
|
||
id = attr.ib(type=str, default='')
|
||
name = attr.ib(type=str, default='', metadata={'json': 'name'})
|
||
token = attr.ib(type=str, default='')
|
||
create_uid = attr.ib(type=str, default='', metadata={'json': 'createUid'})
|
||
edit_uid = attr.ib(type=str, default='', metadata={'json': 'editUid'})
|
||
parent_id = attr.ib(type=str, default='', metadata={'json': 'parentId'})
|
||
own_uid = attr.ib(type=str, default='', metadata={'json': 'ownUid'})
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveFileMeta(object):
|
||
"""文件元信息
|
||
"""
|
||
name = attr.ib(type=str, default='', metadata={'json': 'title'})
|
||
token = attr.ib(type=str, default='', metadata={'json': 'docs_token'})
|
||
type = attr.ib(type=DriveFileType, default=None, metadata={'json': 'docs_type'})
|
||
owner_open_id = attr.ib(type=str, default='', metadata={'json': 'owner_id'})
|
||
create_time = attr.ib(type=int, default=0)
|
||
latest_modify_open_id = attr.ib(type=str, default='', metadata={'json': 'latest_modify_user'})
|
||
latest_modify_time = attr.ib(type=str, default='')
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveCreateFile(object):
|
||
"""创建的文件对象
|
||
"""
|
||
revision = attr.ib(type=int, default=0)
|
||
token = attr.ib(type=str, default='')
|
||
url = attr.ib(type=str, default='')
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveDeleteFile(object):
|
||
"""删除的文件对象
|
||
"""
|
||
id = attr.ib(type=str, default='')
|
||
result = attr.ib(type=bool, default=False)
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveCopyFile(object):
|
||
"""复制的文件对象
|
||
"""
|
||
folder_token = attr.ib(type=str, default='', metadata={'json': 'folderToken'})
|
||
revision = attr.ib(type=int, default=0)
|
||
token = attr.ib(type=str, default='')
|
||
url = attr.ib(type=str, default='')
|
||
type = attr.ib(type=DriveFileType, default=None)
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveDocFileMeta(object):
|
||
create_date = attr.ib(type=str, default='')
|
||
create_time = attr.ib(type=int, default=0)
|
||
create_uid = attr.ib(type=str, default='')
|
||
create_user_name = attr.ib(type=str, default='')
|
||
delete_flag = attr.ib(type=DriveDeleteFlag, default=DriveDeleteFlag.normal)
|
||
edit_time = attr.ib(type=int, default=0)
|
||
edit_user_name = attr.ib(type=str, default='')
|
||
is_external = attr.ib(type=bool, default=False)
|
||
is_pined = attr.ib(type=bool, default=False)
|
||
is_stared = attr.ib(type=bool, default=False)
|
||
type = attr.ib(type=DriveFileType, default=None, metadata={'json': 'obj_type'}) # doc
|
||
owner_uid = attr.ib(type=str, default='', metadata={'json': 'owner_id'}) # 这里不是 open_id,接口不标准
|
||
owner_user_name = attr.ib(type=str, default='')
|
||
server_time = attr.ib(type=int, default=0)
|
||
tenant_id = attr.ib(type=str, default='')
|
||
title = attr.ib(type=str, default='')
|
||
url = attr.ib(type=str, default='')
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveComment(object):
|
||
"""回复的对象
|
||
"""
|
||
comment_id = attr.ib(type=str, default='')
|
||
create_timestamp = attr.ib(type=int, default=0)
|
||
reply_id = attr.ib(type=str, default='')
|
||
update_timestamp = attr.ib(type=int, default=0)
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveSubSheetMeta(object):
|
||
id = attr.ib(type=str, default='', metadata={'json': 'sheetId'})
|
||
title = attr.ib(type=str, default='')
|
||
index = attr.ib(type=int, default=0)
|
||
row_count = attr.ib(type=int, default=0, metadata={'json': 'rowCount'})
|
||
column_count = attr.ib(type=int, default=0, metadata={'json': 'columnCount'})
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveSheetMeta(object):
|
||
title = attr.ib(type=str, default='')
|
||
owner_uid = attr.ib(type=int, default=0, metadata={'json': 'ownerUser'})
|
||
sheet_count = attr.ib(type=int, default=0, metadata={'json': 'sheetCount'})
|
||
token = attr.ib(type=str, default='', metadata={'json': 'spreadsheetToken'})
|
||
sheets = attr.ib(type=List[DriveSubSheetMeta], default=None) # type: List[DriveSubSheetMeta]
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveInsertSheet(object):
|
||
sheet_token = attr.ib(type=str, default='')
|
||
sheet_id = attr.ib(type=str, default='')
|
||
revision = attr.ib(type=int, default=0)
|
||
updated_range = attr.ib(type=str, default='')
|
||
rows = attr.ib(type=int, default=0)
|
||
columns = attr.ib(type=int, default=0)
|
||
cells = attr.ib(type=int, default=0)
|
||
|
||
|
||
class DriveSheetStyleTextDecoration(Enum):
|
||
normal = 0
|
||
underline = 1 # 下划线
|
||
line_through = 2 # 删除线
|
||
underline_and_line_through = 3 # 下划线+删除线
|
||
|
||
|
||
class DriveSheetStyleNumber(Enum):
|
||
normal = '' # 常规
|
||
plain_text = '@' # 纯文本
|
||
number = '0' # 数字:1024
|
||
number_thousandths = '#,##0' # 数字(千分位):1,024
|
||
number_thousandths_decimal = '#,##0.00' # 数字(千分位 小数点):1024.56
|
||
percent = '0%' # 百分比:10%
|
||
percent_decimal = '0.00%' # 百分比(小数点):10.24%
|
||
scientific_notation = '0.00E+00' # 科学计数:1.02E+03
|
||
rmb = '¥#,##0' # 人民币:¥1,024
|
||
rmb_decimal = '¥#,##0.00' # 人民币(小数点):¥1,024.56
|
||
usd = '$#,##0' # 美元:$1,024
|
||
usd_decimal = '$#,##0.00' # 美元(小数点):$1,024.56
|
||
date_slash = 'yyyy/MM/dd' # 日期:2017/08/10
|
||
date_hor = 'yyyy-MM-dd' # 日期:2017-08-10
|
||
time = 'HH:mm:ss' # 时间:23:24:25
|
||
datetime = 'yyyy/MM/dd HH:mm:ss' # 日期时间:2017/08/10 23:24:25
|
||
|
||
|
||
class DriveSheetStyleHorizontalAlign(Enum):
|
||
left = 0 # 左
|
||
center = 1
|
||
right = 2 # 右
|
||
|
||
|
||
class DriveSheetStyleVerticalAlign(Enum):
|
||
up = 0 # 上
|
||
center = 1
|
||
down = 2 # 下
|
||
|
||
|
||
class DriveSheetStyleBorderType(Enum):
|
||
full_border = 'FULL_BORDER'
|
||
outer_border = 'OUTER_BORDER'
|
||
inner_border = 'INNER_BORDER'
|
||
no_border = 'NO_BORDER'
|
||
left_border = 'LEFT_BORDER'
|
||
right_border = 'RIGHT_BORDER'
|
||
top_border = 'TOP_BORDER'
|
||
bottom_border = 'BOTTOM_BORDER'
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveSheetStyleFont(object):
|
||
bold = attr.ib(type=bool, default=False) # 是否粗体
|
||
italic = attr.ib(type=bool, default=False) # 是否斜体
|
||
font_size = attr.ib(type=str, default=None, metadata={'json': 'fontSize'}) # 10pt/1.5:字号大小为9~36 行距固定为1.5
|
||
clean = attr.ib(type=bool, default=False) # 清除font格式
|
||
|
||
def as_sheet_style(self):
|
||
d = {
|
||
'bold': self.bold,
|
||
'italic': self.italic,
|
||
'clean': self.clean,
|
||
}
|
||
if self.font_size is not None:
|
||
d['fontSize'] = self.font_size
|
||
return d
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveSheetStyle(object):
|
||
font = attr.ib(type=DriveSheetStyleFont, default=None) # 字体
|
||
text_decoration = attr.ib(type=DriveSheetStyleTextDecoration, default=DriveSheetStyleTextDecoration.normal,
|
||
metadata={'json': 'textDecoration'}) # 文本装饰:0 默认,1 下划线,2 删除线,3 下划线和删除线
|
||
formatter = attr.ib(type=DriveSheetStyleNumber, default=DriveSheetStyleNumber.normal) # 数字格式
|
||
horizontal_align = attr.ib(type=DriveSheetStyleHorizontalAlign, default=DriveSheetStyleHorizontalAlign.left,
|
||
metadata={'json': 'hAlign'}) # 水平对齐:0 左对齐,1 中对齐,2 右对齐
|
||
vertical_align = attr.ib(type=DriveSheetStyleVerticalAlign, default=DriveSheetStyleVerticalAlign.up,
|
||
metadata={'json': 'vAlign'}) # 垂直对齐:0 上对齐,1 中对齐, 2 下对齐
|
||
fore_color = attr.ib(type=str, default='', metadata={'json': 'foreColor'}) # 字体颜色
|
||
back_color = attr.ib(type=str, default='', metadata={'json': 'backColor'}) # 背景颜色
|
||
border_type = attr.ib(type=DriveSheetStyleBorderType, default=None, metadata={'json': 'borderType'}) # 边框颜色
|
||
border_color = attr.ib(type=str, default='', metadata={'json': 'borderColor'}) # 边框颜色
|
||
clean = attr.ib(type=bool, default=False) # 清除格式
|
||
|
||
def as_sheet_style(self):
|
||
d = {
|
||
'textDecoration': self.text_decoration.value,
|
||
'formatter': self.formatter.value,
|
||
'hAlign': self.horizontal_align.value,
|
||
'vAlign': self.vertical_align.value,
|
||
'foreColor': self.fore_color,
|
||
'backColor': self.back_color,
|
||
'borderColor': self.border_color,
|
||
'clean': self.clean,
|
||
}
|
||
if self.border_type is not None:
|
||
d['borderType'] = self.border_type.value
|
||
if self.font is not None:
|
||
d['font'] = self.font.as_sheet_style()
|
||
return d
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class BatchSetDriveSheetStyleRequest(object):
|
||
ranges = attr.ib(type=List[str], default=None) # type: List[str]
|
||
style = attr.ib(type=DriveSheetStyle, default=None)
|
||
raw_style = attr.ib(type=dict, default=None)
|
||
|
||
def as_dict(self, sheet_id):
|
||
d = {
|
||
'ranges': [join_range(sheet_id, i) for i in self.ranges],
|
||
}
|
||
if self.raw_style is not None:
|
||
d['style'] = self.raw_style
|
||
return d
|
||
|
||
d['style'] = self.style.as_sheet_style()
|
||
return d
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class LockDriveSheetRequest(object):
|
||
sheet_id = attr.ib(type=str)
|
||
start_index = attr.ib(type=int)
|
||
end_index = attr.ib(type=int)
|
||
editor_uids = attr.ib(type=List[int], default=None) # type: List[int]
|
||
is_rows = attr.ib(type=bool, default=True)
|
||
lock_info = attr.ib(type=str, default=None)
|
||
|
||
def as_dict(self):
|
||
d = {
|
||
"dimension": {
|
||
"sheetId": self.sheet_id,
|
||
"majorDimension": 'ROWS' if self.is_rows else "COLUMNS",
|
||
"startIndex": self.start_index,
|
||
"endIndex": self.end_index
|
||
},
|
||
}
|
||
if self.editor_uids is not None:
|
||
d['editors'] = self.editor_uids
|
||
if self.lock_info is not None:
|
||
d['lockInfo'] = self.lock_info
|
||
|
||
return d
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class ReadDriveSheetRequest(object):
|
||
sheet_id = attr.ib(type=str)
|
||
range = attr.ib(type=str)
|
||
|
||
def as_str(self):
|
||
return join_range(self.sheet_id, self.range)
|
||
|
||
|
||
class DriveSheetMergeType(Enum):
|
||
all = 'MERGE_ALL' # 将所选区域直接合并
|
||
rows = 'MERGE_ROWS' # 将所选区域按行合并
|
||
columns = 'MERGE_COLUMNS' # 将所选区域按列合并响应
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class WriteDriveSheetRequest(object):
|
||
sheet_id = attr.ib(type=str)
|
||
range = attr.ib(type=str)
|
||
values = attr.ib(type=List[List[Any]]) # type: List[List[Any]]
|
||
|
||
def as_dict(self):
|
||
return {
|
||
'range': join_range(self.sheet_id, self.range),
|
||
'values': [[i.as_sheet_dict() if hasattr(i, 'as_sheet_dict') else i for i in value]
|
||
for value in self.values]
|
||
}
|
||
|
||
|
||
class DriveFilePermission(Enum):
|
||
view = 'view'
|
||
edit = 'edit'
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveFileUser(object):
|
||
email = attr.ib(type=str, default=None) # 邮箱
|
||
open_id = attr.ib(type=str, default=None) # 人的 open_id
|
||
chat_id = attr.ib(type=str, default=None) # 群聊的 chat_id
|
||
employee_id = attr.ib(type=str, default=None) # lark_id
|
||
|
||
def as_dict(self):
|
||
d = {}
|
||
if self.email is not None:
|
||
d['member_id'] = self.email
|
||
d['member_type'] = 'email'
|
||
elif self.open_id is not None:
|
||
d['member_type'] = 'openid'
|
||
d['member_id'] = self.open_id
|
||
elif self.chat_id is not None:
|
||
d['member_type'] = 'openchat'
|
||
d['member_id'] = self.chat_id
|
||
elif self.employee_id is not None:
|
||
d['member_type'] = 'userid'
|
||
d['member_id'] = self.employee_id
|
||
else:
|
||
raise LarkInvalidArguments(msg='email / open_id / chat_id / uid 必须有一个')
|
||
|
||
return d
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class DriveFileUserPermission(DriveFileUser):
|
||
permission = attr.ib(type=DriveFilePermission, default=DriveFilePermission.view)
|
||
|
||
def as_dict(self):
|
||
d = super(DriveFileUserPermission, self).as_dict()
|
||
d['perm'] = self.permission.value
|
||
return d
|
||
|
||
|
||
def unmarshal_drive_user_permission(members,
|
||
email_type='email',
|
||
email_key='member_id',
|
||
open_id_type='openid',
|
||
open_id_key='member_id',
|
||
chat_id_type='openchat',
|
||
chat_id_key='member_id',
|
||
employee_id_type='userid',
|
||
employee_id_key='member_id',
|
||
is_unmarshal_perm=False):
|
||
d = []
|
||
for i in members:
|
||
member_type = i.get('member_type', '')
|
||
|
||
if is_unmarshal_perm:
|
||
v = DriveFileUserPermission(permission=i.get('perm', ''))
|
||
else:
|
||
v = DriveFileUser()
|
||
|
||
if member_type == email_type:
|
||
v.email = i.get(email_key) or ''
|
||
elif member_type == open_id_type:
|
||
v.open_id = i.get(open_id_key) or ''
|
||
elif member_type == chat_id_type:
|
||
v.chat_id = i.get(chat_id_key) or ''
|
||
elif member_type == employee_id_type:
|
||
v.employee_id = i.get(employee_id_key) or ''
|
||
d.append(v)
|
||
return d
|
||
|
||
|
||
class DriveFilePublicLinkSharePermission(Enum):
|
||
tenant_readable = 'tenant_readable' # 组织内获得链接的人可阅读
|
||
tenant_editable = 'tenant_editable' # 组织内获得链接的人可编辑
|
||
anyone_readable = 'anyone_readable' # 获得链接的任何人可阅读
|
||
anyone_editable = 'anyone_editable' # 获得链接的任何人可编辑
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class BatchUpdateDriveSheetRequestAdd(object):
|
||
title = attr.ib(type=str)
|
||
index = attr.ib(type=int, default=None)
|
||
|
||
def as_dict(self):
|
||
d = {'title': self.title}
|
||
if self.index is not None:
|
||
d['index'] = self.index
|
||
return {
|
||
'addSheet': {
|
||
'properties': d
|
||
}
|
||
}
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class BatchUpdateDriveSheetRequestCopy(object):
|
||
sheet_id = attr.ib(type=str)
|
||
dst_title = attr.ib(type=str)
|
||
|
||
def as_dict(self):
|
||
return {
|
||
'copySheet': {
|
||
'source': {
|
||
'sheetId': self.sheet_id
|
||
},
|
||
'destination': {
|
||
'title': self.dst_title
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class BatchUpdateDriveSheetRequestDelete(object):
|
||
sheet_id = attr.ib(type=str)
|
||
|
||
def as_dict(self):
|
||
return {
|
||
'deleteSheet': {
|
||
'sheetId': self.sheet_id
|
||
}
|
||
}
|
||
|
||
|
||
@to_json_decorator
|
||
@attr.s
|
||
class UpdateDriveSheetResponse(object):
|
||
sheet_id = attr.ib(type=str, default=None, metadata={'json': 'sheetId'})
|
||
title = attr.ib(type=str, default=None)
|
||
index = attr.ib(type=int, default=None)
|