slack-term 1.31.6 → 1.32.0
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.
- package/dist/cli.js +112 -106
- package/package.json +1 -1
- package/ts/cli.ts +157 -16
package/package.json
CHANGED
package/ts/cli.ts
CHANGED
|
@@ -2615,6 +2615,37 @@ async function cmdDoctor(token: string): Promise<void> {
|
|
|
2615
2615
|
}
|
|
2616
2616
|
|
|
2617
2617
|
// --- schedule ---
|
|
2618
|
+
|
|
2619
|
+
/** The xoxb bot token, or a clean exit. `--as-bot` is only ever a token switch,
|
|
2620
|
+
* and its failure is always the same one: the env var is not set. */
|
|
2621
|
+
function requireBotToken(): string {
|
|
2622
|
+
const botToken = resolveBotToken();
|
|
2623
|
+
if (!botToken) {
|
|
2624
|
+
console.error(
|
|
2625
|
+
"Error: --as-bot needs a bot token, but no xoxb- token was found.\n" +
|
|
2626
|
+
" Set SLACK_BOT_TOKEN=xoxb-... in ~/.config/slack-cli/.env (or your shell).",
|
|
2627
|
+
);
|
|
2628
|
+
process.exit(1);
|
|
2629
|
+
}
|
|
2630
|
+
return botToken;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
/** The bot↔user DM for an `@handle`. The handle is resolved on the USER token
|
|
2634
|
+
* (which has users:read) and the DM is opened on the BOT token, so the channel
|
|
2635
|
+
* is the bot's IM with that person and not the person's own self-DM. That
|
|
2636
|
+
* distinction is the entire point of `--as-bot` here: Slack never notifies you
|
|
2637
|
+
* about your own message, so a digest scheduled into a self-DM is delivered
|
|
2638
|
+
* into silence at the one moment nobody is at the terminal to notice. */
|
|
2639
|
+
async function botDmForTarget(
|
|
2640
|
+
target: string,
|
|
2641
|
+
botToken: string,
|
|
2642
|
+
userToken: string,
|
|
2643
|
+
userCookie?: string,
|
|
2644
|
+
): Promise<string> {
|
|
2645
|
+
const userId = await resolveUserId(userToken, target, userCookie);
|
|
2646
|
+
return openDm(botToken, userId);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2618
2649
|
function parsePostAt(at: string): number {
|
|
2619
2650
|
if (/^\d{10,}$/.test(at)) return parseInt(at, 10);
|
|
2620
2651
|
const d = new Date(at.replace(" ", "T"));
|
|
@@ -2650,6 +2681,10 @@ interface ScheduleSendArgs {
|
|
|
2650
2681
|
channelId?: string;
|
|
2651
2682
|
userId?: string;
|
|
2652
2683
|
cookie?: string;
|
|
2684
|
+
asBot?: boolean;
|
|
2685
|
+
/** How the caller overrode the target text, e.g. `--user-id U0123`. Set only
|
|
2686
|
+
* when a FLAG supplied it — never when the command resolved the id itself. */
|
|
2687
|
+
targetOverride?: string;
|
|
2653
2688
|
}
|
|
2654
2689
|
async function cmdScheduleSend(token: string, args: ScheduleSendArgs): Promise<void> {
|
|
2655
2690
|
const getSelf = selfLookup(token, args.cookie);
|
|
@@ -2670,19 +2705,30 @@ async function cmdScheduleSend(token: string, args: ScheduleSendArgs): Promise<v
|
|
|
2670
2705
|
// Self-DM is worst here: the message fires later, unattended, and Slack never
|
|
2671
2706
|
// notifies you about your own post — so a scheduled reminder to yourself is
|
|
2672
2707
|
// delivered into silence at exactly the moment you were counting on it.
|
|
2673
|
-
|
|
2708
|
+
// --as-bot is exempt: that posts as the app, a different identity, which does
|
|
2709
|
+
// notify you. Being unable to say that is what made this warning a dead end —
|
|
2710
|
+
// it named the problem and offered no way out.
|
|
2711
|
+
if (!args.asBot && await isSelfDmChannel(token, channelId, self?.userId, args.cookie)) {
|
|
2674
2712
|
console.error(
|
|
2675
|
-
`Warning: "${args.target}" is a DM to yourself — Slack will NOT notify you when this fires
|
|
2713
|
+
`Warning: "${args.target}" is a DM to yourself — Slack will NOT notify you when this fires.\n` +
|
|
2714
|
+
` To be notified, schedule it as the bot: slack schedule send '${args.target}' '...' --at '${args.at}' --as-bot`,
|
|
2676
2715
|
);
|
|
2677
2716
|
}
|
|
2678
2717
|
|
|
2679
2718
|
const code = safetyCode(channelId, args.message, String(postAt), self?.userId ?? "");
|
|
2680
2719
|
|
|
2720
|
+
// `--channel-id` / `--user-id` win over the target text, so the target stops
|
|
2721
|
+
// being where this goes the moment the two disagree — and the gate printed
|
|
2722
|
+
// only the target. `schedule send @alice … --user-id <bob>` read "To: @alice"
|
|
2723
|
+
// and went to bob. A confirm gate that names the wrong recipient with no sign
|
|
2724
|
+
// it was overridden is worse than no gate: it gets read, and it gets passed.
|
|
2725
|
+
const destOverride = args.targetOverride ? ` [${args.targetOverride} → ${channelId}]` : "";
|
|
2726
|
+
|
|
2681
2727
|
if (args.code !== code) {
|
|
2682
2728
|
requireCode(args.code, code, [
|
|
2683
2729
|
`--- Scheduling message -----------------------`,
|
|
2684
|
-
fromLine(self, { pad: 9 }),
|
|
2685
|
-
` To: ${ref}${threadTs ? ` (thread ${threadTs})` : ""}`,
|
|
2730
|
+
fromLine(self, { asBot: args.asBot, pad: 9 }),
|
|
2731
|
+
` To: ${ref}${threadTs ? ` (thread ${threadTs})` : ""}${destOverride}`,
|
|
2686
2732
|
` At: ${fmtPostAt(postAt)}`,
|
|
2687
2733
|
` Message: ${args.message}`,
|
|
2688
2734
|
`---------------------------------------------`,
|
|
@@ -2690,6 +2736,37 @@ async function cmdScheduleSend(token: string, args: ScheduleSendArgs): Promise<v
|
|
|
2690
2736
|
}
|
|
2691
2737
|
const id = await scheduleMessage(token, channelId, args.message, postAt, threadTs, args.cookie);
|
|
2692
2738
|
console.log(`✓ Scheduled (id: ${id}, at: ${postAtDate})`);
|
|
2739
|
+
|
|
2740
|
+
if (args.asBot) {
|
|
2741
|
+
// The message now belongs to the BOT, so `schedule ls` on the user token
|
|
2742
|
+
// cannot see it and `schedule rm` cannot cancel it. Said at the only moment
|
|
2743
|
+
// the id is in front of the reader.
|
|
2744
|
+
console.error(
|
|
2745
|
+
` Scheduled as the bot — the user token cannot see or cancel it. Use the same flag:\n` +
|
|
2746
|
+
` slack schedule ls --as-bot\n` +
|
|
2747
|
+
` slack schedule rm '${args.target}' ${id} --as-bot`,
|
|
2748
|
+
);
|
|
2749
|
+
// A scheduled bot DM fires unattended, so an app that cannot actually carry
|
|
2750
|
+
// a DM fails at the one moment nobody is watching. Same check `send --as-bot`
|
|
2751
|
+
// runs after the fact; here it is the difference between finding out now and
|
|
2752
|
+
// finding out from an empty inbox.
|
|
2753
|
+
if (channelId.startsWith("D")) {
|
|
2754
|
+
try {
|
|
2755
|
+
const diag = await diagnoseBotMessaging(token);
|
|
2756
|
+
if (!diag.ok) {
|
|
2757
|
+
for (const line of formatDiagnosis(diag, true)) console.error(line);
|
|
2758
|
+
} else {
|
|
2759
|
+
const appUrl = diag.appId ? `https://api.slack.com/apps/${diag.appId}` : "https://api.slack.com/apps";
|
|
2760
|
+
console.error(
|
|
2761
|
+
`Note: if ${args.target} can't reply (app messaging may be off), ` +
|
|
2762
|
+
`enable Messages Tab: ${appUrl} → App Home.`,
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
} catch {
|
|
2766
|
+
// diagnosis is best-effort; never turn a successful schedule into a failure.
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2693
2770
|
}
|
|
2694
2771
|
|
|
2695
2772
|
async function cmdScheduleList(token: string, target?: string, channelId?: string, cookie?: string): Promise<void> {
|
|
@@ -2716,6 +2793,9 @@ interface ScheduleRmArgs {
|
|
|
2716
2793
|
code?: string;
|
|
2717
2794
|
channelId?: string;
|
|
2718
2795
|
cookie?: string;
|
|
2796
|
+
asBot?: boolean;
|
|
2797
|
+
/** How the caller overrode the target text, e.g. `--channel-id C0123`. */
|
|
2798
|
+
targetOverride?: string;
|
|
2719
2799
|
}
|
|
2720
2800
|
async function cmdScheduleRm(token: string, args: ScheduleRmArgs): Promise<void> {
|
|
2721
2801
|
let channelId: string;
|
|
@@ -2727,8 +2807,8 @@ async function cmdScheduleRm(token: string, args: ScheduleRmArgs): Promise<void>
|
|
|
2727
2807
|
if (args.code !== code) {
|
|
2728
2808
|
requireCode(args.code, code, [
|
|
2729
2809
|
`--- Deleting scheduled message ---------------`,
|
|
2730
|
-
fromLine(self, { pad: 9 }),
|
|
2731
|
-
` Channel: ${args.target}`,
|
|
2810
|
+
fromLine(self, { asBot: args.asBot, pad: 9 }),
|
|
2811
|
+
` Channel: ${args.target}${args.targetOverride ? ` [${args.targetOverride} → ${channelId}]` : ""}`,
|
|
2732
2812
|
` ID: ${args.id}`,
|
|
2733
2813
|
`---------------------------------------------`,
|
|
2734
2814
|
]);
|
|
@@ -3729,15 +3809,52 @@ async function main(): Promise<void> {
|
|
|
3729
3809
|
.option("at", { type: "string", demandOption: true, describe: "Delivery time (ISO datetime or Unix ts)" })
|
|
3730
3810
|
.option("code", { type: "string", describe: "Safety hash to confirm" })
|
|
3731
3811
|
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
3732
|
-
.option("user-id", { type: "string", describe: "Raw user ID (opens DM)" })
|
|
3812
|
+
.option("user-id", { type: "string", describe: "Raw user ID (opens DM)" })
|
|
3813
|
+
.option("as-bot", { type: "boolean", default: false, describe: "Schedule via the bot token (xoxb / SLACK_BOT_TOKEN) so a DM notifies the recipient. Without it a scheduled DM to yourself is posted by you, and Slack never notifies you of your own message — it fires into silence." }),
|
|
3733
3814
|
async (argv) => {
|
|
3734
3815
|
const args: ScheduleSendArgs = { target: argv.target!, message: argv.message!, at: argv.at! };
|
|
3735
3816
|
if (argv.code) args.code = argv.code;
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
if (
|
|
3740
|
-
|
|
3817
|
+
// Recorded here, not in the command, because only a flag counts as an
|
|
3818
|
+
// override — under --as-bot the command sets args.userId itself from
|
|
3819
|
+
// the target, and claiming THAT was an override would be a new lie.
|
|
3820
|
+
if (argv["channel-id"]) {
|
|
3821
|
+
args.channelId = argv["channel-id"];
|
|
3822
|
+
args.targetOverride = `--channel-id ${argv["channel-id"]}`;
|
|
3823
|
+
}
|
|
3824
|
+
if (argv["user-id"]) {
|
|
3825
|
+
args.userId = argv["user-id"];
|
|
3826
|
+
args.targetOverride = `--user-id ${argv["user-id"]}`;
|
|
3827
|
+
}
|
|
3828
|
+
let scheduleToken: string;
|
|
3829
|
+
if (argv["as-bot"]) {
|
|
3830
|
+
const botToken = requireBotToken();
|
|
3831
|
+
// Resolve @user → user_id with the *user* token (has users:read),
|
|
3832
|
+
// then let cmdScheduleSend open the DM with the bot token
|
|
3833
|
+
// (im:write). Same split as `send --as-bot`: the bot never needs
|
|
3834
|
+
// users:read, and the DM is bot↔user rather than a self-DM.
|
|
3835
|
+
if (!args.channelId && !args.userId && args.target.startsWith("@")) {
|
|
3836
|
+
try {
|
|
3837
|
+
args.userId = await resolveUserId(tok(argv as W), args.target, ck(argv as W));
|
|
3838
|
+
} catch (e: unknown) {
|
|
3839
|
+
console.error(
|
|
3840
|
+
`Error: could not resolve ${args.target} to a user for the bot DM.\n` +
|
|
3841
|
+
` ${e instanceof Error ? e.message : String(e)}\n` +
|
|
3842
|
+
` Tip: pass --user-id <Uxxxx> to skip the lookup.`,
|
|
3843
|
+
);
|
|
3844
|
+
process.exit(1);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
scheduleToken = botToken;
|
|
3848
|
+
args.asBot = true;
|
|
3849
|
+
} else {
|
|
3850
|
+
scheduleToken = tok(argv as W);
|
|
3851
|
+
// Session cookie for the token that will actually schedule — an
|
|
3852
|
+
// xoxc- desktop token is rejected by the public API without it.
|
|
3853
|
+
// Bot tokens need none.
|
|
3854
|
+
const cookie = ck(argv as W);
|
|
3855
|
+
if (cookie) args.cookie = cookie;
|
|
3856
|
+
}
|
|
3857
|
+
await cmdScheduleSend(scheduleToken, args);
|
|
3741
3858
|
},
|
|
3742
3859
|
)
|
|
3743
3860
|
.command(
|
|
@@ -3745,9 +3862,20 @@ async function main(): Promise<void> {
|
|
|
3745
3862
|
"List pending scheduled messages",
|
|
3746
3863
|
(y2) => y2
|
|
3747
3864
|
.positional("target", { type: "string", describe: "#channel to filter by" })
|
|
3748
|
-
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
3865
|
+
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
3866
|
+
.option("as-bot", { type: "boolean", default: false, describe: "List what the BOT token scheduled. A message from `schedule send --as-bot` belongs to the app, so the user token's listing does not contain it — and an invisible pending message cannot be cancelled." }),
|
|
3749
3867
|
async (argv) => {
|
|
3750
|
-
|
|
3868
|
+
const target = argv.target as string | undefined;
|
|
3869
|
+
if (argv["as-bot"]) {
|
|
3870
|
+
const botToken = requireBotToken();
|
|
3871
|
+
let channelId = argv["channel-id"] as string | undefined;
|
|
3872
|
+
if (!channelId && target?.startsWith("@")) {
|
|
3873
|
+
channelId = await botDmForTarget(target, botToken, tok(argv as W), ck(argv as W));
|
|
3874
|
+
}
|
|
3875
|
+
await cmdScheduleList(botToken, target, channelId);
|
|
3876
|
+
return;
|
|
3877
|
+
}
|
|
3878
|
+
await cmdScheduleList(tok(argv as W), target, argv["channel-id"], ck(argv as W));
|
|
3751
3879
|
},
|
|
3752
3880
|
)
|
|
3753
3881
|
.command(
|
|
@@ -3757,13 +3885,26 @@ async function main(): Promise<void> {
|
|
|
3757
3885
|
.positional("target", { type: "string", demandOption: true, describe: "#channel or @user" })
|
|
3758
3886
|
.positional("id", { type: "string", demandOption: true, describe: "Scheduled message ID" })
|
|
3759
3887
|
.option("code", { type: "string", describe: "Safety hash to confirm" })
|
|
3760
|
-
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
3888
|
+
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
3889
|
+
.option("as-bot", { type: "boolean", default: false, describe: "Cancel a message scheduled with `schedule send --as-bot`. It belongs to the bot token, so the user token cannot delete it." }),
|
|
3761
3890
|
async (argv) => {
|
|
3762
3891
|
const args: ScheduleRmArgs = { target: argv.target!, id: argv.id! };
|
|
3763
3892
|
if (argv.code) args.code = argv.code;
|
|
3893
|
+
if (argv["channel-id"]) {
|
|
3894
|
+
args.channelId = argv["channel-id"];
|
|
3895
|
+
args.targetOverride = `--channel-id ${argv["channel-id"]}`;
|
|
3896
|
+
}
|
|
3897
|
+
if (argv["as-bot"]) {
|
|
3898
|
+
const botToken = requireBotToken();
|
|
3899
|
+
if (!args.channelId && args.target.startsWith("@")) {
|
|
3900
|
+
args.channelId = await botDmForTarget(args.target, botToken, tok(argv as W), ck(argv as W));
|
|
3901
|
+
}
|
|
3902
|
+
args.asBot = true;
|
|
3903
|
+
await cmdScheduleRm(botToken, args);
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3764
3906
|
const cookie = ck(argv as W);
|
|
3765
3907
|
if (cookie) args.cookie = cookie;
|
|
3766
|
-
if (argv["channel-id"]) args.channelId = argv["channel-id"];
|
|
3767
3908
|
await cmdScheduleRm(tok(argv as W), args);
|
|
3768
3909
|
},
|
|
3769
3910
|
)
|