lansenger-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lansenger_cli/__init__.py +0 -0
- lansenger_cli/commands/__init__.py +0 -0
- lansenger_cli/commands/calendar.py +154 -0
- lansenger_cli/commands/callback.py +64 -0
- lansenger_cli/commands/config.py +54 -0
- lansenger_cli/commands/department.py +59 -0
- lansenger_cli/commands/group.py +117 -0
- lansenger_cli/commands/health.py +17 -0
- lansenger_cli/commands/media.py +42 -0
- lansenger_cli/commands/message.py +162 -0
- lansenger_cli/commands/oauth.py +73 -0
- lansenger_cli/commands/staff.py +102 -0
- lansenger_cli/commands/streaming.py +27 -0
- lansenger_cli/commands/todo.py +220 -0
- lansenger_cli/main.py +45 -0
- lansenger_cli/utils.py +79 -0
- lansenger_cli-0.1.0.dist-info/METADATA +22 -0
- lansenger_cli-0.1.0.dist-info/RECORD +22 -0
- lansenger_cli-0.1.0.dist-info/WHEEL +5 -0
- lansenger_cli-0.1.0.dist-info/entry_points.txt +2 -0
- lansenger_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- lansenger_cli-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import json
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from lansenger_cli.utils import get_client, output_result, output_list
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Calendar and schedule operations")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command("primary")
|
|
11
|
+
def fetch_primary_calendar(
|
|
12
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
13
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
14
|
+
):
|
|
15
|
+
client = get_client()
|
|
16
|
+
result = client.fetch_primary_calendar(user_token=user_token, user_id=user_id)
|
|
17
|
+
output_result(result, fields=[
|
|
18
|
+
"calendar_id", "summary", "description", "permissions", "role",
|
|
19
|
+
], title="Primary Calendar")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command("create-schedule")
|
|
23
|
+
def create_schedule(
|
|
24
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
25
|
+
summary: str = typer.Argument(help="Schedule summary/title"),
|
|
26
|
+
start_time: str = typer.Argument(help="Start time as JSON: '{\"dateTime\":\"2026-01-01T09:00:00\",\"timeZone\":\"Asia/Shanghai\"}'"),
|
|
27
|
+
end_time: str = typer.Argument(help="End time as JSON"),
|
|
28
|
+
attendees: str = typer.Argument(help="Attendees as JSON list: '[{\"staffId\":\"xxx\"}]'"),
|
|
29
|
+
description: str = typer.Option("", "--desc", "-d", help="Schedule description"),
|
|
30
|
+
all_day: str = typer.Option("no", "--all-day", help="yes or no"),
|
|
31
|
+
repeat_type: str = typer.Option("no", "--repeat", help="Repeat type: no, daily, weekly, monthly, yearly"),
|
|
32
|
+
reminder_type: str = typer.Option("yes", "--reminder", help="Reminder type: yes or no"),
|
|
33
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
34
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
35
|
+
):
|
|
36
|
+
client = get_client()
|
|
37
|
+
start_time_dict = json.loads(start_time)
|
|
38
|
+
end_time_dict = json.loads(end_time)
|
|
39
|
+
attendees_list = json.loads(attendees)
|
|
40
|
+
result = client.create_schedule(
|
|
41
|
+
calendar_id=calendar_id, summary=summary,
|
|
42
|
+
start_time=start_time_dict, end_time=end_time_dict,
|
|
43
|
+
attendees=attendees_list, description=description,
|
|
44
|
+
all_day=all_day, repeat_type=repeat_type,
|
|
45
|
+
reminder_type=reminder_type,
|
|
46
|
+
user_token=user_token, user_id=user_id,
|
|
47
|
+
)
|
|
48
|
+
output_result(result, fields=["schedule_id"], title="Create Schedule Result")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("fetch-schedule")
|
|
52
|
+
def fetch_schedule(
|
|
53
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
54
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
55
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
56
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
57
|
+
):
|
|
58
|
+
client = get_client()
|
|
59
|
+
result = client.fetch_schedule(
|
|
60
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
61
|
+
user_token=user_token, user_id=user_id,
|
|
62
|
+
)
|
|
63
|
+
output_result(result, fields=[
|
|
64
|
+
"schedule_id", "summary", "description", "all_day",
|
|
65
|
+
"start_time", "end_time", "creator", "rsvp_status",
|
|
66
|
+
], title="Schedule Info")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command("delete-schedule")
|
|
70
|
+
def delete_schedule(
|
|
71
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
72
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
73
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
74
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
75
|
+
):
|
|
76
|
+
client = get_client()
|
|
77
|
+
result = client.delete_schedule(
|
|
78
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
79
|
+
user_token=user_token, user_id=user_id,
|
|
80
|
+
)
|
|
81
|
+
output_result(result, fields=["schedule_id"], title="Delete Schedule Result")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command("list-schedules")
|
|
85
|
+
def fetch_schedule_list(
|
|
86
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
87
|
+
start_time: int = typer.Argument(help="Start time (unix timestamp)"),
|
|
88
|
+
end_time: int = typer.Argument(help="End time (unix timestamp)"),
|
|
89
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
90
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
91
|
+
):
|
|
92
|
+
client = get_client()
|
|
93
|
+
result = client.fetch_schedule_list(
|
|
94
|
+
calendar_id=calendar_id, start_time=start_time, end_time=end_time,
|
|
95
|
+
user_token=user_token, user_id=user_id,
|
|
96
|
+
)
|
|
97
|
+
if result.success and result.schedule_list:
|
|
98
|
+
output_list(result.schedule_list, columns=["Schedule ID", "Summary"], row_mapper=lambda s: [
|
|
99
|
+
getattr(s, "schedule_id", ""), getattr(s, "summary", ""),
|
|
100
|
+
])
|
|
101
|
+
else:
|
|
102
|
+
output_result(result, title="Schedule List")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.command("attendees")
|
|
106
|
+
def fetch_schedule_attendees(
|
|
107
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
108
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
109
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
110
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
111
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
112
|
+
page_size: int = typer.Option(500, "--size", "-s", help="Page size"),
|
|
113
|
+
):
|
|
114
|
+
client = get_client()
|
|
115
|
+
result = client.fetch_schedule_attendees(
|
|
116
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
117
|
+
user_token=user_token, user_id=user_id,
|
|
118
|
+
page=page, page_size=page_size,
|
|
119
|
+
)
|
|
120
|
+
output_result(result, fields=["total"], title="Schedule Attendees")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command("add-attendees")
|
|
124
|
+
def add_schedule_attendees(
|
|
125
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
126
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
127
|
+
attendees: str = typer.Argument(help="Attendee staff IDs as JSON list: '[\"id1\",\"id2\"]'"),
|
|
128
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
129
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
130
|
+
):
|
|
131
|
+
client = get_client()
|
|
132
|
+
attendees_list = json.loads(attendees)
|
|
133
|
+
result = client.add_schedule_attendees(
|
|
134
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
135
|
+
attendees=attendees_list, user_token=user_token, user_id=user_id,
|
|
136
|
+
)
|
|
137
|
+
output_result(result, fields=["schedule_id"], title="Add Attendees Result")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command("delete-attendees")
|
|
141
|
+
def delete_schedule_attendees(
|
|
142
|
+
calendar_id: str = typer.Argument(help="Calendar ID"),
|
|
143
|
+
schedule_id: str = typer.Argument(help="Schedule ID"),
|
|
144
|
+
attendees: str = typer.Argument(help="Attendee staff IDs as JSON list"),
|
|
145
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
146
|
+
user_id: str = typer.Option("", "--user-id", help="User ID"),
|
|
147
|
+
):
|
|
148
|
+
client = get_client()
|
|
149
|
+
attendees_list = json.loads(attendees)
|
|
150
|
+
result = client.delete_schedule_attendees(
|
|
151
|
+
calendar_id=calendar_id, schedule_id=schedule_id,
|
|
152
|
+
attendees=attendees_list, user_token=user_token, user_id=user_id,
|
|
153
|
+
)
|
|
154
|
+
output_result(result, fields=["schedule_id"], title="Delete Attendees Result")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import json
|
|
3
|
+
from rich import print as rprint
|
|
4
|
+
|
|
5
|
+
from lansenger_cli.utils import is_json_output, console
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Parse and verify callback events")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command("parse-payload")
|
|
11
|
+
def parse_callback_payload(
|
|
12
|
+
encrypted_data: str = typer.Argument(help="Encrypted callback data"),
|
|
13
|
+
encoding_key: str = typer.Option("", "--encoding-key", help="Encoding key for decryption"),
|
|
14
|
+
verify_signature: bool = typer.Option(False, "--verify-sig", help="Verify signature"),
|
|
15
|
+
timestamp: str = typer.Option("", "--timestamp", help="Timestamp for signature verification"),
|
|
16
|
+
nonce: str = typer.Option("", "--nonce", help="Nonce for signature verification"),
|
|
17
|
+
signature: str = typer.Option("", "--signature", help="Signature to verify"),
|
|
18
|
+
):
|
|
19
|
+
from lansenger_sdk import parse_callback_payload
|
|
20
|
+
events = parse_callback_payload(
|
|
21
|
+
encrypted_data,
|
|
22
|
+
encoding_key=encoding_key,
|
|
23
|
+
verify_signature=verify_signature,
|
|
24
|
+
timestamp=timestamp,
|
|
25
|
+
nonce=nonce,
|
|
26
|
+
signature=signature,
|
|
27
|
+
)
|
|
28
|
+
if is_json_output():
|
|
29
|
+
rprint(json.dumps([e.to_dict() if hasattr(e, "to_dict") else str(e) for e in events], indent=2, ensure_ascii=False))
|
|
30
|
+
return
|
|
31
|
+
for event in events:
|
|
32
|
+
rprint(f"[bold cyan]Event #{event.event_id}[/bold cyan] — {event.event_type} ({event.category})")
|
|
33
|
+
if hasattr(event, "data") and hasattr(event.data, "to_dict"):
|
|
34
|
+
rprint(json.dumps(event.data.to_dict(), indent=2, ensure_ascii=False))
|
|
35
|
+
else:
|
|
36
|
+
rprint(event.data)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command("verify-signature")
|
|
40
|
+
def verify_callback_signature(
|
|
41
|
+
timestamp: str = typer.Argument(help="Timestamp"),
|
|
42
|
+
nonce: str = typer.Argument(help="Nonce"),
|
|
43
|
+
signature: str = typer.Argument(help="Signature"),
|
|
44
|
+
encoding_key: str = typer.Argument(help="Encoding key"),
|
|
45
|
+
):
|
|
46
|
+
from lansenger_sdk import verify_callback_signature
|
|
47
|
+
valid = verify_callback_signature(timestamp, nonce, signature, encoding_key)
|
|
48
|
+
rprint(f"Signature valid: {valid}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("event-types")
|
|
52
|
+
def get_callback_event_types():
|
|
53
|
+
from lansenger_sdk import get_callback_event_types
|
|
54
|
+
types = get_callback_event_types()
|
|
55
|
+
if is_json_output():
|
|
56
|
+
rprint(json.dumps(types, indent=2, ensure_ascii=False))
|
|
57
|
+
return
|
|
58
|
+
from rich.table import Table
|
|
59
|
+
table = Table(title="Callback Event Types", show_header=True, header_style="bold cyan")
|
|
60
|
+
table.add_column("Event Type")
|
|
61
|
+
table.add_column("Category")
|
|
62
|
+
for event_type, category in types.items():
|
|
63
|
+
table.add_row(event_type, category)
|
|
64
|
+
console.print(table)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print as rprint
|
|
3
|
+
|
|
4
|
+
from lansenger_sdk import CredentialStore
|
|
5
|
+
|
|
6
|
+
from lansenger_cli.utils import get_store, output_result, is_json_output
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="Manage CLI configuration (credentials, tokens)")
|
|
9
|
+
|
|
10
|
+
VALID_KEYS = ["app_id", "app_secret", "api_gateway_url", "passport_url"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command("set")
|
|
14
|
+
def config_set(
|
|
15
|
+
key: str = typer.Argument(help=f"Config key: {', '.join(VALID_KEYS)}"),
|
|
16
|
+
value: str = typer.Argument(help="Config value"),
|
|
17
|
+
):
|
|
18
|
+
if key not in VALID_KEYS:
|
|
19
|
+
rprint(f"[red]Error:[/red] Invalid key '{key}'. Valid keys: {', '.join(VALID_KEYS)}")
|
|
20
|
+
raise typer.Exit(1)
|
|
21
|
+
store = get_store()
|
|
22
|
+
creds = store.load_credentials()
|
|
23
|
+
creds[key] = value
|
|
24
|
+
store.save_credentials(
|
|
25
|
+
app_id=creds.get("app_id", ""),
|
|
26
|
+
app_secret=creds.get("app_secret", ""),
|
|
27
|
+
api_gateway_url=creds.get("api_gateway_url", ""),
|
|
28
|
+
passport_url=creds.get("passport_url", ""),
|
|
29
|
+
)
|
|
30
|
+
rprint(f"[green]Set[/green] {key} = {value}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command("show")
|
|
34
|
+
def config_show():
|
|
35
|
+
store = get_store()
|
|
36
|
+
if is_json_output():
|
|
37
|
+
rprint(store.load())
|
|
38
|
+
return
|
|
39
|
+
creds = store.load_credentials()
|
|
40
|
+
has = store.has_credentials()
|
|
41
|
+
full = store.has_full_config()
|
|
42
|
+
rprint(f"Credentials configured: {has}")
|
|
43
|
+
rprint(f"Full config available: {full}")
|
|
44
|
+
rprint(f"Store path: {store.path}")
|
|
45
|
+
for k, v in creds.items():
|
|
46
|
+
display = v if k in ("api_gateway_url", "passport_url") else ("***" if v else "(empty)")
|
|
47
|
+
rprint(f" {k}: {display}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command("clear")
|
|
51
|
+
def config_clear():
|
|
52
|
+
store = get_store()
|
|
53
|
+
store.clear()
|
|
54
|
+
rprint("[green]Cleared[/green] all stored configuration.")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from lansenger_cli.utils import get_client, output_result, output_list
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(help="Query department information")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@app.command("detail")
|
|
9
|
+
def fetch_department_detail(
|
|
10
|
+
department_id: str = typer.Argument(help="Department ID"),
|
|
11
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
12
|
+
tag_id: str = typer.Option("", "--tag-id", help="Tag ID"),
|
|
13
|
+
):
|
|
14
|
+
client = get_client()
|
|
15
|
+
result = client.fetch_department_detail(
|
|
16
|
+
department_id=department_id, user_token=user_token, tag_id=tag_id,
|
|
17
|
+
)
|
|
18
|
+
output_result(result, fields=[
|
|
19
|
+
"id", "name", "parent_id", "has_children",
|
|
20
|
+
"normal_members", "inactive_members",
|
|
21
|
+
], title="Department Detail")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("children")
|
|
25
|
+
def fetch_department_children(
|
|
26
|
+
department_id: str = typer.Argument(help="Department ID"),
|
|
27
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
28
|
+
):
|
|
29
|
+
client = get_client()
|
|
30
|
+
result = client.fetch_department_children(department_id=department_id, user_token=user_token)
|
|
31
|
+
if result.success and result.departments:
|
|
32
|
+
output_list(result.departments, columns=["ID", "Name", "Parent ID", "Has Children"], row_mapper=lambda d: [
|
|
33
|
+
getattr(d, "id", ""), getattr(d, "name", ""),
|
|
34
|
+
getattr(d, "parent_id", ""), getattr(d, "has_children", ""),
|
|
35
|
+
])
|
|
36
|
+
else:
|
|
37
|
+
output_result(result, title="Department Children")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.command("staffs")
|
|
41
|
+
def fetch_department_staffs(
|
|
42
|
+
department_id: str = typer.Argument(help="Department ID"),
|
|
43
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
44
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
45
|
+
page_size: int = typer.Option(100, "--size", "-s", help="Page size"),
|
|
46
|
+
):
|
|
47
|
+
client = get_client()
|
|
48
|
+
result = client.fetch_department_staffs(
|
|
49
|
+
department_id=department_id, user_token=user_token,
|
|
50
|
+
page=page, page_size=page_size,
|
|
51
|
+
)
|
|
52
|
+
if result.success and result.staffs:
|
|
53
|
+
output_result(result, fields=["has_more", "total"], title="Department Staffs")
|
|
54
|
+
output_list(result.staffs, columns=["Staff ID", "Name", "Gender"], row_mapper=lambda s: [
|
|
55
|
+
getattr(s, "staff_id", ""), getattr(s, "name", ""),
|
|
56
|
+
getattr(s, "gender", ""),
|
|
57
|
+
])
|
|
58
|
+
else:
|
|
59
|
+
output_result(result)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
from lansenger_cli.utils import get_client, output_result, output_list
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(help="Manage groups")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command("create")
|
|
10
|
+
def create_group(
|
|
11
|
+
name: str = typer.Argument(help="Group name"),
|
|
12
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
13
|
+
owner_id: str = typer.Option("", "--owner", help="Owner staff ID"),
|
|
14
|
+
description: str = typer.Option("", "--desc", "-d", help="Group description"),
|
|
15
|
+
avatar_id: str = typer.Option("", "--avatar", help="Avatar ID"),
|
|
16
|
+
staff_id_list: Optional[List[str]] = typer.Option(None, "--staff", help="Staff IDs to add"),
|
|
17
|
+
department_id_list: Optional[List[str]] = typer.Option(None, "--dept", help="Department IDs to add"),
|
|
18
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
19
|
+
):
|
|
20
|
+
client = get_client()
|
|
21
|
+
result = client.create_group(
|
|
22
|
+
name=name, org_id=org_id, owner_id=owner_id,
|
|
23
|
+
description=description, avatar_id=avatar_id,
|
|
24
|
+
staff_id_list=staff_id_list, department_id_list=department_id_list,
|
|
25
|
+
user_token=user_token,
|
|
26
|
+
)
|
|
27
|
+
output_result(result, fields=["group_id", "total_members"], title="Create Group Result")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command("info")
|
|
31
|
+
def fetch_group_info(
|
|
32
|
+
group_id: str = typer.Argument(help="Group ID"),
|
|
33
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
34
|
+
):
|
|
35
|
+
client = get_client()
|
|
36
|
+
result = client.fetch_group_info(group_id=group_id, user_token=user_token)
|
|
37
|
+
output_result(result, fields=[
|
|
38
|
+
"name", "description", "owner", "creator", "state",
|
|
39
|
+
"manage_mode", "is_public", "max_members", "total_members",
|
|
40
|
+
], title="Group Info")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command("members")
|
|
44
|
+
def fetch_group_members(
|
|
45
|
+
group_id: str = typer.Argument(help="Group ID"),
|
|
46
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
47
|
+
page_offset: int = typer.Option(0, "--page", "-p", help="Page offset"),
|
|
48
|
+
page_size: int = typer.Option(100, "--size", "-s", help="Page size"),
|
|
49
|
+
):
|
|
50
|
+
client = get_client()
|
|
51
|
+
result = client.fetch_group_members(
|
|
52
|
+
group_id=group_id, user_token=user_token,
|
|
53
|
+
page_offset=page_offset, page_size=page_size,
|
|
54
|
+
)
|
|
55
|
+
if result.success:
|
|
56
|
+
output_result(result, fields=["total_members"], title="Group Members")
|
|
57
|
+
if result.members:
|
|
58
|
+
output_list(result.members, columns=["Staff ID", "Name", "Role"], row_mapper=lambda m: [
|
|
59
|
+
getattr(m, "staff_id", ""), getattr(m, "name", ""), getattr(m, "role", "")
|
|
60
|
+
])
|
|
61
|
+
else:
|
|
62
|
+
output_result(result)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command("list")
|
|
66
|
+
def fetch_group_list(
|
|
67
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
68
|
+
page_offset: int = typer.Option(0, "--page", "-p", help="Page offset"),
|
|
69
|
+
page_size: int = typer.Option(100, "--size", "-s", help="Page size"),
|
|
70
|
+
):
|
|
71
|
+
client = get_client()
|
|
72
|
+
result = client.fetch_group_list(user_token=user_token, page_offset=page_offset, page_size=page_size)
|
|
73
|
+
output_result(result, fields=["total_group_ids"], title="Group List")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.command("check")
|
|
77
|
+
def check_is_in_group(
|
|
78
|
+
group_id: str = typer.Argument(help="Group ID"),
|
|
79
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
80
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID to check"),
|
|
81
|
+
):
|
|
82
|
+
client = get_client()
|
|
83
|
+
result = client.check_is_in_group(group_id=group_id, user_token=user_token, staff_id=staff_id)
|
|
84
|
+
output_result(result, fields=["is_in_group"], title="Is In Group")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command("update")
|
|
88
|
+
def update_group_info(
|
|
89
|
+
group_id: str = typer.Argument(help="Group ID"),
|
|
90
|
+
name: str = typer.Option("", "--name", help="New group name"),
|
|
91
|
+
description: str = typer.Option("", "--desc", help="New description"),
|
|
92
|
+
owner_id: str = typer.Option("", "--owner", help="New owner ID"),
|
|
93
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
94
|
+
):
|
|
95
|
+
client = get_client()
|
|
96
|
+
result = client.update_group_info(
|
|
97
|
+
group_id=group_id, name=name, description=description,
|
|
98
|
+
owner_id=owner_id, user_token=user_token,
|
|
99
|
+
)
|
|
100
|
+
output_result(result, title="Update Group Result")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command("update-members")
|
|
104
|
+
def update_group_members(
|
|
105
|
+
group_id: str = typer.Argument(help="Group ID"),
|
|
106
|
+
add_user: Optional[List[str]] = typer.Option(None, "--add", help="Staff IDs to add"),
|
|
107
|
+
del_user: Optional[List[str]] = typer.Option(None, "--remove", help="Staff IDs to remove"),
|
|
108
|
+
add_dept: Optional[List[str]] = typer.Option(None, "--add-dept", help="Department IDs to add"),
|
|
109
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
110
|
+
):
|
|
111
|
+
client = get_client()
|
|
112
|
+
result = client.update_group_members(
|
|
113
|
+
group_id=group_id, add_user_list=add_user,
|
|
114
|
+
del_user_list=del_user, add_department_id_list=add_dept,
|
|
115
|
+
user_token=user_token,
|
|
116
|
+
)
|
|
117
|
+
output_result(result, fields=["total_members", "added_staff_count", "deleted_staff_count"], title="Update Members Result")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print as rprint
|
|
3
|
+
|
|
4
|
+
from lansenger_cli.utils import get_client
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(help="Health check and connection verification")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command("check")
|
|
10
|
+
def health_check():
|
|
11
|
+
client = get_client()
|
|
12
|
+
result = client.health_check()
|
|
13
|
+
if result:
|
|
14
|
+
rprint("[green]OK[/green] — Lansenger connection is healthy (app token obtained successfully)")
|
|
15
|
+
else:
|
|
16
|
+
rprint("[red]FAIL[/red] — Could not obtain app token. Check your credentials.")
|
|
17
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from lansenger_cli.utils import get_client, output_result
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(help="Upload and download media files")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@app.command("upload")
|
|
9
|
+
def upload_media(
|
|
10
|
+
file_path: str = typer.Argument(help="Local file path to upload"),
|
|
11
|
+
media_type: int = typer.Option(3, "--media-type", "-t", help="1=video, 2=image, 3=file"),
|
|
12
|
+
):
|
|
13
|
+
client = get_client()
|
|
14
|
+
result = client.upload_media(file_path=file_path, media_type=media_type)
|
|
15
|
+
output_result(result, fields=["message_id"], title="Upload Media Result")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command("download")
|
|
19
|
+
def download_media(
|
|
20
|
+
media_id: str = typer.Argument(help="Media ID to download"),
|
|
21
|
+
):
|
|
22
|
+
client = get_client()
|
|
23
|
+
result = client.download_media(media_id=media_id)
|
|
24
|
+
if result.success:
|
|
25
|
+
from rich import print as rprint
|
|
26
|
+
rprint(f"[green]Downloaded media[/green] (size: {len(result.data) if result.data else 0} bytes)")
|
|
27
|
+
else:
|
|
28
|
+
output_result(result)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command("download-to-file")
|
|
32
|
+
def download_media_to_file(
|
|
33
|
+
media_id: str = typer.Argument(help="Media ID to download"),
|
|
34
|
+
target_path: str = typer.Option("", "--output", "-o", help="Target file path"),
|
|
35
|
+
media_type: str = typer.Option("file", "--media-type", help="file, image, or video"),
|
|
36
|
+
):
|
|
37
|
+
client = get_client()
|
|
38
|
+
saved_path = client.download_media_to_file(
|
|
39
|
+
media_id=media_id, target_path=target_path, media_type=media_type,
|
|
40
|
+
)
|
|
41
|
+
from rich import print as rprint
|
|
42
|
+
rprint(f"[green]Saved to:[/green] {saved_path}")
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
from lansenger_cli.utils import get_client, output_result, output_list
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(help="Send and manage messages")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command("send-text")
|
|
10
|
+
def send_text(
|
|
11
|
+
chat_id: str = typer.Argument(help="Chat ID (user/group)"),
|
|
12
|
+
content: str = typer.Argument(help="Text content"),
|
|
13
|
+
file_path: str = typer.Option("", "--file", "-f", help="File path to attach"),
|
|
14
|
+
is_group: bool = typer.Option(False, "--group", "-g", help="Send as group message"),
|
|
15
|
+
reminder_all: bool = typer.Option(False, "--mention-all", help="@all in group"),
|
|
16
|
+
reminder_user_ids: Optional[List[str]] = typer.Option(None, "--mention", help="User IDs to @mention"),
|
|
17
|
+
user_token: str = typer.Option("", "--user-token", help="User token for private channel"),
|
|
18
|
+
sender_id: str = typer.Option("", "--sender-id", help="Sender staff ID for group message"),
|
|
19
|
+
):
|
|
20
|
+
client = get_client()
|
|
21
|
+
result = client.send_text(
|
|
22
|
+
chat_id=chat_id,
|
|
23
|
+
content=content,
|
|
24
|
+
file_path=file_path,
|
|
25
|
+
is_group=is_group,
|
|
26
|
+
reminder_all=reminder_all,
|
|
27
|
+
reminder_user_ids=reminder_user_ids,
|
|
28
|
+
user_token=user_token,
|
|
29
|
+
sender_id=sender_id,
|
|
30
|
+
)
|
|
31
|
+
output_result(result, fields=["message_id", "msg_type", "operation"], title="Send Text Result")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command("send-markdown")
|
|
35
|
+
def send_markdown(
|
|
36
|
+
chat_id: str = typer.Argument(help="Chat ID"),
|
|
37
|
+
content: str = typer.Argument(help="Markdown content"),
|
|
38
|
+
):
|
|
39
|
+
client = get_client()
|
|
40
|
+
result = client.send_markdown(chat_id=chat_id, content=content)
|
|
41
|
+
output_result(result, fields=["message_id", "msg_type", "operation"], title="Send Markdown Result")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command("send-file")
|
|
45
|
+
def send_file(
|
|
46
|
+
chat_id: str = typer.Argument(help="Chat ID"),
|
|
47
|
+
file_path: str = typer.Argument(help="Local file path"),
|
|
48
|
+
caption: str = typer.Option("", "--caption", "-c", help="Caption text"),
|
|
49
|
+
media_type: Optional[int] = typer.Option(None, "--media-type", help="1=video, 2=image, 3=file"),
|
|
50
|
+
):
|
|
51
|
+
client = get_client()
|
|
52
|
+
result = client.send_file(
|
|
53
|
+
chat_id=chat_id,
|
|
54
|
+
file_path=file_path,
|
|
55
|
+
caption=caption,
|
|
56
|
+
media_type=media_type,
|
|
57
|
+
)
|
|
58
|
+
output_result(result, fields=["message_id", "msg_type", "operation"], title="Send File Result")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command("send-image-url")
|
|
62
|
+
def send_image_url(
|
|
63
|
+
chat_id: str = typer.Argument(help="Chat ID"),
|
|
64
|
+
image_url: str = typer.Argument(help="Image URL to send"),
|
|
65
|
+
caption: str = typer.Option("", "--caption", "-c", help="Caption text"),
|
|
66
|
+
):
|
|
67
|
+
client = get_client()
|
|
68
|
+
result = client.send_image_url(chat_id=chat_id, image_url=image_url, caption=caption)
|
|
69
|
+
output_result(result, fields=["message_id", "msg_type", "operation"], title="Send Image Result")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.command("send-link-card")
|
|
73
|
+
def send_link_card(
|
|
74
|
+
chat_id: str = typer.Argument(help="Chat ID"),
|
|
75
|
+
title: str = typer.Argument(help="Card title"),
|
|
76
|
+
link: str = typer.Argument(help="Card link URL"),
|
|
77
|
+
description: str = typer.Option("", "--desc", "-d", help="Card description"),
|
|
78
|
+
icon_link: str = typer.Option("", "--icon", help="Icon URL"),
|
|
79
|
+
pc_link: str = typer.Option("", "--pc-link", help="PC link URL"),
|
|
80
|
+
from_name: str = typer.Option("", "--from-name", help="Source name"),
|
|
81
|
+
from_icon_link: str = typer.Option("", "--from-icon", help="Source icon URL"),
|
|
82
|
+
):
|
|
83
|
+
client = get_client()
|
|
84
|
+
result = client.send_link_card(
|
|
85
|
+
chat_id=chat_id, title=title, link=link,
|
|
86
|
+
description=description, icon_link=icon_link,
|
|
87
|
+
pc_link=pc_link, from_name=from_name, from_icon_link=from_icon_link,
|
|
88
|
+
)
|
|
89
|
+
output_result(result, fields=["message_id", "msg_type", "operation"], title="Send Link Card Result")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command("send-app-articles")
|
|
93
|
+
def send_app_articles(
|
|
94
|
+
chat_id: str = typer.Argument(help="Chat ID"),
|
|
95
|
+
articles: List[str] = typer.Argument(help="Articles as JSON dicts, e.g. '{\"title\":\"T\",\"url\":\"U\"}'"),
|
|
96
|
+
):
|
|
97
|
+
import json
|
|
98
|
+
parsed = [json.loads(a) for a in articles]
|
|
99
|
+
client = get_client()
|
|
100
|
+
result = client.send_app_articles(chat_id=chat_id, articles=parsed)
|
|
101
|
+
output_result(result, fields=["message_id", "msg_type", "operation"], title="Send App Articles Result")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@app.command("send-app-card")
|
|
105
|
+
def send_app_card(
|
|
106
|
+
chat_id: str = typer.Argument(help="Chat ID"),
|
|
107
|
+
body_title: str = typer.Argument(help="Card body title"),
|
|
108
|
+
head_title: str = typer.Option("", "--head-title", help="Card head title"),
|
|
109
|
+
body_sub_title: str = typer.Option("", "--sub-title", help="Card sub title"),
|
|
110
|
+
body_content: str = typer.Option("", "--content", help="Card body content"),
|
|
111
|
+
signature: str = typer.Option("", "--signature", help="Card signature"),
|
|
112
|
+
card_link: str = typer.Option("", "--card-link", help="Card link URL"),
|
|
113
|
+
pc_card_link: str = typer.Option("", "--pc-card-link", help="PC card link URL"),
|
|
114
|
+
is_dynamic: bool = typer.Option(False, "--dynamic", help="Enable dynamic card updates"),
|
|
115
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID"),
|
|
116
|
+
head_icon_url: str = typer.Option("", "--head-icon", help="Head icon URL"),
|
|
117
|
+
):
|
|
118
|
+
client = get_client()
|
|
119
|
+
result = client.send_app_card(
|
|
120
|
+
chat_id=chat_id, body_title=body_title,
|
|
121
|
+
head_title=head_title, body_sub_title=body_sub_title,
|
|
122
|
+
body_content=body_content, signature=signature,
|
|
123
|
+
card_link=card_link, pc_card_link=pc_card_link,
|
|
124
|
+
is_dynamic=is_dynamic, staff_id=staff_id, head_icon_url=head_icon_url,
|
|
125
|
+
)
|
|
126
|
+
output_result(result, fields=["message_id", "msg_type", "operation"], title="Send App Card Result")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@app.command("update-dynamic-card")
|
|
130
|
+
def update_dynamic_card(
|
|
131
|
+
msg_id: str = typer.Argument(help="Message ID of the dynamic card"),
|
|
132
|
+
is_last_update: bool = typer.Option(False, "--last", help="Mark as last update"),
|
|
133
|
+
):
|
|
134
|
+
client = get_client()
|
|
135
|
+
result = client.update_dynamic_card(msg_id=msg_id, is_last_update=is_last_update)
|
|
136
|
+
output_result(result, fields=["message_id", "operation"], title="Update Dynamic Card Result")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@app.command("revoke")
|
|
140
|
+
def revoke_message(
|
|
141
|
+
message_ids: List[str] = typer.Argument(help="Message IDs to revoke"),
|
|
142
|
+
chat_type: str = typer.Option("bot", "--chat-type", help="bot, account, or private"),
|
|
143
|
+
sender_id: str = typer.Option("", "--sender-id", help="Sender staff ID"),
|
|
144
|
+
):
|
|
145
|
+
client = get_client()
|
|
146
|
+
result = client.revoke_message(message_ids=message_ids, chat_type=chat_type, sender_id=sender_id)
|
|
147
|
+
output_result(result, fields=["message_id", "operation"], title="Revoke Message Result")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@app.command("query-groups")
|
|
151
|
+
def query_groups(
|
|
152
|
+
page_offset: int = typer.Option(1, "--page", "-p", help="Page offset"),
|
|
153
|
+
page_size: int = typer.Option(100, "--size", "-s", help="Page size"),
|
|
154
|
+
):
|
|
155
|
+
client = get_client()
|
|
156
|
+
result = client.query_groups(page_offset=page_offset, page_size=page_size)
|
|
157
|
+
if result.success:
|
|
158
|
+
output_result(result, fields=["total_group_ids", "operation"], title="Query Groups Result")
|
|
159
|
+
if result.group_ids:
|
|
160
|
+
output_list(result.group_ids, columns=["Group ID"], title="Groups", row_mapper=lambda gid: [gid])
|
|
161
|
+
else:
|
|
162
|
+
output_result(result)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print as rprint
|
|
3
|
+
|
|
4
|
+
from lansenger_cli.utils import get_client, output_result
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(help="OAuth2 user authentication operations")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command("authorize-url")
|
|
10
|
+
def build_authorize_url(
|
|
11
|
+
redirect_uri: str = typer.Argument(help="Redirect URI after auth"),
|
|
12
|
+
scope: str = typer.Option("basic_userinfor", "--scope", "-s", help="OAuth2 scope"),
|
|
13
|
+
state: str = typer.Option("", "--state", help="State parameter for CSRF protection"),
|
|
14
|
+
):
|
|
15
|
+
client = get_client()
|
|
16
|
+
url = client.build_authorize_url(redirect_uri=redirect_uri, scope=scope, state=state)
|
|
17
|
+
rprint(f"[green]Authorize URL:[/green] {url}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.command("exchange-code")
|
|
21
|
+
def exchange_code(
|
|
22
|
+
code: str = typer.Argument(help="Authorization code from callback"),
|
|
23
|
+
redirect_uri: str = typer.Option("", "--redirect-uri", help="Redirect URI used in authorize"),
|
|
24
|
+
):
|
|
25
|
+
client = get_client()
|
|
26
|
+
result = client.exchange_code(code=code, redirect_uri=redirect_uri)
|
|
27
|
+
output_result(result, fields=[
|
|
28
|
+
"user_token", "expires_in", "refresh_token",
|
|
29
|
+
"refresh_expires_in", "staff_id", "scope",
|
|
30
|
+
], title="Exchange Code Result")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command("refresh-token")
|
|
34
|
+
def refresh_user_token(
|
|
35
|
+
refresh_token: str = typer.Argument(help="Refresh token"),
|
|
36
|
+
scope: str = typer.Option("", "--scope", "-s", help="Scope"),
|
|
37
|
+
):
|
|
38
|
+
client = get_client()
|
|
39
|
+
result = client.refresh_user_token(refresh_token=refresh_token, scope=scope)
|
|
40
|
+
output_result(result, fields=[
|
|
41
|
+
"user_token", "expires_in", "refresh_token", "staff_id",
|
|
42
|
+
], title="Refresh Token Result")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("user-info")
|
|
46
|
+
def fetch_user_info(
|
|
47
|
+
user_token: str = typer.Argument(help="User token"),
|
|
48
|
+
):
|
|
49
|
+
client = get_client()
|
|
50
|
+
result = client.fetch_user_info(user_token=user_token)
|
|
51
|
+
output_result(result, fields=[
|
|
52
|
+
"staff_id", "name", "org_id", "org_name",
|
|
53
|
+
"mobile_phone", "email", "employee_number",
|
|
54
|
+
], title="User Info")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("parse-callback")
|
|
58
|
+
def parse_authorize_callback(
|
|
59
|
+
query_string: str = typer.Argument(help="Query string from callback URL"),
|
|
60
|
+
):
|
|
61
|
+
from lansenger_sdk import LansengerSyncClient
|
|
62
|
+
params = LansengerSyncClient.parse_authorize_callback(query_string)
|
|
63
|
+
rprint(params)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command("validate-state")
|
|
67
|
+
def validate_callback_state(
|
|
68
|
+
callback_state: str = typer.Argument(help="State from callback"),
|
|
69
|
+
expected_state: str = typer.Argument(help="Expected state you set"),
|
|
70
|
+
):
|
|
71
|
+
from lansenger_sdk import LansengerSyncClient
|
|
72
|
+
valid = LansengerSyncClient.validate_callback_state(callback_state, expected_state)
|
|
73
|
+
rprint(f"State valid: {valid}")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
from lansenger_cli.utils import get_client, output_result, output_list
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(help="Query staff/contacts information")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command("basic-info")
|
|
10
|
+
def fetch_staff_basic_info(
|
|
11
|
+
staff_id: str = typer.Argument(help="Staff ID"),
|
|
12
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
13
|
+
):
|
|
14
|
+
client = get_client()
|
|
15
|
+
result = client.fetch_staff_basic_info(staff_id=staff_id, user_token=user_token)
|
|
16
|
+
output_result(result, fields=[
|
|
17
|
+
"org_id", "org_name", "name", "gender", "signature",
|
|
18
|
+
"avatar_url", "status", "departments",
|
|
19
|
+
], title="Staff Basic Info")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command("detail")
|
|
23
|
+
def fetch_staff_detail(
|
|
24
|
+
staff_id: str = typer.Argument(help="Staff ID"),
|
|
25
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
26
|
+
):
|
|
27
|
+
client = get_client()
|
|
28
|
+
result = client.fetch_staff_detail(staff_id=staff_id, user_token=user_token)
|
|
29
|
+
output_result(result, fields=[
|
|
30
|
+
"org_id", "org_name", "name", "gender", "email",
|
|
31
|
+
"mobile_phone", "avatar_url", "career", "tags",
|
|
32
|
+
], title="Staff Detail")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command("ancestors")
|
|
36
|
+
def fetch_department_ancestors(
|
|
37
|
+
staff_id: str = typer.Argument(help="Staff ID"),
|
|
38
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
39
|
+
):
|
|
40
|
+
client = get_client()
|
|
41
|
+
result = client.fetch_department_ancestors(staff_id=staff_id, user_token=user_token)
|
|
42
|
+
if result.success and result.ancestor_groups:
|
|
43
|
+
output_list(result.ancestor_groups, columns=["Department Group"], row_mapper=lambda g: [str(g)])
|
|
44
|
+
else:
|
|
45
|
+
output_result(result, title="Department Ancestors")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@app.command("id-mapping")
|
|
49
|
+
def fetch_staff_id_mapping(
|
|
50
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
51
|
+
id_type: str = typer.Argument(help="ID type: phone, email, login_name, external_id"),
|
|
52
|
+
id_value: str = typer.Argument(help="ID value to map"),
|
|
53
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
54
|
+
):
|
|
55
|
+
client = get_client()
|
|
56
|
+
result = client.fetch_staff_id_mapping(
|
|
57
|
+
org_id=org_id, id_type=id_type, id_value=id_value, user_token=user_token,
|
|
58
|
+
)
|
|
59
|
+
output_result(result, fields=["staff_id"], title="Staff ID Mapping")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.command("org-extra-fields")
|
|
63
|
+
def fetch_org_extra_field_ids(
|
|
64
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
65
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
66
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
67
|
+
page_size: int = typer.Option(1000, "--size", "-s", help="Page size"),
|
|
68
|
+
):
|
|
69
|
+
client = get_client()
|
|
70
|
+
result = client.fetch_org_extra_field_ids(
|
|
71
|
+
org_id=org_id, user_token=user_token, page=page, page_size=page_size,
|
|
72
|
+
)
|
|
73
|
+
output_result(result, fields=["has_more", "total"], title="Org Extra Fields")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.command("search")
|
|
77
|
+
def search_staff(
|
|
78
|
+
keyword: str = typer.Argument(help="Search keyword"),
|
|
79
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
80
|
+
user_id: str = typer.Option("", "--user-id", help="User ID context"),
|
|
81
|
+
recursive: bool = typer.Option(True, "--recursive/--no-recursive", help="Recursive search"),
|
|
82
|
+
sector_ids: Optional[List[str]] = typer.Option(None, "--sector", help="Sector IDs"),
|
|
83
|
+
page: Optional[int] = typer.Option(None, "--page", "-p", help="Page number"),
|
|
84
|
+
page_size: Optional[int] = typer.Option(None, "--size", "-s", help="Page size"),
|
|
85
|
+
):
|
|
86
|
+
client = get_client()
|
|
87
|
+
result = client.search_staff(
|
|
88
|
+
keyword=keyword, user_token=user_token, user_id=user_id,
|
|
89
|
+
recursive=recursive, sector_ids=sector_ids,
|
|
90
|
+
page=page, page_size=page_size,
|
|
91
|
+
)
|
|
92
|
+
output_result(result, fields=["has_more", "total"], title="Staff Search")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command("org-info")
|
|
96
|
+
def fetch_org_info(
|
|
97
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
98
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
99
|
+
):
|
|
100
|
+
client = get_client()
|
|
101
|
+
result = client.fetch_org_info(org_id=org_id, user_token=user_token)
|
|
102
|
+
output_result(result, fields=["org_id", "org_name", "icon_url"], title="Org Info")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from lansenger_cli.utils import get_client, output_result
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(help="Streaming message operations (for AI agent progressive output)")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@app.command("create")
|
|
9
|
+
def create_stream_message(
|
|
10
|
+
receiver_id: str = typer.Argument(help="Receiver ID"),
|
|
11
|
+
receiver_type: str = typer.Argument(help="Receiver type: single or group"),
|
|
12
|
+
stream_id: str = typer.Argument(help="Stream ID (unique per session)"),
|
|
13
|
+
):
|
|
14
|
+
client = get_client()
|
|
15
|
+
result = client.create_stream_message(
|
|
16
|
+
receiver_id=receiver_id, receiver_type=receiver_type, stream_id=stream_id,
|
|
17
|
+
)
|
|
18
|
+
output_result(result, fields=["message_id"], title="Create Stream Message Result")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.command("fetch")
|
|
22
|
+
def fetch_stream_message(
|
|
23
|
+
msg_id: str = typer.Argument(help="Message ID of the stream message"),
|
|
24
|
+
):
|
|
25
|
+
client = get_client()
|
|
26
|
+
result = client.fetch_stream_message(msg_id=msg_id)
|
|
27
|
+
output_result(result, fields=["message_id"], title="Fetch Stream Message Result")
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import json
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
from lansenger_cli.utils import get_client, output_result, output_list
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Manage todo tasks")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command("create")
|
|
11
|
+
def create_todo_task(
|
|
12
|
+
title: str = typer.Argument(help="Task title"),
|
|
13
|
+
link: str = typer.Argument(help="Task link URL"),
|
|
14
|
+
pc_link: str = typer.Argument(help="PC link URL"),
|
|
15
|
+
executor_ids: str = typer.Argument(help="Executor IDs as comma-separated list"),
|
|
16
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
17
|
+
type: int = typer.Option(1, "--type", "-t", help="1=notification, 2=approval"),
|
|
18
|
+
source_id: str = typer.Option("", "--source-id", help="Source ID"),
|
|
19
|
+
desc: str = typer.Option("", "--desc", "-d", help="Task description"),
|
|
20
|
+
sender_id: str = typer.Option("", "--sender-id", help="Sender staff ID"),
|
|
21
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
22
|
+
):
|
|
23
|
+
client = get_client()
|
|
24
|
+
ids = executor_ids.split(",")
|
|
25
|
+
result = client.create_todo_task(
|
|
26
|
+
title=title, link=link, pc_link=pc_link,
|
|
27
|
+
executor_ids=ids, org_id=org_id, type=type,
|
|
28
|
+
source_id=source_id, desc=desc,
|
|
29
|
+
sender_id=sender_id, user_token=user_token,
|
|
30
|
+
)
|
|
31
|
+
output_result(result, fields=["todotask_id"], title="Create Todo Result")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command("update")
|
|
35
|
+
def update_todo_task(
|
|
36
|
+
todotask_id: str = typer.Argument(help="Todo task ID"),
|
|
37
|
+
title: str = typer.Argument(help="New title"),
|
|
38
|
+
link: str = typer.Argument(help="New link URL"),
|
|
39
|
+
pc_link: str = typer.Argument(help="New PC link URL"),
|
|
40
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
41
|
+
desc: str = typer.Option("", "--desc", "-d", help="New description"),
|
|
42
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
43
|
+
):
|
|
44
|
+
client = get_client()
|
|
45
|
+
result = client.update_todo_task(
|
|
46
|
+
todotask_id=todotask_id, title=title, link=link,
|
|
47
|
+
pc_link=pc_link, org_id=org_id, desc=desc,
|
|
48
|
+
user_token=user_token,
|
|
49
|
+
)
|
|
50
|
+
output_result(result, fields=["todotask_id"], title="Update Todo Result")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command("update-status")
|
|
54
|
+
def update_todo_task_status(
|
|
55
|
+
todotask_id: str = typer.Argument(help="Todo task ID"),
|
|
56
|
+
status: str = typer.Argument(help="Status: 11=pending read, 12=read, 21=pending do, 22=done"),
|
|
57
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
58
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID"),
|
|
59
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
60
|
+
):
|
|
61
|
+
client = get_client()
|
|
62
|
+
result = client.update_todo_task_status(
|
|
63
|
+
todotask_id=todotask_id, status=status, org_id=org_id,
|
|
64
|
+
staff_id=staff_id, user_token=user_token,
|
|
65
|
+
)
|
|
66
|
+
output_result(result, fields=["todotask_id"], title="Update Todo Status Result")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command("delete")
|
|
70
|
+
def delete_todo_task(
|
|
71
|
+
todotask_id: str = typer.Argument(help="Todo task ID"),
|
|
72
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
73
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID"),
|
|
74
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
75
|
+
):
|
|
76
|
+
client = get_client()
|
|
77
|
+
result = client.delete_todo_task(
|
|
78
|
+
todotask_id=todotask_id, org_id=org_id,
|
|
79
|
+
staff_id=staff_id, user_token=user_token,
|
|
80
|
+
)
|
|
81
|
+
output_result(result, fields=["todotask_id"], title="Delete Todo Result")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command("list")
|
|
85
|
+
def fetch_todo_task_list(
|
|
86
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
87
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
88
|
+
app_ids: Optional[str] = typer.Option(None, "--app-ids", help="App IDs (comma-separated)"),
|
|
89
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID"),
|
|
90
|
+
status_list: Optional[str] = typer.Option(None, "--status", help="Status list (comma-separated)"),
|
|
91
|
+
):
|
|
92
|
+
client = get_client()
|
|
93
|
+
app_ids_list = app_ids.split(",") if app_ids else None
|
|
94
|
+
status_list_parsed = status_list.split(",") if status_list else None
|
|
95
|
+
result = client.fetch_todo_task_list(
|
|
96
|
+
org_id=org_id, user_token=user_token,
|
|
97
|
+
app_ids=app_ids_list, staff_id=staff_id,
|
|
98
|
+
status_list=status_list_parsed,
|
|
99
|
+
)
|
|
100
|
+
output_result(result, fields=["total"], title="Todo Task List")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command("fetch-by-source")
|
|
104
|
+
def fetch_todo_task_by_source_id(
|
|
105
|
+
source_id: str = typer.Argument(help="Source ID"),
|
|
106
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
107
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID"),
|
|
108
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
109
|
+
):
|
|
110
|
+
client = get_client()
|
|
111
|
+
result = client.fetch_todo_task_by_source_id(
|
|
112
|
+
source_id=source_id, org_id=org_id,
|
|
113
|
+
staff_id=staff_id, user_token=user_token,
|
|
114
|
+
)
|
|
115
|
+
output_result(result, fields=[
|
|
116
|
+
"todotask_id", "source_id", "title", "desc",
|
|
117
|
+
"status", "type", "link", "executor_ids",
|
|
118
|
+
], title="Todo Task")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.command("fetch-by-id")
|
|
122
|
+
def fetch_todo_task_by_id(
|
|
123
|
+
todotask_id: str = typer.Argument(help="Todo task ID"),
|
|
124
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
125
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID"),
|
|
126
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
127
|
+
):
|
|
128
|
+
client = get_client()
|
|
129
|
+
result = client.fetch_todo_task_by_id(
|
|
130
|
+
todotask_id=todotask_id, org_id=org_id,
|
|
131
|
+
staff_id=staff_id, user_token=user_token,
|
|
132
|
+
)
|
|
133
|
+
output_result(result, fields=[
|
|
134
|
+
"todotask_id", "source_id", "title", "desc",
|
|
135
|
+
"status", "type", "link", "executor_ids",
|
|
136
|
+
], title="Todo Task")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@app.command("status-counts")
|
|
140
|
+
def fetch_todo_task_status_counts(
|
|
141
|
+
staff_id: str = typer.Argument(help="Staff ID"),
|
|
142
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
143
|
+
app_id: str = typer.Option("", "--app-id", help="App ID"),
|
|
144
|
+
status_list: Optional[str] = typer.Option(None, "--status", help="Status list (comma-separated)"),
|
|
145
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
146
|
+
):
|
|
147
|
+
client = get_client()
|
|
148
|
+
status_parsed = status_list.split(",") if status_list else None
|
|
149
|
+
result = client.fetch_todo_task_status_counts(
|
|
150
|
+
staff_id=staff_id, org_id=org_id,
|
|
151
|
+
app_id=app_id, status_list=status_parsed,
|
|
152
|
+
user_token=user_token,
|
|
153
|
+
)
|
|
154
|
+
output_result(result, fields=["status_counts"], title="Todo Status Counts")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command("executor-status")
|
|
158
|
+
def update_executor_status(
|
|
159
|
+
executor_status_list: str = typer.Argument(help="Executor status list as JSON: '[{\"executorId\":\"x\",\"status\":\"22\"}]'"),
|
|
160
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
161
|
+
todotask_id: str = typer.Option("", "--task-id", help="Todo task ID"),
|
|
162
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
163
|
+
):
|
|
164
|
+
client = get_client()
|
|
165
|
+
parsed = json.loads(executor_status_list)
|
|
166
|
+
result = client.update_executor_status(
|
|
167
|
+
executor_status_list=parsed, org_id=org_id,
|
|
168
|
+
todotask_id=todotask_id, user_token=user_token,
|
|
169
|
+
)
|
|
170
|
+
output_result(result, fields=["todotask_id"], title="Update Executor Status Result")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@app.command("add-executors")
|
|
174
|
+
def add_executors(
|
|
175
|
+
executor_ids: str = typer.Argument(help="Executor IDs (comma-separated)"),
|
|
176
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
177
|
+
todotask_id: str = typer.Option("", "--task-id", help="Todo task ID"),
|
|
178
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
179
|
+
):
|
|
180
|
+
client = get_client()
|
|
181
|
+
ids = executor_ids.split(",")
|
|
182
|
+
result = client.add_executors(
|
|
183
|
+
executor_ids=ids, org_id=org_id,
|
|
184
|
+
todotask_id=todotask_id, user_token=user_token,
|
|
185
|
+
)
|
|
186
|
+
output_result(result, fields=["todotask_id"], title="Add Executors Result")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@app.command("delete-executors")
|
|
190
|
+
def delete_executors(
|
|
191
|
+
executor_ids: str = typer.Argument(help="Executor IDs (comma-separated)"),
|
|
192
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
193
|
+
todotask_id: str = typer.Option("", "--task-id", help="Todo task ID"),
|
|
194
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
195
|
+
):
|
|
196
|
+
client = get_client()
|
|
197
|
+
ids = executor_ids.split(",")
|
|
198
|
+
result = client.delete_executors(
|
|
199
|
+
executor_ids=ids, org_id=org_id,
|
|
200
|
+
todotask_id=todotask_id, user_token=user_token,
|
|
201
|
+
)
|
|
202
|
+
output_result(result, fields=["todotask_id"], title="Delete Executors Result")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.command("executor-list")
|
|
206
|
+
def fetch_executor_list(
|
|
207
|
+
todotask_id: str = typer.Argument(help="Todo task ID"),
|
|
208
|
+
org_id: str = typer.Argument(help="Organization ID"),
|
|
209
|
+
staff_id: str = typer.Option("", "--staff-id", help="Staff ID"),
|
|
210
|
+
status_list: Optional[str] = typer.Option(None, "--status", help="Status list (comma-separated)"),
|
|
211
|
+
user_token: str = typer.Option("", "--user-token", help="User token"),
|
|
212
|
+
):
|
|
213
|
+
client = get_client()
|
|
214
|
+
status_parsed = status_list.split(",") if status_list else None
|
|
215
|
+
result = client.fetch_executor_list(
|
|
216
|
+
todotask_id=todotask_id, org_id=org_id,
|
|
217
|
+
staff_id=staff_id, status_list=status_parsed,
|
|
218
|
+
user_token=user_token,
|
|
219
|
+
)
|
|
220
|
+
output_result(result, fields=["total"], title="Executor List")
|
lansenger_cli/main.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from lansenger_cli.utils import get_store, output_result, is_json_output, console
|
|
4
|
+
from lansenger_cli.commands import (
|
|
5
|
+
config as config_cmd,
|
|
6
|
+
message as message_cmd,
|
|
7
|
+
group as group_cmd,
|
|
8
|
+
staff as staff_cmd,
|
|
9
|
+
department as department_cmd,
|
|
10
|
+
calendar as calendar_cmd,
|
|
11
|
+
todo as todo_cmd,
|
|
12
|
+
oauth as oauth_cmd,
|
|
13
|
+
callback as callback_cmd,
|
|
14
|
+
media as media_cmd,
|
|
15
|
+
streaming as streaming_cmd,
|
|
16
|
+
health as health_cmd,
|
|
17
|
+
)
|
|
18
|
+
from lansenger_cli.utils import set_json_output
|
|
19
|
+
|
|
20
|
+
app = typer.Typer(
|
|
21
|
+
name="lansenger",
|
|
22
|
+
help="Lansenger (蓝信) CLI — interact with Lansenger APIs from the command line.",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
rich_markup_mode="rich",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
app.add_typer(config_cmd.app, name="config")
|
|
28
|
+
app.add_typer(message_cmd.app, name="message")
|
|
29
|
+
app.add_typer(group_cmd.app, name="group")
|
|
30
|
+
app.add_typer(staff_cmd.app, name="staff")
|
|
31
|
+
app.add_typer(department_cmd.app, name="department")
|
|
32
|
+
app.add_typer(calendar_cmd.app, name="calendar")
|
|
33
|
+
app.add_typer(todo_cmd.app, name="todo")
|
|
34
|
+
app.add_typer(oauth_cmd.app, name="oauth")
|
|
35
|
+
app.add_typer(callback_cmd.app, name="callback")
|
|
36
|
+
app.add_typer(media_cmd.app, name="media")
|
|
37
|
+
app.add_typer(streaming_cmd.app, name="streaming")
|
|
38
|
+
app.add_typer(health_cmd.app, name="health")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.callback()
|
|
42
|
+
def global_options(
|
|
43
|
+
json: bool = typer.Option(False, "--json", "-j", help="Output raw JSON instead of formatted tables"),
|
|
44
|
+
):
|
|
45
|
+
set_json_output(json)
|
lansenger_cli/utils.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from lansenger_sdk import LansengerSyncClient, CredentialStore, LansengerConfig
|
|
2
|
+
from rich import print as rprint
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
_json_output = False
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def set_json_output(value: bool):
|
|
11
|
+
global _json_output
|
|
12
|
+
_json_output = value
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_json_output() -> bool:
|
|
16
|
+
return _json_output
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_store() -> CredentialStore:
|
|
20
|
+
return CredentialStore()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_client(store_path: str = "") -> LansengerSyncClient:
|
|
24
|
+
store = CredentialStore(path=store_path) if store_path else CredentialStore()
|
|
25
|
+
creds = store.load_credentials()
|
|
26
|
+
if not creds.get("app_id") or not creds.get("app_secret"):
|
|
27
|
+
env_config = LansengerConfig.from_env()
|
|
28
|
+
if env_config.is_configured():
|
|
29
|
+
return LansengerSyncClient.from_config(env_config)
|
|
30
|
+
rprint("[red]Error:[/red] No credentials configured. Run [bold]lansenger config set[/bold] first, or set LANSENGER_APP_ID / LANSENGER_APP_SECRET env vars.")
|
|
31
|
+
raise SystemExit(1)
|
|
32
|
+
config = LansengerConfig(
|
|
33
|
+
app_id=creds["app_id"],
|
|
34
|
+
app_secret=creds["app_secret"],
|
|
35
|
+
api_gateway_url=creds.get("api_gateway_url", "https://open.e.lanxin.cn/open/apigw"),
|
|
36
|
+
passport_url=creds.get("passport_url", ""),
|
|
37
|
+
)
|
|
38
|
+
return LansengerSyncClient.from_config(config)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def output_result(result, fields: list[str] | None = None, title: str = ""):
|
|
42
|
+
if is_json_output():
|
|
43
|
+
rprint(result.to_dict())
|
|
44
|
+
return
|
|
45
|
+
if not result.success:
|
|
46
|
+
rprint(f"[red]Error:[/red] {result.error}")
|
|
47
|
+
raise SystemExit(1)
|
|
48
|
+
if fields:
|
|
49
|
+
table = Table(title=title, show_header=True, header_style="bold cyan")
|
|
50
|
+
table.add_column("Field")
|
|
51
|
+
table.add_column("Value")
|
|
52
|
+
for f in fields:
|
|
53
|
+
val = getattr(result, f, None)
|
|
54
|
+
if val is not None:
|
|
55
|
+
if isinstance(val, (list, dict)):
|
|
56
|
+
val = str(val)
|
|
57
|
+
table.add_row(f, str(val))
|
|
58
|
+
console.print(table)
|
|
59
|
+
else:
|
|
60
|
+
rprint(result.to_dict())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def output_list(items: list, columns: list[str], title: str = "", row_mapper=None):
|
|
64
|
+
if is_json_output():
|
|
65
|
+
rprint([item.to_dict() if hasattr(item, "to_dict") else item for item in items])
|
|
66
|
+
return
|
|
67
|
+
if not items:
|
|
68
|
+
rprint("[yellow]No results.[/yellow]")
|
|
69
|
+
return
|
|
70
|
+
table = Table(title=title, show_header=True, header_style="bold cyan")
|
|
71
|
+
for col in columns:
|
|
72
|
+
table.add_column(col)
|
|
73
|
+
for item in items:
|
|
74
|
+
if row_mapper:
|
|
75
|
+
row = row_mapper(item)
|
|
76
|
+
else:
|
|
77
|
+
row = [str(getattr(item, col, "")) for col in columns]
|
|
78
|
+
table.add_row(*row)
|
|
79
|
+
console.print(table)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lansenger-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for Lansenger (蓝信) — send messages, manage groups, staff, departments, calendars, todos, and more
|
|
5
|
+
Author: Lansenger PM Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: lansenger,蓝信,cli,chatbot,messaging,enterprise-chat
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Communications :: Chat
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: lansenger-sdk>=1.0.1
|
|
18
|
+
Requires-Dist: typer>=0.9
|
|
19
|
+
Requires-Dist: rich>=13
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
lansenger_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
lansenger_cli/main.py,sha256=m_xplXuUWJvtMFp3JTASTPPuPk4Q7fgWcCwd7E88GQI,1422
|
|
3
|
+
lansenger_cli/utils.py,sha256=uGZmNzCCmEx65ll-4zCfev4OKdrgXiYG6gvT5RihWXM,2676
|
|
4
|
+
lansenger_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
lansenger_cli/commands/calendar.py,sha256=gxvRKOl-DOenLXjfcTSbeJW8jxC0GisfRx34wWmM8SI,6583
|
|
6
|
+
lansenger_cli/commands/callback.py,sha256=vONZogM00ufA5ktxzw-jIiy_0R4FuKjnN_-vVNQR0Ro,2572
|
|
7
|
+
lansenger_cli/commands/config.py,sha256=pcnYCO3TbinGBE6blCNLibokK7J-Bi1JUABGj8xhe40,1683
|
|
8
|
+
lansenger_cli/commands/department.py,sha256=Evyhb1VTeCcCz-NM_V8g4w6wSIoEmO1irj6aKQjE8kg,2306
|
|
9
|
+
lansenger_cli/commands/group.py,sha256=RE4rS6IdRF3EVM0kur8vJz7nenN8E2YDTkZH5zdc7GI,4930
|
|
10
|
+
lansenger_cli/commands/health.py,sha256=P3-yZRqbMAeq0vWDDy_aGxz-__ha8LB1_u2tUvzbrtg,511
|
|
11
|
+
lansenger_cli/commands/media.py,sha256=tY7i8JiOLzlv5m5CLFF0Xy822W4mRV5eLWOrV_WQqgg,1477
|
|
12
|
+
lansenger_cli/commands/message.py,sha256=NprCYSciVL_ZpQXSwuzWw3v8GeSlXep5k7xC0iDp9MU,7090
|
|
13
|
+
lansenger_cli/commands/oauth.py,sha256=HAe74GRc1SJHXcIj3qjKSvpikO4YpNK8Vpn7gN2PGRM,2603
|
|
14
|
+
lansenger_cli/commands/staff.py,sha256=lPOWtcgTp73BqqbuLa5kFKqwPnR3BSnvCJPHVWyJCyY,4083
|
|
15
|
+
lansenger_cli/commands/streaming.py,sha256=OJpEHlNfdOOmQVwY8KrXHtJAQLxZ2V5qB-a7nOv1KwE,972
|
|
16
|
+
lansenger_cli/commands/todo.py,sha256=FERpN4IH4tw-_SpljhEU5QMTQoiHdqUiV1WwkN6HWL4,9017
|
|
17
|
+
lansenger_cli-0.1.0.dist-info/licenses/LICENSE,sha256=34BiAEjSD1EejpdCkHjFTvIzyLpIGVCvWlD1dyUFPzk,1073
|
|
18
|
+
lansenger_cli-0.1.0.dist-info/METADATA,sha256=QFwSPciEhEcUqwLpfsUuTAEqrDLeYSuyTmdADBjSfMo,836
|
|
19
|
+
lansenger_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
20
|
+
lansenger_cli-0.1.0.dist-info/entry_points.txt,sha256=Wl0YcBzsfQOnfB5j_5joUSDIWSzE7U86scMaUeTnopQ,53
|
|
21
|
+
lansenger_cli-0.1.0.dist-info/top_level.txt,sha256=2hKVxWtjVnynROfgDw9VIkkSBOkXh-Fht3cUxfJrYuY,14
|
|
22
|
+
lansenger_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lansenger PM Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lansenger_cli
|