create-gramstax 0.13.1 → 0.14.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.
@@ -1,5 +1,5 @@
1
1
  import {describe, it, expect, beforeEach} from "bun:test"
2
- import {e2e} from "./setup"
2
+ import {e2e} from "../setup"
3
3
 
4
4
  const intent = `start`
5
5
  const asUser = {user: {id: 2001, username: `testuser`}, chat: {id: 2001}}
@@ -0,0 +1,28 @@
1
+ import {describe, it, expect, beforeEach} from "bun:test"
2
+ import {e2e} from "../../setup"
3
+
4
+ const intent = `start/random`
5
+ const asUser = {user: {id: 9001, username: `testuser`}, chat: {id: 9001}}
6
+
7
+ beforeEach(() => {
8
+ e2e.clearCalls()
9
+ })
10
+
11
+ describe(`E2E: StartRandomPage (hub)`, () => {
12
+ it(`show hub menu when navigating from /start`, async () => {
13
+ await e2e.sendText(`/start`, asUser)
14
+ e2e.clearCalls()
15
+
16
+ const responses = await e2e.sendCallbackQuery(intent, asUser)
17
+ const responsesId = await e2e.sendCallbackQuery(intent, {user: {id: 9002, username: `pengguna`, language_code: `id`}, chat: {id: 9002}})
18
+
19
+ expect(responses.length).toBeGreaterThan(0)
20
+ expect(responsesId.length).toBeGreaterThan(0)
21
+ const edit = responses[0]!
22
+ const editId = responsesId[0]!
23
+ expect(edit.body?.rich_message.html).toContain(`Random Generator`)
24
+ expect(editId.body?.rich_message.html).toContain(`Generator Acak`)
25
+ expect(e2e.extractButtonLabels(edit)).toEqual(e2e.gkb(intent, `default`, `inline`, {}))
26
+ expect(e2e.extractButtonLabels(editId)).toEqual(e2e.gkb(intent, `default`, `inline`, {}, `id`))
27
+ })
28
+ })
@@ -1,20 +1,23 @@
1
1
  import {describe, it, expect, beforeEach} from "bun:test"
2
- import {e2e} from "./setup"
2
+ import {e2e} from "../../setup"
3
3
 
4
- const intent = `random-number`
5
- const asUser = {user: {id: 2003, username: `testuser`}, chat: {id: 2003}}
4
+ const intent = `start/random/number`
5
+ const asUser = {user: {id: 8001, username: `testuser`}, chat: {id: 8001}}
6
6
 
7
7
  beforeEach(() => {
8
8
  e2e.clearCalls()
9
9
  })
10
10
 
