slack-term 1.32.2 → 1.32.3
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 +108 -105
- package/package.json +1 -1
- package/ts/cli.ts +106 -11
package/package.json
CHANGED
package/ts/cli.ts
CHANGED
|
@@ -1168,7 +1168,62 @@ interface EditArgs {
|
|
|
1168
1168
|
// Session cookie (xoxd) for `token` — required for an xoxc- desktop session
|
|
1169
1169
|
// token to be accepted by the public Slack API at all.
|
|
1170
1170
|
cookie?: string;
|
|
1171
|
+
asBot?: boolean;
|
|
1172
|
+
/** The USER token, kept for mention resolution when the edit itself runs as
|
|
1173
|
+
* the bot. A bot token has no users:read, and an unresolved @handle degrades
|
|
1174
|
+
* to plain text SILENTLY — which is the precise failure `edit` exists to
|
|
1175
|
+
* correct, so re-introducing it here would be circular. Same split as
|
|
1176
|
+
* `send --as-bot`. */
|
|
1177
|
+
mentionToken?: string;
|
|
1178
|
+
mentionCookie?: string;
|
|
1179
|
+
}
|
|
1180
|
+
/** The bot token, plus the bot↔user DM when the target names an `@handle`.
|
|
1181
|
+
*
|
|
1182
|
+
* Both halves are load-bearing. A bot token has no `users:read`, so resolving
|
|
1183
|
+
* `@bob` with it does not merely pick the wrong channel — it fails outright with
|
|
1184
|
+
* `missing_scope`, before the confirm gate is ever printed. And even where it
|
|
1185
|
+
* could resolve, a BOT-authored DM lives in the bot's conversation with that
|
|
1186
|
+
* person, not in the user's: looking the handle up as the user and opening the
|
|
1187
|
+
* DM as the bot is the only combination that names the message being corrected.
|
|
1188
|
+
* Same split `send --as-bot` makes.
|
|
1189
|
+
*
|
|
1190
|
+
* Found by the cross-vendor review of this change, which also named why the
|
|
1191
|
+
* first round of tests missed it: they all targeted a permalink, and
|
|
1192
|
+
* `resolveChannel` parses those locally without touching the API. */
|
|
1193
|
+
async function asBotTarget(
|
|
1194
|
+
target: string,
|
|
1195
|
+
userToken: string,
|
|
1196
|
+
userCookie: string | undefined,
|
|
1197
|
+
/** Already known from `--channel-id`. Passed in so the DM lookup is SKIPPED
|
|
1198
|
+
* rather than performed and discarded: `conversations.open` is a side effect,
|
|
1199
|
+
* and opening a DM the caller did not ask for is not undone by ignoring the
|
|
1200
|
+
* result. */
|
|
1201
|
+
knownChannelId?: string,
|
|
1202
|
+
): Promise<{ botToken: string; channelId?: string }> {
|
|
1203
|
+
const botToken = resolveBotToken();
|
|
1204
|
+
if (!botToken) {
|
|
1205
|
+
console.error(
|
|
1206
|
+
"Error: --as-bot needs a bot token, but no xoxb- token was found.\n" +
|
|
1207
|
+
" Set SLACK_BOT_TOKEN=xoxb-... in ~/.config/slack-cli/.env (or your shell).",
|
|
1208
|
+
);
|
|
1209
|
+
process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
if (knownChannelId) return { botToken };
|
|
1212
|
+
const { ref } = splitRefTs(target);
|
|
1213
|
+
if (!ref.startsWith("@")) return { botToken };
|
|
1214
|
+
try {
|
|
1215
|
+
const userId = await resolveUserId(userToken, ref, userCookie);
|
|
1216
|
+
return { botToken, channelId: await openDm(botToken, userId) };
|
|
1217
|
+
} catch (e: unknown) {
|
|
1218
|
+
console.error(
|
|
1219
|
+
`Error: could not resolve ${stripTerminalControls(ref)} to the bot's DM.\n` +
|
|
1220
|
+
` ${e instanceof Error ? e.message : String(e)}\n` +
|
|
1221
|
+
` Tip: pass the message permalink, or --channel-id <D…>, to skip the lookup.`,
|
|
1222
|
+
);
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1171
1225
|
}
|
|
1226
|
+
|
|
1172
1227
|
async function cmdEdit(token: string, args: EditArgs): Promise<void> {
|
|
1173
1228
|
const getSelf = selfLookup(token, args.cookie);
|
|
1174
1229
|
const { ref, ts } = splitRefTs(args.target);
|
|
@@ -1192,8 +1247,9 @@ async function cmdEdit(token: string, args: EditArgs): Promise<void> {
|
|
|
1192
1247
|
const originalText = typeof original.text === "string" ? original.text : "";
|
|
1193
1248
|
|
|
1194
1249
|
// Convert @handle → <@USERID> before hashing/editing (unresolved stay as text).
|
|
1250
|
+
const mentionCookie = args.mentionCookie ?? args.cookie;
|
|
1195
1251
|
const newText = args.mentions
|
|
1196
|
-
? await encodeMentions(token, args.newText, channelId,
|
|
1252
|
+
? await encodeMentions(args.mentionToken ?? token, args.newText, channelId, mentionCookie ? { cookie: mentionCookie } : {})
|
|
1197
1253
|
: args.newText;
|
|
1198
1254
|
|
|
1199
1255
|
// Identity is part of the hash for the same reason it is on `send`: a code
|
|
@@ -1205,7 +1261,7 @@ async function cmdEdit(token: string, args: EditArgs): Promise<void> {
|
|
|
1205
1261
|
if (args.code !== code) {
|
|
1206
1262
|
requireCode(args.code, code, [
|
|
1207
1263
|
`--- Editing as -------------------------------`,
|
|
1208
|
-
fromLine(self),
|
|
1264
|
+
fromLine(self, { asBot: args.asBot }),
|
|
1209
1265
|
`--- Original message -------------------------`,
|
|
1210
1266
|
...originalText.split("\n").map((l) => ` ${l}`),
|
|
1211
1267
|
`--- Replacing with ---------------------------`,
|
|
@@ -1224,6 +1280,7 @@ interface DeleteArgs {
|
|
|
1224
1280
|
code?: string;
|
|
1225
1281
|
channelId?: string;
|
|
1226
1282
|
cookie?: string;
|
|
1283
|
+
asBot?: boolean;
|
|
1227
1284
|
}
|
|
1228
1285
|
async function cmdDelete(token: string, args: DeleteArgs): Promise<void> {
|
|
1229
1286
|
const getSelf = selfLookup(token, args.cookie);
|
|
@@ -1255,7 +1312,7 @@ async function cmdDelete(token: string, args: DeleteArgs): Promise<void> {
|
|
|
1255
1312
|
const dest = await destLabel(token, channelId, ref, args.cookie);
|
|
1256
1313
|
requireCode(args.code, code, [
|
|
1257
1314
|
`--- Deleting message -------------------------`,
|
|
1258
|
-
fromLine(self),
|
|
1315
|
+
fromLine(self, { asBot: args.asBot }),
|
|
1259
1316
|
` → ${dest} at ${slackTsToIso(ts)}`,
|
|
1260
1317
|
...originalText.split("\n").map((l) => ` ${l}`),
|
|
1261
1318
|
`---------------------------------------------`,
|
|
@@ -4021,15 +4078,39 @@ async function main(): Promise<void> {
|
|
|
4021
4078
|
.positional("newText", { type: "string", demandOption: true })
|
|
4022
4079
|
.option("code", { type: "string", describe: "Safety hash to confirm edit" })
|
|
4023
4080
|
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
4024
|
-
.option("mentions", { type: "boolean", default: true, describe: "Convert @handle tokens in the new text to real <@USERID> mentions (on by default). Unresolved handles stay as plain text. Disable with --no-mentions for literal @text." })
|
|
4081
|
+
.option("mentions", { type: "boolean", default: true, describe: "Convert @handle tokens in the new text to real <@USERID> mentions (on by default). Unresolved handles stay as plain text. Disable with --no-mentions for literal @text." })
|
|
4082
|
+
.option("as-bot", { type: "boolean", default: false, describe: "Edit as the bot (xoxb / SLACK_BOT_TOKEN). REQUIRED to correct a message the bot posted: Slack only lets a token edit its OWN messages, so a bot-authored message is uneditable by the user token and chat.update returns cant_update_message. Address it by permalink or --channel-id; an @user target resolves against the BOT's DM list, which is where a bot-authored DM actually lives." }),
|
|
4025
4083
|
async (argv) => {
|
|
4026
4084
|
const args: EditArgs = { target: argv.target!, newText: argv.newText! };
|
|
4027
4085
|
if (argv.code) args.code = argv.code;
|
|
4028
4086
|
if (argv["channel-id"]) args.channelId = argv["channel-id"];
|
|
4029
4087
|
if (argv.mentions !== false) args.mentions = true;
|
|
4030
|
-
|
|
4031
|
-
if (
|
|
4032
|
-
|
|
4088
|
+
let editToken: string;
|
|
4089
|
+
if (argv["as-bot"]) {
|
|
4090
|
+
// --as-bot is not a switch on chat.update. It selects a DIFFERENT
|
|
4091
|
+
// resolved token, and until now `edit` never wired one — so every
|
|
4092
|
+
// message the bot posted was uncorrectable, and the workaround people
|
|
4093
|
+
// reached for was RESENDING, which this repo's own etiquette forbids.
|
|
4094
|
+
// Bot identity is what makes a DM notify at all, so "uncorrectable"
|
|
4095
|
+
// applied to exactly the messages that matter most.
|
|
4096
|
+
const resolved = await asBotTarget(args.target, tok(argv as W), ck(argv as W), args.channelId);
|
|
4097
|
+
editToken = resolved.botToken;
|
|
4098
|
+
if (resolved.channelId && !args.channelId) args.channelId = resolved.channelId;
|
|
4099
|
+
args.asBot = true;
|
|
4100
|
+
// Mentions still resolve on the USER token: the bot has no users:read,
|
|
4101
|
+
// and a silently unresolved @handle in a CORRECTION is the same defect
|
|
4102
|
+
// twice.
|
|
4103
|
+
args.mentionToken = tok(argv as W);
|
|
4104
|
+
const mc = ck(argv as W);
|
|
4105
|
+
if (mc) args.mentionCookie = mc;
|
|
4106
|
+
} else {
|
|
4107
|
+
editToken = tok(argv as W);
|
|
4108
|
+
// Not set under --as-bot: a bot token needs no session cookie, and
|
|
4109
|
+
// attaching one is how a working call starts failing.
|
|
4110
|
+
const cookie = ck(argv as W);
|
|
4111
|
+
if (cookie) args.cookie = cookie;
|
|
4112
|
+
}
|
|
4113
|
+
await cmdEdit(editToken, args);
|
|
4033
4114
|
},
|
|
4034
4115
|
)
|
|
4035
4116
|
.command(
|
|
@@ -4038,14 +4119,28 @@ async function main(): Promise<void> {
|
|
|
4038
4119
|
(y) => y
|
|
4039
4120
|
.positional("target", { type: "string", demandOption: true, describe: "#chan:ts, @user:ts, or permalink" })
|
|
4040
4121
|
.option("code", { type: "string", describe: "Safety hash to confirm delete" })
|
|
4041
|
-
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
4122
|
+
.option("channel-id", { type: "string", describe: "Raw channel ID" })
|
|
4123
|
+
.option("as-bot", { type: "boolean", default: false, describe: "Delete as the bot (xoxb / SLACK_BOT_TOKEN). REQUIRED for a message the bot posted: chat.delete refuses another token's message exactly as chat.update does." }),
|
|
4042
4124
|
async (argv) => {
|
|
4043
4125
|
const args: DeleteArgs = { target: argv.target! };
|
|
4044
4126
|
if (argv.code) args.code = argv.code;
|
|
4045
4127
|
if (argv["channel-id"]) args.channelId = argv["channel-id"];
|
|
4046
|
-
|
|
4047
|
-
if (
|
|
4048
|
-
|
|
4128
|
+
let deleteToken: string;
|
|
4129
|
+
if (argv["as-bot"]) {
|
|
4130
|
+
// The SECOND call site of one defect, not a second defect: `delete`
|
|
4131
|
+
// had the identical unconditional `tok(argv)`, and chat.delete refuses
|
|
4132
|
+
// another token's message exactly as chat.update does. Fixing one and
|
|
4133
|
+
// leaving the other is how the next person finds it the hard way.
|
|
4134
|
+
const resolved = await asBotTarget(args.target, tok(argv as W), ck(argv as W), args.channelId);
|
|
4135
|
+
deleteToken = resolved.botToken;
|
|
4136
|
+
if (resolved.channelId && !args.channelId) args.channelId = resolved.channelId;
|
|
4137
|
+
args.asBot = true;
|
|
4138
|
+
} else {
|
|
4139
|
+
deleteToken = tok(argv as W);
|
|
4140
|
+
const cookie = ck(argv as W);
|
|
4141
|
+
if (cookie) args.cookie = cookie;
|
|
4142
|
+
}
|
|
4143
|
+
await cmdDelete(deleteToken, args);
|
|
4049
4144
|
},
|
|
4050
4145
|
)
|
|
4051
4146
|
.command(
|