This commit is contained in:
Evgeny @ SimpleX Chat
2026-09-25 10:55:41 +00:00
parent d2c3162453
commit 9f0ed3ac65
5 changed files with 31 additions and 10 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ Each user who connects via the bot's business address receives a calculator mess
- `%` is computed as in Apple's calculator: `100 + 15 % =` equals 115.
- Tap `C` to clear the number, and `C` again to clear the calculation.
- Numbers are shown without exponent, up to 15 digits; larger results are shown as `Error`.
- You can also send numbers, expressions like `(2 + 3) × 4`, which are entered as one number, and keys like `+`, `=`, `add` or `c`.
- You can also send numbers and keys like `+`, `=`, `add` or `c`. Expressions like `(2 + 3) × 4` are entered as one number, and the bot replies to them with the result.
- The bot deletes tapped keys and sends each calculation after `=`.
- After 10 minutes without input, the bot turns the calculator off and discards the calculation; tap any key to turn it on.
- The bot sends keys like `/+` to apps with chat protocol version 21 or later, and keys like `/add` to older apps.
+13 -1
View File
@@ -6,7 +6,7 @@ type Input = {tap: string} | {text: string}
function run(inputs: Input[]): {calc: Calc, logLines: string[]} {
const logLines: string[] = []
const calc = inputs.reduce((current, input) => {
const [next, logLine] = "tap" in input ? press(current, input.tap as Key) : textInput(input.text)!(current)
const [next, logLine] = "tap" in input ? press(current, input.tap as Key) : textInput(input.text)!.update(current)
if (logLine) logLines.push(logLine)
return next
}, initialCalc)
@@ -142,6 +142,18 @@ describe("typed messages", () => {
expect(type("10", "+", "25=").calc.display).toBe("35")
})
test("result only for expressions", () => {
const result = (text: string) => textInput(text)?.result
expect(result("(2 + 3) × 4")).toBe("20")
expect(result("(2 + 3) × 4 =")).toBe("20")
expect(result("15%")).toBe("0.15")
expect(result("1 / 0")).toBe("Error")
expect(result("25")).toBeUndefined()
expect(result("-5")).toBeUndefined()
expect(result("25=")).toBeUndefined()
expect(result("+")).toBeUndefined()
})
test("overflow in typed numbers", () => {
expect(type("1234567890123456").calc.display).toBe("Error")
expect(type("123456789012345").calc.display).toBe("123456789012345")
@@ -31,6 +31,9 @@ const calculatorShows = (display: string): ItemCheck => ci => ci.chatItem.meta.i
const hasText = (text: string): ItemCheck => ci => ci.chatItem.meta.itemText === text
const repliesTo = (quoted: string, text: string): ItemCheck => ({chatItem}) =>
chatItem.meta.itemText === text && chatItem.quotedItem?.content.text === quoted
test("calculator in business chat", async () => {
const dir = mkdtempSync(join(tmpdir(), "calculator-bot-"))
const botDbOpts: bot.BotDbOpts = {type: "sqlite", filePrefix: join(dir, "bot")}
@@ -54,7 +57,7 @@ test("calculator in business chat", async () => {
for (const event of received) expect(await event).toBeDefined()
}
await send(["/2", "/+", "/2", "/="], hasText("2 + 2 = 4"), calculatorShows("4"))
await send(["2 × (3 + 4) - 1"], calculatorShows("13"))
await send(["2 × (3 + 4) - 1"], repliesTo("2 × (3 + 4) - 1", "= 13"), calculatorShows("13"))
await send(["25", "+", "25", "="], hasText("25 + 25 = 50"), calculatorShows("50"))
} finally {
await alice.close()
@@ -157,17 +157,19 @@ function format(n: number): string {
return trimmed === "-0" ? "0" : trimmed
}
export function textInput(text: string): Update | undefined {
export function textInput(text: string): {update: Update, result?: string} | undefined {
const input = text.replace(/\s/g, "")
const n = expressionValue(input.replace(/=$/, ""))
const entry = input.replace(/=$/, "")
const n = expressionValue(entry)
if (n !== undefined) {
return calc => {
const update: Update = calc => {
const entered = showNumber(calc, n)
return input.endsWith("=") ? press(entered, "=") : [entered]
}
return {update, result: Number.isNaN(Number(entry)) ? format(n) : undefined}
}
const key = keyNames.get(input.toLowerCase())
return key ? calc => press(calc, key) : undefined
return key ? {update: calc => press(calc, key)} : undefined
}
const tokenPattern = /\d+(?:\.\d*)?|\.\d+|[-+−×x*÷\/%()]/g
@@ -68,9 +68,13 @@ const keyCommands = Object.fromEntries([...keyNames].map(([name, key]) => [name,
async function onMessage(ci: T.AChatItem, content: T.MsgContent, chat: api.ChatApi): Promise<void> {
const sender = groupSender(ci)
if (!sender || content.type !== "text") return
const update = textInput(content.text)
if (update) await updateCalculator(chat, sender, update)
else await chat.apiSendTextReply(ci, hint)
const input = textInput(content.text)
if (!input) {
await chat.apiSendTextReply(ci, hint)
return
}
if (input.result) await chat.apiSendTextReply(ci, `= ${input.result}`)
await updateCalculator(chat, sender, input.update)
}
export function runCalculatorBot(dbOpts: bot.BotDbOpts): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> {