11
- describe(`E2E: RandomNumberPage`, () => {
12
- it(`show generated random number when navigating from /start`, async () => {
11
+ describe(`E2E: StartRandomNumberPage`, () => {
12
+ it(`show generated random number when navigating from hub`, async () => {
13
13
  await e2e.sendText(`/start`, asUser)
14
14
  e2e.clearCalls()
15
15
 
16
+ await e2e.sendCallbackQuery(`start/random`, asUser)
17
+ e2e.clearCalls()
18
+
16
19
  const responses = await e2e.sendCallbackQuery(intent, asUser)
17
- const responsesId = await e2e.sendCallbackQuery(intent, {user: {id: 6001, username: `pengguna`, language_code: `id`}, chat: {id: 6001}})
20
+ const responsesId = await e2e.sendCallbackQuery(intent, {user: {id: 8002, username: `pengguna`, language_code: `id`}, chat: {id: 8002}})
18
21
 
19
22
  expect(responses.length).toBeGreaterThan(0)
20
23
  expect(responsesId.length).toBeGreaterThan(0)
@@ -0,0 +1,31 @@
1
+ import {describe, it, expect, beforeEach} from "bun:test"
2
+ import {e2e} from "../../setup"
3
+
4
+ const intent = `start/random/text`
5
+ const asUser = {user: {id: 10001, username: `testuser`}, chat: {id: 10001}}
6
+
7
+ beforeEach(() => {
8
+ e2e.clearCalls()
9
+ })
10
+
11
+ describe(`E2E: StartRandomTextPage`, () => {
12
+ it(`show random text when navigating from hub`, async () => {
13
+ await e2e.sendText(`/start`, asUser)
14
+ e2e.clearCalls()
15
+
16
+ await e2e.sendCallbackQuery(`start/random`, asUser)
17
+ e2e.clearCalls()
18
+
19
+ const responses = await e2e.sendCallbackQuery(intent, asUser)
20
+ const responsesId = await e2e.sendCallbackQuery(intent, {user: {id: 10002, username: `pengguna`, language_code: `id`}, chat: {id: 10002}})
21
+
22
+ expect(responses.length).toBeGreaterThan(0)
23
+ expect(responsesId.length).toBeGreaterThan(0)
24
+ const edit = responses[0]!
25
+ const editId = responsesId[0]!
26
+ expect(edit.body?.text).toContain(`Random Text`)
27
+ expect(editId.body?.text).toContain(`Teks Acak`)
28
+ expect(e2e.extractButtonLabels(edit)).toEqual(e2e.gkb(intent, `default`, `inline`, {}))
29
+ expect(e2e.extractButtonLabels(editId)).toEqual(e2e.gkb(intent, `default`, `inline`, {}, `id`))
30
+ })
31
+ })
@@ -1,14 +1,14 @@
1
1
  import {describe, it, expect, beforeEach} from "bun:test"
2
2
  import {e2e} from "./setup"
3
3
 
4
- const intent = `general-status`
4
+ const intent = `status`
5
5
  const asUser = {user: {id: 8001, username: `testuser`}, chat: {id: 8001}}
6
6
 
7
7
  beforeEach(() => {
8
8
  e2e.clearCalls()
9
9
  })
10
10
 
11
- describe(`E2E: GeneralStatusPage`, () => {
11
+ describe(`E2E: StatusPage`, () => {
12
12
  it(`show 'Route not found' for unrecognized callback query`, async () => {
13
13
  await e2e.sendText(`/start`, asUser)
14
14
  e2e.clearCalls()
@@ -38,8 +38,8 @@
38
38
  "test:all": "bun test test/ e2e/"
39
39
  },
40
40
  "dependencies": {
41
- "@gramstax/lmdb": "^0.13.0",
42
- "gramstax": "^0.13.0",
41
+ "@gramstax/lmdb": "^0.13.1",
42
+ "gramstax": "^0.13.1",
43
43
  "dayjs": "^1.11.13"
44
44
  },
45
45
  "peerDependencies": {
@@ -0,0 +1,148 @@
1
+ /**
2
+ * check-e2e-mirror.ts
3
+ *
4
+ * Ensures the e2e/ directory structure mirrors src/pages exactly.
5
+ *
6
+ * Convention:
7
+ * src/pages/foo.ts → e2e/foo.test.ts (same relative path, same name)
8
+ * src/pages/foo/bar.ts → e2e/foo/bar.test.ts (same relative path)
9
+ * src/pages/foo/index.ts → e2e/foo/index.test.ts (same relative path)
10
+ *
11
+ * Usage:
12
+ * bun run scripts/check-e2e-mirror.ts
13
+ * → Check this project (src/pages vs e2e)
14
+ *
15
+ * bun run scripts/check-e2e-mirror.ts --path ./my-app
16
+ * → Check another project (pages: ./my-app/src/pages, e2e: ./my-app/e2e)
17
+ *
18
+ * bun run scripts/check-e2e-mirror.ts --path ./my-app --fix
19
+ * → Check + auto-create missing e2e test files
20
+ *
21
+ * Exit code: 0 if all OK, 1 if any mismatch.
22
+ */
23
+
24
+ import {existsSync, readdirSync, mkdirSync, writeFileSync} from "node:fs"
25
+ import {join, dirname} from "node:path"
26
+
27
+ // ── Parse CLI ─────────────────────────────────────────────────────────
28
+
29
+ const args = process.argv.slice(2)
30
+
31
+ const FIX_MODE = args.includes(`--fix`)
32
+
33
+ const pathIndex = args.indexOf(`--path`)
34
+ const customPath = pathIndex !== -1 && pathIndex + 1 < args.length ? args[pathIndex + 1]! : null
35
+
36
+ // ── Helpers ───────────────────────────────────────────────────────────
37
+
38
+ function walkPages(dir: string, prefix = ``): Array<{rel: string}> {
39
+ const results: Array<{rel: string}> = []
40
+ for (const entry of readdirSync(dir, {withFileTypes: true})) {
41
+ const full = join(dir, entry.name)
42
+ if (entry.isDirectory()) {
43
+ results.push(...walkPages(full, prefix ? `${prefix}/${entry.name}` : entry.name))
44
+ } else if (entry.isFile() && /\.(ts|js)$/.test(entry.name) && !entry.name.endsWith(`.test`)) {
45
+ results.push({rel: prefix ? `${prefix}/${entry.name}` : entry.name})
46
+ }
47
+ }
48
+ return results
49
+ }
50
+
51
+ function getTestExt(rel: string): string {
52
+ return rel.endsWith(`.js`) ? `.test.js` : `.test.ts`
53
+ }
54
+
55
+ function getSetupImportPrefix(rel: string): string {
56
+ const depth = rel.split(`/`).length - 1
57
+ if (depth === 0) return `./`
58
+ let prefix = ``
59
+ for (let i = 0; i < depth; i++) prefix += `../`
60
+ return prefix
61
+ }
62
+
63
+ function generateTestContent(pageRel: string): string {
64
+ const baseName = pageRel.replace(/\.(ts|js)$/, ``)
65
+ const parts = baseName.split(`/`)
66
+ const pageName = parts[parts.length - 1] || parts[parts.length - 2] || `Page`
67
+ const className = pageName
68
+ .split(/[-_]/)
69
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
70
+ .join(``) + `Page`
71
+
72
+ return [
73
+ `import {describe, it, expect} from "bun:test"`,
74
+ `import {bot} from "./setup"`,
75
+ ``,
76
+ `describe(\`E2E: ${className}\`, () => {`,
77
+ ` it(\`load as a registered page in the bot\`, () => {`,
78
+ ` const allPages = bot.pages.all`,
79
+ ` expect(allPages[\`${baseName}\`]).toBeDefined()`,
80
+ ` })`,
81
+ `})`,
82
+ ``
83
+ ].join(`\n`)
84
+ }
85
+
86
+ function checkProject(label: string, pagesDir: string, e2eDir: string): boolean {
87
+ if (!existsSync(pagesDir)) {
88
+ console.log(` ⏭️ ${label}: pages directory not found at ${pagesDir}`)
89
+ return true
90
+ }
91
+ if (!existsSync(e2eDir)) {
92
+ console.log(` ⏭️ ${label}: e2e directory not found at ${e2eDir}`)
93
+ return true
94
+ }
95
+
96
+ const pages = walkPages(pagesDir)
97
+ let ok = true
98
+
99
+ for (const page of pages) {
100
+ const ext = getTestExt(page.rel)
101
+ const expectedE2E = page.rel.replace(/\.(ts|js)$/, ``) + ext
102
+ const e2ePath = join(e2eDir, expectedE2E)
103
+
104
+ if (!existsSync(e2ePath)) {
105
+ console.log(` ❌ ${label}: pages/${page.rel} -> e2e/${expectedE2E} MISSING`)
106
+ ok = false
107
+
108
+ if (FIX_MODE) {
109
+ const content = generateTestContent(page.rel)
110
+ .replace(`"./setup"`, `"${getSetupImportPrefix(expectedE2E)}setup"`)
111
+ mkdirSync(dirname(e2ePath), {recursive: true})
112
+ writeFileSync(e2ePath, content)
113
+ console.log(` ✅ Created e2e/${expectedE2E}`)
114
+ }
115
+ }
116
+ }
117
+
118
+ if (ok) {
119
+ console.log(` ✅ ${label}: ${pages.length} pages, all mirrored`)
120
+ }
121
+ return ok
122
+ }
123
+
124
+ // ── Main ──────────────────────────────────────────────────────────────
125
+
126
+ let allOk = true
127
+
128
+ if (customPath) {
129
+ const label = customPath.replace(/\\/g, `/`).replace(/\/+$/, ``)
130
+ const pagesDir = join(customPath, `src`, `pages`)
131
+ const e2eDir = join(customPath, `e2e`)
132
+ const ok = checkProject(label, pagesDir, e2eDir)
133
+ if (!ok) allOk = false
134
+ } else {
135
+ // Default: check this project's own pages and e2e
136
+ const pagesDir = join(import.meta.dir, `../src/pages`)
137
+ const e2eDir = join(import.meta.dir, `..`)
138
+ const ok = checkProject(`this project`, pagesDir, e2eDir)
139
+ if (!ok) allOk = false
140
+ }
141
+
142
+ if (!allOk && !FIX_MODE) {
143
+ console.log(`\nSome e2e tests are missing. Run with --fix to auto-create them.`)
144
+ process.exit(1)
145
+ } else if (allOk) {
146
+ console.log(`\nAll OK!`)
147
+ process.exit(0)
148
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * generate-e2e-structure.ts
3
+ *
4
+ * Auto-create e2e test files for every page file that does not yet have
5
+ * a matching e2e test. The e2e structure mirrors src/pages exactly.
6
+ *
7
+ * Convention:
8
+ * src/pages/foo.ts → e2e/foo.test.ts (same relative path)
9
+ * src/pages/foo/bar.ts → e2e/foo/bar.test.ts (same relative path)
10
+ * src/pages/foo/index.ts → e2e/foo/index.test.ts (same relative path)
11
+ *
12
+ * Each generated e2e test contains:
13
+ * - Import setup with the correct relative path
14
+ * - Basic test: "load as a registered page in the bot"
15
+ * - Class name derived from the file name (PascalCase)
16
+ *
17
+ * Usage:
18
+ * bun run scripts/generate-e2e-structure.ts
19
+ * → Generate for this project (src/pages → e2e)
20
+ *
21
+ * bun run scripts/generate-e2e-structure.ts --path ./my-app
22
+ * → Generate for another project
23
+ *
24
+ * bun run scripts/generate-e2e-structure.ts --path ./my-app --dry-run
25
+ * → Preview what would be created without writing files
26
+ *
27
+ * bun run scripts/generate-e2e-structure.ts --path ./my-app --force
28
+ * → Overwrite existing e2e tests
29
+ *
30
+ * bun run scripts/generate-e2e-structure.ts --path ./my-app --check
31
+ * → Only check, no file creation
32
+ *
33
+ * Exit code: 0 if OK, 1 if any errors.
34
+ */
35
+
36
+ import {existsSync, readdirSync, mkdirSync, writeFileSync} from "node:fs"
37
+ import {join, dirname} from "node:path"
38
+
39
+ // ── Parse CLI ─────────────────────────────────────────────────────────
40
+
41
+ const args = process.argv.slice(2)
42
+
43
+ const DRY_RUN = args.includes(`--dry-run`)
44
+ const FORCE = args.includes(`--force`)
45
+ const CHECK_ONLY = args.includes(`--check`)
46
+
47
+ const pathIndex = args.indexOf(`--path`)
48
+ const customPath = pathIndex !== -1 && pathIndex + 1 < args.length ? args[pathIndex + 1]! : null
49
+
50
+ // ── Helpers ───────────────────────────────────────────────────────────
51
+
52
+ /** Walk directory recursively, return relative file paths of all pages */
53
+ function walkFiles(dir: string, prefix = ``): string[] {
54
+ const results: string[] = []
55
+ for (const entry of readdirSync(dir, {withFileTypes: true})) {
56
+ const full = join(dir, entry.name)
57
+ if (entry.isDirectory()) {
58
+ results.push(...walkFiles(full, prefix ? `${prefix}/${entry.name}` : entry.name))
59
+ } else if (entry.isFile() && /\.(ts|js)$/.test(entry.name) && !entry.name.endsWith(`.test`)) {
60
+ results.push(prefix ? `${prefix}/${entry.name}` : entry.name)
61
+ }
62
+ }
63
+ return results
64
+ }
65
+
66
+ /** Determine test file extension based on page file extension */
67
+ function testExt(rel: string): string {
68
+ return rel.endsWith(`.js`) ? `.test.js` : `.test.ts`
69
+ }
70
+
71
+ /** Build correct relative import path from a test file to setup */
72
+ function setupImport(rel: string): string {
73
+ const depth = rel.split(`/`).length - 1
74
+ if (depth === 0) return `./`
75
+ let prefix = ``
76
+ for (let i = 0; i < depth; i++) prefix += `../`
77
+ return prefix
78
+ }
79
+
80
+ /** Convert a filename segment to PascalCase */
81
+ function toClassName(name: string): string {
82
+ return name
83
+ .split(/[-_/]/)
84
+ .filter(Boolean)
85
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
86
+ .join(` `)
87
+ .replace(/\s+/g, ``)
88
+ }
89
+
90
+ /** Generate the content for an e2e test file */
91
+ function generateTestContent(pageRel: string): string {
92
+ const baseName = pageRel.replace(/\.(ts|js)$/, ``)
93
+ const parts = baseName.split(`/`)
94
+ const fileName = parts[parts.length - 1] || `index`
95
+ const className = toClassName(fileName) + `Page`
96
+ const importPath = setupImport(baseName + testExt(pageRel))
97
+
98
+ return [
99
+ `import {describe, it, expect} from "bun:test"`,
100
+ `import {bot} from "${importPath}setup"`,
101
+ ``,
102
+ `describe(\`E2E: ${className}\`, () => {`,
103
+ ` it(\`load as a registered page in the bot\`, () => {`,
104
+ ` const allPages = bot.pages.all`,
105
+ ` expect(allPages[\`${baseName}\`]).toBeDefined()`,
106
+ ` })`,
107
+ `})`,
108
+ ``
109
+ ].join(`\n`)
110
+ }
111
+
112
+ // ── Process one project ───────────────────────────────────────────────
113
+
114
+ function processProject(
115
+ label: string,
116
+ pagesDir: string,
117
+ e2eDir: string
118
+ ): {created: number; skipped: number; errors: number} {
119
+ const result = {created: 0, skipped: 0, errors: 0}
120
+
121
+ if (!existsSync(pagesDir)) {
122
+ console.log(` ⏭️ ${label}: pages directory not found at ${pagesDir}`)
123
+ result.errors++
124
+ return result
125
+ }
126
+
127
+ if (!existsSync(e2eDir)) {
128
+ if (CHECK_ONLY) {
129
+ console.log(` ⏭️ ${label}: e2e directory not found at ${e2eDir}`)
130
+ result.errors++
131
+ return result
132
+ }
133
+ if (!DRY_RUN) {
134
+ mkdirSync(e2eDir, {recursive: true})
135
+ console.log(` 📁 ${label}: created e2e directory`)
136
+ } else {
137
+ console.log(` 📁 ${label}: would create e2e directory`)
138
+ }
139
+ }
140
+
141
+ const pageFiles = walkFiles(pagesDir)
142
+
143
+ for (const pageRel of pageFiles) {
144
+ const ext = testExt(pageRel)
145
+ const expectedE2E = pageRel.replace(/\.(ts|js)$/, ``) + ext
146
+ const e2ePath = join(e2eDir, expectedE2E)
147
+
148
+ if (existsSync(e2ePath) && !FORCE) {
149
+ result.skipped++
150
+ continue
151
+ }
152
+
153
+ if (CHECK_ONLY) {
154
+ console.log(` ❌ ${label}: e2e/${expectedE2E} (for pages/${pageRel})`)
155
+ continue
156
+ }
157
+
158
+ const content = generateTestContent(pageRel)
159
+
160
+ if (DRY_RUN) {
161
+ console.log(` 📝 ${label}: would create e2e/${expectedE2E}`)
162
+ console.log(content.split(`\n`).map((l) => ` | ${l}`).join(`\n`))
163
+ console.log(``)
164
+ result.created++
165
+ continue
166
+ }
167
+
168
+ try {
169
+ mkdirSync(dirname(e2ePath), {recursive: true})
170
+ writeFileSync(e2ePath, content)
171
+ console.log(` ✅ ${label}: created e2e/${expectedE2E}`)
172
+ result.created++
173
+ } catch (err) {
174
+ console.log(` ❌ ${label}: failed to create e2e/${expectedE2E}: ${err}`)
175
+ result.errors++
176
+ }
177
+ }
178
+
179
+ if (result.created === 0 && result.errors === 0 && !CHECK_ONLY) {
180
+ console.log(` ✅ ${label}: all ${pageFiles.length} pages already have matching e2e tests`)
181
+ }
182
+
183
+ return result
184
+ }
185
+
186
+ // ── Main ──────────────────────────────────────────────────────────────
187
+
188
+ let totalCreated = 0
189
+ let totalSkipped = 0
190
+ let totalErrors = 0
191
+
192
+ if (customPath) {
193
+ const label = customPath.replace(/\\/g, `/`).replace(/\/+$/, ``)
194
+ const pagesDir = join(customPath, `src`, `pages`)
195
+ const e2eDir = join(customPath, `e2e`)
196
+ const r = processProject(label, pagesDir, e2eDir)
197
+ totalCreated += r.created
198
+ totalSkipped += r.skipped
199
+ totalErrors += r.errors
200
+ } else {
201
+ // Default: generate for this project's own pages → e2e
202
+ const pagesDir = join(import.meta.dir, `../src/pages`)
203
+ const e2eDir = join(import.meta.dir, `..`)
204
+ const r = processProject(`this project`, pagesDir, e2eDir)
205
+ totalCreated += r.created
206
+ totalSkipped += r.skipped
207
+ totalErrors += r.errors
208
+ }
209
+
210
+ // ── Summary ───────────────────────────────────────────────────────────
211
+
212
+ console.log(``)
213
+ console.log(`Summary:`)
214
+ console.log(` Created: ${totalCreated}`)
215
+ console.log(` Skipped (already exists): ${totalSkipped}`)
216
+ console.log(` Errors: ${totalErrors}`)
217
+
218
+ if (totalErrors > 0) {
219
+ process.exit(1)
220
+ }
@@ -1,6 +1,6 @@
1
1
  import {Gramstax} from "gramstax"
2
2
  import {UserGuard} from "~/guards/user"
3
- import {GeneralStatusPage} from "~/pages/general-status"
3
+ import {StatusPage} from "~/pages/status"
4
4
  import type {CtxCore} from "./ctx"
5
5
 
6
6
  export class BotCore extends Gramstax {
@@ -13,10 +13,10 @@ export class BotCore extends Gramstax {
13
13
  }
14
14
 
15
15
  async hookErrorPage(ctx: CtxCore, data: NonNullable<Parameters<NonNullable<Gramstax[`hookErrorPage`]>>[1]>) {
16
- await new GeneralStatusPage(ctx).berror.show(`${data.listenerName}:hookErrorPage`, data.error, data.isEdit)
16
+ await new StatusPage(ctx).berror.show(`${data.listenerName}:hookErrorPage`, data.error, data.isEdit)
17
17
  }
18
18
 
19
19
  async hookErrorInputNotFoundPage(ctx: CtxCore) {
20
- await new GeneralStatusPage(ctx).berrorNotFound.show()
20
+ await new StatusPage(ctx).bnotFound.show()
21
21
  }
22
22
  }
@@ -1,10 +1,10 @@
1
1
  import {GuardBase} from "~/base/guard"
2
- import {GeneralStatusPage} from "~/pages/general-status"
2
+ import {StatusPage} from "~/pages/status"
3
3
  import {UserNameNotFoundPage} from "~/pages/username-notfound"
4
4
 
5
5
  export class UserGuard extends GuardBase {
6
6
  async _catch(err: any, func: any, isEdit: boolean) {
7
- await new GeneralStatusPage(this).berror.show(func, err, isEdit)
7
+ await new StatusPage(this).berror.show(func, err, isEdit)
8
8
  return null
9
9
  }
10
10
 
@@ -1,7 +1,7 @@
1
- import {HelpPage} from "./help"
1
+ import {HelpPage} from "../help"
2
2
  import {PageBase} from "~/base/page"
3
- import {RandomNumberPage} from "./random-number"
4
- import {SumNumberPage} from "./sum-number"
3
+ import {SumNumberPage} from "../sum-number"
4
+ import {StartRandomPage} from "./random"
5
5
 
6
6
  export class StartPage extends PageBase {
7
7
  async handleCallbackQueryData() {
@@ -15,7 +15,7 @@ export class StartPage extends PageBase {
15
15
  bdefault = {
16
16
  show: async (isEdit = true) => {
17
17
  await this.clearSession()
18
- const kb = this.inlineKeyboardT([RandomNumberPage, SumNumberPage, HelpPage], `2`)
18
+ const kb = this.inlineKeyboardT([StartRandomPage, SumNumberPage, HelpPage], `2`)
19
19
  const da = {userName: this.from?.username}
20
20
  await this.renderAutoRich(kb, da, null, null, null, isEdit)
21
21
  }
@@ -23,14 +23,14 @@ export class StartPage extends PageBase {
23
23
 
24
24
  static template = `
25
25
  <g-base>
26
- <g-keyboard type="inline">🎰 Random Number, ➕ Sum Number, 🆘 Help</g-keyboard>
27
- <g-keyboard type="inline" lang="id">🎰 Angka Acak, ➕ Jumlah Angka, 🆘 Bantuan</g-keyboard>
26
+ <g-keyboard type="inline">🎲 Random, ➕ Sum Number, 🆘 Help</g-keyboard>
27
+ <g-keyboard type="inline" lang="id">🎲 Acak, ➕ Jumlah Angka, 🆘 Bantuan</g-keyboard>
28
28
  <g-message>
29
29
  <b>👋 Welcome to the bot {{userName}}</b>
30
30
 
31
31
  This bot was initially generated using Gramstax as the base setup and starting structure. It provides a clean foundation that can be extended with custom features, commands, and integrations as needed.
32
32
 
33
- <b>🎰 Random Number</b> - Generate a random number
33
+ <b>🎲 Random</b> - Generate random numbers or text
34
34
  <b>➕ Sum Number</b> - Add two numbers together
35
35
  <b>❓ Help</b> - View help and available commands
36
36
 
@@ -43,7 +43,7 @@ export class StartPage extends PageBase {
43
43
 
44
44
  Bot ini dibuat menggunakan Gramstax sebagai generator awal dan struktur dasar. Dari sini, bot dapat dikembangkan lebih lanjut dengan fitur, perintah, dan integrasi sesuai kebutuhan.
45
45
 
46
- <b>🎰 Angka Acak</b> - Menghasilkan angka secara acak
46
+ <b>🎲 Acak</b> - Hasilkan angka atau teks acak
47
47
  <b>➕ Jumlah Angka</b> - Menjumlahkan dua angka
48
48
  <b>❓ Bantuan</b> - Lihat bantuan dan daftar perintah
49
49
 
@@ -0,0 +1,38 @@
1
+ import {PageBase} from "~/base/page"
2
+ import {StartPage} from ".."
3
+ import {StartRandomNumberPage} from "./number"
4
+ import {StartRandomTextPage} from "./text"
5
+
6
+ export class StartRandomPage extends PageBase {
7
+ async handleCallbackQueryData() {
8
+ await this.bdefault.show(true)
9
+ }
10
+
11
+ async handleCommandText() {
12
+ await this.bdefault.show(false)
13
+ }
14
+
15
+ bdefault = {
16
+ show: async (isEdit = true) => {
17
+ const kb = this.inlineKeyboardT([StartRandomNumberPage, StartRandomTextPage, StartPage], `2`)
18
+ await this.renderAutoRich(kb, null, null, null, null, isEdit)
19
+ }
20
+ }
21
+
22
+ static template = `
23
+ <g-base>
24
+ <g-keyboard type="inline">🎲 Random Number, 📝 Random Text, ${this.kbl.back.default}</g-keyboard>
25
+ <g-keyboard type="inline" lang="id">🎲 Angka Acak, 📝 Teks Acak, ${this.kbl.back.id}</g-keyboard>
26
+ <g-message>
27
+ <b>🎲 Random Generator</b>
28
+
29
+ Choose what kind of random you want to generate.
30
+ </g-message>
31
+ <g-message lang="id">
32
+ <b>🎲 Generator Acak</b>
33
+
34
+ Pilih jenis acak yang ingin dihasilkan.
35
+ </g-message>
36
+ </g-base>
37
+ `
38
+ }
@@ -1,18 +1,14 @@
1
1
  import {PageBase} from "~/base/page"
2
- import {StartPage} from "./start"
2
+ import {StartRandomPage} from "."
3
3
 
4
- export class RandomNumberPage extends PageBase {
4
+ export class StartRandomNumberPage extends PageBase {
5
5
  async handleCallbackQueryData() {
6
6
  await this.bdefault.show(true)
7
7
  }
8
8
 
9
- async handleCommandText() {
10
- await this.bdefault.show(false)
11
- }
12
-
13
9
  bdefault = {
14
10
  show: async (isEdit = true) => {
15
- const kb = this.inlineKeyboardT([this, StartPage])
11
+ const kb = this.inlineKeyboardT([this, StartRandomPage], `2`)
16
12
  const da = {result: Math.random() * 10 ** 18}
17
13
  await this.renderAuto(kb, da, null, null, isEdit)
18
14
  }
@@ -0,0 +1,98 @@
1
+ import {PageBase} from "~/base/page"
2
+ import {StartRandomPage} from "."
3
+
4
+ const _WORDS = [
5
+ `innovation`,
6
+ `discovery`,
7
+ `adventure`,
8
+ `knowledge`,
9
+ `wisdom`,
10
+ `curiosity`,
11
+ `imagination`,
12
+ `creation`,
13
+ `exploration`,
14
+ `inspiration`,
15
+ `freedom`,
16
+ `harmony`,
17
+ `courage`,
18
+ `patience`,
19
+ `kindness`,
20
+ `brilliance`,
21
+ `serendipity`,
22
+ `resilience`,
23
+ `ambition`,
24
+ `compassion`,
25
+ `optimism`,
26
+ `integrity`,
27
+ `gratitude`,
28
+ `humility`,
29
+ `perseverance`
30
+ ]
31
+
32
+ const _ID_WORDS = [
33
+ `inovasi`,
34
+ `penemuan`,
35
+ `petualangan`,
36
+ `pengetahuan`,
37
+ `kebijaksanaan`,
38
+ `rasa ingin tahu`,
39
+ `imajinasi`,
40
+ `kreasi`,
41
+ `eksplorasi`,
42
+ `inspirasi`,
43
+ `kemerdekaan`,
44
+ `keharmonisan`,
45
+ `keberanian`,
46
+ `kesabaran`,
47
+ `kebaikan`,
48
+ `kecerdasan`,
49
+ `keberuntungan`,
50
+ `ketahanan`,
51
+ `ambisi`,
52
+ `kasih sayang`,
53
+ `optimisme`,
54
+ `integritas`,
55
+ `rasa syukur`,
56
+ `kerendahan hati`,
57
+ `ketekunan`
58
+ ]
59
+
60
+ class Helper {
61
+ static pickRandom<T>(arr: T[], count: number): T[] {
62
+ const shuffled = [...arr].sort(() => Math.random() - 0.5)
63
+ return shuffled.slice(0, count)
64
+ }
65
+ }
66
+
67
+ export class StartRandomTextPage extends PageBase {
68
+ async handleCallbackQueryData() {
69
+ await this.bdefault.show(true)
70
+ }
71
+
72
+ bdefault = {
73
+ show: async (isEdit = true) => {
74
+ const kb = this.inlineKeyboardT([this, StartRandomPage], `2`)
75
+ const count = Math.floor(Math.random() * 3) + 2 // 2–4 words
76
+ const lang = this.from.language_code
77
+ const da = {res: lang === `en` ? Helper.pickRandom(_WORDS, count).join(` `) : lang === `id` ? Helper.pickRandom(_ID_WORDS, count).join(` `) : Helper.pickRandom(_WORDS, count).join(` `)}
78
+ await this.renderAuto(kb, da, null, null, isEdit)
79
+ }
80
+ }
81
+
82
+ static template = `
83
+ <g-base>
84
+ <g-keyboard type="inline">🔁 Regenerate, ${this.kbl.back.default}</g-keyboard>
85
+ <g-keyboard type="inline" lang="id">🔁 Regenerasi, ${this.kbl.back.id}</g-keyboard>
86
+ <g-message>
87
+ <b>📝 Random Text</b>
88
+
89
+ {{res}}
90
+ </g-message>
91
+ <g-message lang="id">
92
+ <b>📝 Teks Acak</b>
93
+
94
+ {{res}}
95
+ </g-message>
96
+ </g-base>
97
+ `
98
+ }
@@ -2,7 +2,7 @@ import {LogHe} from "~/helpers/log"
2
2
  import {PageBase} from "~/base/page"
3
3
  import {StartPage} from "./start"
4
4
 
5
- export class GeneralStatusPage extends PageBase {
5
+ export class StatusPage extends PageBase {
6
6
  constructorName?: string
7
7
 
8
8
  constructor(p: any) {
@@ -21,7 +21,7 @@ export class GeneralStatusPage extends PageBase {
21
21
  }
22
22
  }
23
23
 
24
- berrorNotFound = {
24
+ bnotFound = {
25
25
  show: async () => {
26
26
  const kb = this.inlineKeyboardT([StartPage])
27
27
  const da = {isNotFound: true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-gramstax",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",