diff --git a/.env b/.env index 850b974..c76c8cc 100644 --- a/.env +++ b/.env @@ -14,6 +14,11 @@ VERIFICATION_TOKEN=35LaoAPYTXRyBmjeaBvzRbRUsJSL1KBx BITABLE_TOKEN=XDmHbhbZcaY4gZsOvBJcLcHonKh TABLE_ID=tblwgGCDCE74Ugv6 +# category +OFFICE_XBOT_BITABLE_TOKEN=XDmHbhbZcaY4gZsOvBJcLcHonKh +OFFICE_XBOT_TABLE_ID=tbl2GcBvkvMtOBnl +OFFICE_XBOT_VIEW_ID=vewIAewVD2 + # REDIS BROKER_URL=redis://office-xbot-redis RESULT_BACKEND=redis://office-xbot-redis diff --git a/app/__init__.py b/app/__init__.py index b306877..a9ee04c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -5,6 +5,7 @@ import time import lark_oapi as lark from celery import Celery from celery import Task +from celery.schedules import crontab from flask import Flask @@ -101,11 +102,6 @@ def create_app() -> Flask: resp = handler.do(parse_req()) return parse_resp(resp) - @app.route("/test", methods=["GET"]) - def test(): - add.delay(1, 2) - return {"test": "ok"} - @app.route("/index", methods=["GET"]) def index(): return {"test": "ok"} @@ -122,5 +118,13 @@ def celery_init_app(app: Flask) -> Celery: celery_app = Celery(app.name, task_cls=FlaskTask) celery_app.config_from_object(app.config["CELERY"]) celery_app.set_default() + celery_app.conf.timezone = "Asia/Shanghai" + celery_app.conf.beat_schedule = { + "sync-office-xbot-info": { + "task": "app.task.sync_xbot_activity_info", + "schedule": crontab(hour=19, minute=57), + }, + } + app.extensions["celery"] = celery_app return celery_app diff --git a/app/__pycache__/task.cpython-310.pyc b/app/__pycache__/task.cpython-310.pyc index 8019e29..a9f963a 100644 Binary files a/app/__pycache__/task.cpython-310.pyc and b/app/__pycache__/task.cpython-310.pyc differ diff --git a/app/make_celery.py b/app/make_celery.py index 9f31f72..c3c84b0 100644 --- a/app/make_celery.py +++ b/app/make_celery.py @@ -2,10 +2,10 @@ from app import create_app flask_app = create_app() celery_app = flask_app.extensions["celery"] -celery_app.conf.beat_schedule = { - "add-every-30-seconds": { - "task": "app.task.sync_xbot_activity_info", - "schedule": 1800, - } -} -celery_app.conf.timezone = "UTC" +# celery_app.conf.beat_schedule = { +# "add-every-30-seconds": { +# "task": "app.task.sync_xbot_activity_info", +# "schedule": 1800, +# } +# } +# celery_app.conf.timezone = "UTC" diff --git a/app/scripts/__init__.py b/app/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/scripts/sync_office_info_to_feishu.py b/app/scripts/sync_office_info_to_feishu.py new file mode 100644 index 0000000..bcf58b4 --- /dev/null +++ b/app/scripts/sync_office_info_to_feishu.py @@ -0,0 +1,71 @@ +import os +import json + +import lark_oapi as lark + +from flask import Flask +from lark_oapi.adapter.flask import * +from lark_oapi.api.im.v1 import * +from lark_oapi.api.bitable.v1 import * +import lark_oapi as lark +from lark_oapi.api.drive.v1 import * +from lark_oapi.api.bitable.v1 import * + +from app.utils.yd_utils import get_activity_info, get_token, get_office_app_uuids +from app.utils.utils import get_filtered_record_by_uuid, update_record, add_record + +app_id = os.environ["APP_ID"] +app_secret = os.environ["APP_SECRET"] + +bitable_token = os.environ["BITABLE_TOKEN"] +table_id = os.environ["TABLE_ID"] + +xbot_username = os.environ["XBOT_USERNAME"] +xbot_password = os.environ["XBOT_PASSWORD"] + +bitable_token = "XDmHbhbZcaY4gZsOvBJcLcHonKh" +table_id = "tbl2GcBvkvMtOBnl" +view_id = "vewIAewVD2" + +client = lark.Client.builder().app_id(app_id).app_secret(app_secret).build() + + +token = get_token(xbot_username, xbot_password) + + +handle_list = [] + +# 同步应用数据 +# office_app_uuids = get_office_app_uuids() + +# print(f"office_activity_uuids count: {len(office_app_uuids)}") + + +# 同步指令数据 +office_activity_uuids = get_activity_info(token) +print(f"office_activity_uuids count: {len(office_activity_uuids)}") + +for item in office_activity_uuids: + robot_uuid = item.get("robotUuid") + package_view_name = item.get("packageViewName") + print("=====================================") + print(f"同步信息: {robot_uuid}, {package_view_name}") + record_id = get_filtered_record_by_uuid( + client, bitable_token, table_id, view_id, "UUID", robot_uuid + ) + if record_id: + print(f"{robot_uuid}已同步多维表格中") + update_record( + client, bitable_token, table_id, record_id, {"名称": package_view_name} + ) + else: + handle_list.append(robot_uuid) + print(f"{robot_uuid}: {package_view_name} 未同步多维表格中") + add_record( + client, + bitable_token, + table_id, + {"UUID": robot_uuid, "名称": package_view_name, "分类": "官方指令"}, + ) +print(f"新增的数量: {len(handle_list)}") +print("新增的uuid: ", handle_list) diff --git a/app/task.py b/app/task.py index d693e96..9435b0d 100644 --- a/app/task.py +++ b/app/task.py @@ -10,25 +10,6 @@ from app.utils.utils import * from app.utils.yd_utils import get_token, get_activity_info -@shared_task(ignore_result=False) -def add(a: int, b: int) -> int: - return a + b - - -@shared_task() -def block() -> None: - time.sleep(5) - - -@shared_task(bind=True, ignore_result=False) -def process(self: Task, total: int) -> object: - for i in range(total): - self.update_state(state="PROGRESS", meta={"current": i + 1, "total": total}) - time.sleep(1) - - return {"current": total, "total": total} - - @shared_task(include_result=False) def process_message_callback( app_id, app_secret, bitable_token, table_id, cache_dir, data @@ -93,11 +74,52 @@ def process_message_callback( @shared_task() def sync_xbot_activity_info(): + app_id = os.environ["APP_ID"] + app_secret = os.environ["APP_SECRET"] + + bitable_token = os.environ["OFFICE_XBOT_BITABLE_TOKEN"] + table_id = os.environ["OFFICE_XBOT_TABLE_ID"] + view_id = os.environ["OFFICE_XBOT_VIEW_ID"] + xbot_username = os.environ["XBOT_USERNAME"] xbot_password = os.environ["XBOT_PASSWORD"] + + client = lark.Client.builder().app_id(app_id).app_secret(app_secret).build() + token = get_token(xbot_username, xbot_password) - office_activity_uuids: list = get_activity_info(token) + + handle_list = [] + + # 同步应用数据 + # office_app_uuids = get_office_app_uuids() + + # print(f"office_activity_uuids count: {len(office_app_uuids)}") + + # 同步指令数据 + office_activity_uuids = get_activity_info(token) + print(f"office_activity_uuids count: {len(office_activity_uuids)}") + for item in office_activity_uuids: - uuid = item.get("uuid") - package_viewName = item.get("packageViewName") - print(uuid, package_viewName) + robot_uuid = item.get("robotUuid") + package_view_name = item.get("packageViewName") + print("=====================================") + print(f"同步信息: {robot_uuid}, {package_view_name}") + record_id = get_filtered_record_by_uuid( + client, bitable_token, table_id, view_id, "UUID", robot_uuid + ) + if record_id: + print(f"{robot_uuid}已同步多维表格中") + update_record( + client, bitable_token, table_id, record_id, {"名称": package_view_name} + ) + else: + handle_list.append(robot_uuid) + print(f"{robot_uuid}: {package_view_name} 未同步多维表格中") + add_record( + client, + bitable_token, + table_id, + {"UUID": robot_uuid, "名称": package_view_name, "分类": "官方指令"}, + ) + print(f"新增的数量: {len(handle_list)}") + print("新增的uuid: ", handle_list) diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/utils/utils.py b/app/utils/utils.py index b837136..cc8a974 100644 --- a/app/utils/utils.py +++ b/app/utils/utils.py @@ -11,6 +11,7 @@ from lark_oapi.api.im.v1 import * from lark_oapi.api.bitable.v1 import * import lark_oapi as lark from lark_oapi.api.drive.v1 import * +from lark_oapi.api.bitable.v1 import * def update_record(client, bitable_token, table_id, record_id, data): @@ -197,3 +198,86 @@ def send_message(client, receive_id, content): # 处理业务结果 lark.logger.info(lark.JSON.marshal(response.data, indent=4)) + + +def get_filtered_record_by_uuid(client, bitable_token, table_id, view_id, field_name, uuid): + request: SearchAppTableRecordRequest = ( + SearchAppTableRecordRequest.builder() + .app_token(bitable_token) + .table_id(table_id) + .user_id_type("user_id") + .page_size(20) + .request_body( + SearchAppTableRecordRequestBody.builder() + .view_id(view_id) + .field_names([field_name]) + .sort([Sort.builder().field_name(field_name).desc(True).build()]) + .filter( + FilterInfo.builder() + .conjunction("and") + .conditions( + [ + Condition.builder() + .field_name(field_name) + .operator("is") + .value([uuid]) + .build() + ] + ) + .build() + ) + .automatic_fields(False) + .build() + ) + .build() + ) + + # 发起请求 + option = ( + lark.RequestOption.builder() + .user_access_token("u-caH0NMIK50kUMfBMUqgMtr1l6blBghh1Mi0015.82GbP") + .build() + ) + response: SearchAppTableRecordResponse = client.bitable.v1.app_table_record.search( + request, option + ) + + # 处理失败返回 + if not response.success(): + # lark.logger.error( + # f"client.bitable.v1.app_table_record.search failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}" + # ) + return + + # 处理业务结果 + lark.logger.info(lark.JSON.marshal(response.data)) + + if response.data.total == 1: + return response.data.items[0].record_id + + if response.data.total == 2: + print("找到了多条记录") + +if __name__ == "__main__": + + app_id = os.environ["APP_ID"] + app_secret = os.environ["APP_SECRET"] + + bitable_token = os.environ["BITABLE_TOKEN"] + table_id = os.environ["TABLE_ID"] + + bitable_token = "XDmHbhbZcaY4gZsOvBJcLcHonKh" + table_id = "tbl2GcBvkvMtOBnl" + + uuid = "4e478138-c8d6-426c-9761-3d1f4fd3cb05" + view_id = "vewIAewVD2" + + # 创建client + client = ( + lark.Client.builder() + .app_id(app_id) + .app_secret(app_secret) + .log_level(lark.LogLevel.DEBUG) + .build() + ) + get_filtered_record_by_uuid(client, bitable_token, table_id, view_id, uuid) diff --git a/app/utils/yd_utils.py b/app/utils/yd_utils.py index 7c34fb1..0feddc5 100644 --- a/app/utils/yd_utils.py +++ b/app/utils/yd_utils.py @@ -90,6 +90,36 @@ def get_activity_detail(token, package_name, package_version): return response.json().get("data") +def get_office_app_uuids() -> list: + """ + 获取官方应用的 uuid, + :return: list, 官方应用 uuid + """ + uuids = [] + tmp_list = [] + page = 1 + url = "https://api.winrobot360.com/api/noauth/v1/group/official/queryOfficialRobotList?page=%s&size=200" + page_response = requests.request("GET", url % page) + + page_info = page_response.json().get("page").get("pages") + + for item in range(1, page_info + 1): + response = requests.request("GET", url % item) + for item in response.json().get("data"): + # if item.get("robotGradeName") == "简单应用": + # continue + + if item.get("uiTags") == "极速": + continue + + print(item.get("name"), item.get("robotGradeName")) + + uuids.append(item.get("uuid")) + name = item.get("name") + uuid = item.get("uuid") + return uuids + + if __name__ == "__main__": dotenv.load_dotenv() xbot_username = os.environ["XBOT_USERNAME"] @@ -97,4 +127,4 @@ if __name__ == "__main__": token = get_token(xbot_username, xbot_password) office_activity_uuids: list = get_activity_info(token) for item in office_activity_uuids: - print(item) + print(item.get("packageViewName"), item.get("uuid")) diff --git a/docker-compose.yaml b/docker-compose.yaml index c23768a..9f808bf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -17,6 +17,10 @@ services: - RESULT_BACKEND=${RESULT_BACKEND} - XBOT_USERNAME=${XBOT_USERNAME} - XBOT_PASSWORD=${XBOT_PASSWORD} + - OFFICE_XBOT_BITABLE_TOKEN=${OFFICE_XBOT_BITABLE_TOKEN} + - OFFICE_XBOT_TABLE_ID=${OFFICE_XBOT_TABLE_ID} + - OFFICE_XBOT_VIEW_ID=${OFFICE_XBOT_VIEW_ID} + - TZ=Asia/Shanghai healthcheck: test: ["CMD", "curl", "-f", "http://office-xbot-app/index"] interval: 30s @@ -40,6 +44,10 @@ services: - RESULT_BACKEND=${RESULT_BACKEND} - XBOT_USERNAME=${XBOT_USERNAME} - XBOT_PASSWORD=${XBOT_PASSWORD} + - OFFICE_XBOT_BITABLE_TOKEN=${OFFICE_XBOT_BITABLE_TOKEN} + - OFFICE_XBOT_TABLE_ID=${OFFICE_XBOT_TABLE_ID} + - OFFICE_XBOT_VIEW_ID=${OFFICE_XBOT_VIEW_ID} + - TZ=Asia/Shanghai healthcheck: test: ["CMD", "celery", "inspect", "ping"] interval: 10s @@ -59,6 +67,9 @@ services: timeout: 10s retries: 3 + environment: + - TZ=Asia/Shanghai + office-xbot-flower: container_name: office-xbot-flower image: "${APP_IMAGE}" @@ -70,6 +81,9 @@ services: condition: service_healthy restart: true + environment: + - TZ=Asia/Shanghai + networks: office-xbot-network: diff --git a/poetry.lock b/poetry.lock index 86979ca..450f860 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,6 +14,28 @@ files = [ [package.dependencies] vine = ">=5.0.0,<6.0.0" +[[package]] +name = "anyio" +version = "4.2.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "async-timeout" version = "4.0.3" @@ -286,6 +308,20 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "flask" version = "3.0.0" @@ -326,6 +362,62 @@ prometheus-client = ">=0.8.0" pytz = "*" tornado = ">=5.0.0,<7.0.0" +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, + {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.24.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "humanize" version = "4.9.0" @@ -413,16 +505,17 @@ zookeeper = ["kazoo (>=2.8.0)"] [[package]] name = "lark-oapi" -version = "1.0.28" +version = "1.2.0" description = "Lark OpenAPI SDK for Python" optional = false python-versions = ">=3.7" files = [ - {file = "lark-oapi-1.0.28.tar.gz", hash = "sha256:18975097872a310e71a6abe48116b27741bc190244fcb0bfccd239303b5a4b19"}, - {file = "lark_oapi-1.0.28-py3-none-any.whl", hash = "sha256:84ddf963dc968dd416f9577e04e27cc297989381733bdcb7e632a95ae74b4f6e"}, + {file = "lark-oapi-1.2.0.tar.gz", hash = "sha256:e6984a1213ce5ceb464023bb77ea7d675638f34c7b8c53050a4b0a121da0726c"}, + {file = "lark_oapi-1.2.0-py3-none-any.whl", hash = "sha256:95c9b48fe4629af2763421d62248301c787829b8d9435b9e09cc4bb4272d587f"}, ] [package.dependencies] +httpx = "*" pycryptodome = "*" requests = "*" requests-toolbelt = "*" @@ -671,6 +764,17 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + [[package]] name = "tornado" version = "6.4" @@ -691,6 +795,17 @@ files = [ {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, ] +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + [[package]] name = "tzdata" version = "2023.4" @@ -760,4 +875,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "2e056586ede929b134afce8a0b28ce5189b1ea298f1682472fdfde6e3e88cb79" +content-hash = "274fcf51d4d82c6a4f00d9dea2f2da7a3716366414e9b54e309a1811a0d7304c" diff --git a/pyproject.toml b/pyproject.toml index e88cabf..a112966 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [{include = "office_xbot_feishu"}] python = "^3.10" flask = "^3.0.0" python-dotenv = "^1.0.0" -lark-oapi = "^1.0.28" +lark-oapi = "1.2.0" celery = "^5.3.6" redis = "^5.0.1" requests = "^2.31.0"