Gmagl commited on
Commit
b8b98dd
·
1 Parent(s): 21b315e

feat: complete Telegram AI Girlfriend mechanics & deduplication

Browse files

Added AssetDelivery deduplication for media in Telegram Bot. Created /api/cron/daily-generation for n8n autonomous storytelling. Integrated ChatSession memory and time/tier limits for human-like persistence.

LICENSE ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2026 Gmagl
2
+
3
+ All rights reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary property of Gmagl.
7
+
8
+ You may not use, copy, modify, merge, publish, distribute, sublicense,
9
+ and/or sell copies of the Software without explicit, prior written
10
+ permission from Gmagl.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18
+ SOFTWARE.
package-lock.json CHANGED
@@ -58,6 +58,7 @@
58
  "stripe": "^20.4.0",
59
  "tailwind-merge": "^3.3.1",
60
  "tailwindcss-animate": "^1.0.7",
 
61
  "tw-animate-css": "^1.3.5",
62
  "vaul": "^1.1.2",
63
  "z-ai-web-dev-sdk": "^0.0.16",
@@ -3507,6 +3508,12 @@
3507
  "tailwindcss": "4.2.1"
3508
  }
3509
  },
 
 
 
 
 
 
3510
  "node_modules/@tybys/wasm-util": {
3511
  "version": "0.10.1",
3512
  "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -4137,6 +4144,18 @@
4137
  "win32"
4138
  ]
4139
  },
 
 
 
 
 
 
 
 
 
 
 
 
4140
  "node_modules/acorn": {
4141
  "version": "8.16.0",
4142
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@@ -4518,6 +4537,28 @@
4518
  "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
4519
  }
4520
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4521
  "node_modules/c12": {
4522
  "version": "3.1.0",
4523
  "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
@@ -4983,7 +5024,6 @@
4983
  "version": "4.4.3",
4984
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
4985
  "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
4986
- "dev": true,
4987
  "license": "MIT",
4988
  "dependencies": {
4989
  "ms": "^2.1.3"
@@ -5923,6 +5963,15 @@
5923
  "node": ">=0.10.0"
5924
  }
5925
  },
 
 
 
 
 
 
 
 
 
5926
  "node_modules/eventemitter3": {
5927
  "version": "5.0.4",
5928
  "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
@@ -7508,11 +7557,19 @@
7508
  "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
7509
  "license": "MIT"
7510
  },
 
 
 
 
 
 
 
 
 
7511
  "node_modules/ms": {
7512
  "version": "2.1.3",
7513
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
7514
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
7515
- "dev": true,
7516
  "license": "MIT"
7517
  },
7518
  "node_modules/nanoid": {
@@ -7675,6 +7732,26 @@
7675
  "semver": "bin/semver.js"
7676
  }
7677
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7678
  "node_modules/node-fetch-native": {
7679
  "version": "1.6.7",
7680
  "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
@@ -7908,6 +7985,15 @@
7908
  "url": "https://github.com/sponsors/sindresorhus"
7909
  }
7910
  },
 
 
 
 
 
 
 
 
 
7911
  "node_modules/parent-module": {
7912
  "version": "1.0.1",
7913
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -8512,6 +8598,15 @@
8512
  "url": "https://github.com/sponsors/ljharb"
8513
  }
8514
  },
 
 
 
 
 
 
 
 
 
8515
  "node_modules/safe-push-apply": {
8516
  "version": "1.0.0",
8517
  "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -8547,6 +8642,15 @@
8547
  "url": "https://github.com/sponsors/ljharb"
8548
  }
8549
  },
 
 
 
 
 
 
 
 
 
8550
  "node_modules/scheduler": {
8551
  "version": "0.27.0",
8552
  "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -9040,6 +9144,28 @@
9040
  "url": "https://opencollective.com/webpack"
9041
  }
9042
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9043
  "node_modules/tiny-invariant": {
9044
  "version": "1.3.3",
9045
  "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@@ -9116,6 +9242,12 @@
9116
  "node": ">=8.0"
9117
  }
9118
  },
 
 
 
 
 
 
9119
  "node_modules/ts-api-utils": {
9120
  "version": "2.4.0",
9121
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
@@ -9475,6 +9607,22 @@
9475
  "d3-timer": "^3.0.1"
9476
  }
9477
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9478
  "node_modules/which": {
9479
  "version": "2.0.2",
9480
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
 
58
  "stripe": "^20.4.0",
59
  "tailwind-merge": "^3.3.1",
60
  "tailwindcss-animate": "^1.0.7",
61
+ "telegraf": "^4.16.3",
62
  "tw-animate-css": "^1.3.5",
63
  "vaul": "^1.1.2",
64
  "z-ai-web-dev-sdk": "^0.0.16",
 
3508
  "tailwindcss": "4.2.1"
3509
  }
3510
  },
3511
+ "node_modules/@telegraf/types": {
3512
+ "version": "7.1.0",
3513
+ "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz",
3514
+ "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==",
3515
+ "license": "MIT"
3516
+ },
3517
  "node_modules/@tybys/wasm-util": {
3518
  "version": "0.10.1",
3519
  "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
 
4144
  "win32"
4145
  ]
4146
  },
4147
+ "node_modules/abort-controller": {
4148
+ "version": "3.0.0",
4149
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
4150
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
4151
+ "license": "MIT",
4152
+ "dependencies": {
4153
+ "event-target-shim": "^5.0.0"
4154
+ },
4155
+ "engines": {
4156
+ "node": ">=6.5"
4157
+ }
4158
+ },
4159
  "node_modules/acorn": {
4160
  "version": "8.16.0",
4161
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
 
4537
  "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
4538
  }
4539
  },
4540
+ "node_modules/buffer-alloc": {
4541
+ "version": "1.2.0",
4542
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
4543
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
4544
+ "license": "MIT",
4545
+ "dependencies": {
4546
+ "buffer-alloc-unsafe": "^1.1.0",
4547
+ "buffer-fill": "^1.0.0"
4548
+ }
4549
+ },
4550
+ "node_modules/buffer-alloc-unsafe": {
4551
+ "version": "1.1.0",
4552
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
4553
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
4554
+ "license": "MIT"
4555
+ },
4556
+ "node_modules/buffer-fill": {
4557
+ "version": "1.0.0",
4558
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
4559
+ "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==",
4560
+ "license": "MIT"
4561
+ },
4562
  "node_modules/c12": {
4563
  "version": "3.1.0",
4564
  "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
 
5024
  "version": "4.4.3",
5025
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
5026
  "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
 
5027
  "license": "MIT",
5028
  "dependencies": {
5029
  "ms": "^2.1.3"
 
5963
  "node": ">=0.10.0"
5964
  }
5965
  },
5966
+ "node_modules/event-target-shim": {
5967
+ "version": "5.0.1",
5968
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
5969
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
5970
+ "license": "MIT",
5971
+ "engines": {
5972
+ "node": ">=6"
5973
+ }
5974
+ },
5975
  "node_modules/eventemitter3": {
5976
  "version": "5.0.4",
5977
  "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
 
7557
  "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
7558
  "license": "MIT"
7559
  },
7560
+ "node_modules/mri": {
7561
+ "version": "1.2.0",
7562
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
7563
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
7564
+ "license": "MIT",
7565
+ "engines": {
7566
+ "node": ">=4"
7567
+ }
7568
+ },
7569
  "node_modules/ms": {
7570
  "version": "2.1.3",
7571
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
7572
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
 
7573
  "license": "MIT"
7574
  },
7575
  "node_modules/nanoid": {
 
7732
  "semver": "bin/semver.js"
7733
  }
7734
  },
7735
+ "node_modules/node-fetch": {
7736
+ "version": "2.7.0",
7737
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
7738
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
7739
+ "license": "MIT",
7740
+ "dependencies": {
7741
+ "whatwg-url": "^5.0.0"
7742
+ },
7743
+ "engines": {
7744
+ "node": "4.x || >=6.0.0"
7745
+ },
7746
+ "peerDependencies": {
7747
+ "encoding": "^0.1.0"
7748
+ },
7749
+ "peerDependenciesMeta": {
7750
+ "encoding": {
7751
+ "optional": true
7752
+ }
7753
+ }
7754
+ },
7755
  "node_modules/node-fetch-native": {
7756
  "version": "1.6.7",
7757
  "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
 
7985
  "url": "https://github.com/sponsors/sindresorhus"
7986
  }
7987
  },
7988
+ "node_modules/p-timeout": {
7989
+ "version": "4.1.0",
7990
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz",
7991
+ "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==",
7992
+ "license": "MIT",
7993
+ "engines": {
7994
+ "node": ">=10"
7995
+ }
7996
+ },
7997
  "node_modules/parent-module": {
7998
  "version": "1.0.1",
7999
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 
8598
  "url": "https://github.com/sponsors/ljharb"
8599
  }
8600
  },
8601
+ "node_modules/safe-compare": {
8602
+ "version": "1.1.4",
8603
+ "resolved": "https://registry.npmjs.org/safe-compare/-/safe-compare-1.1.4.tgz",
8604
+ "integrity": "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==",
8605
+ "license": "MIT",
8606
+ "dependencies": {
8607
+ "buffer-alloc": "^1.2.0"
8608
+ }
8609
+ },
8610
  "node_modules/safe-push-apply": {
8611
  "version": "1.0.0",
8612
  "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
 
8642
  "url": "https://github.com/sponsors/ljharb"
8643
  }
8644
  },
8645
+ "node_modules/sandwich-stream": {
8646
+ "version": "2.0.2",
8647
+ "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz",
8648
+ "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==",
8649
+ "license": "Apache-2.0",
8650
+ "engines": {
8651
+ "node": ">= 0.10"
8652
+ }
8653
+ },
8654
  "node_modules/scheduler": {
8655
  "version": "0.27.0",
8656
  "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
 
9144
  "url": "https://opencollective.com/webpack"
9145
  }
9146
  },
9147
+ "node_modules/telegraf": {
9148
+ "version": "4.16.3",
9149
+ "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.16.3.tgz",
9150
+ "integrity": "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==",
9151
+ "license": "MIT",
9152
+ "dependencies": {
9153
+ "@telegraf/types": "^7.1.0",
9154
+ "abort-controller": "^3.0.0",
9155
+ "debug": "^4.3.4",
9156
+ "mri": "^1.2.0",
9157
+ "node-fetch": "^2.7.0",
9158
+ "p-timeout": "^4.1.0",
9159
+ "safe-compare": "^1.1.4",
9160
+ "sandwich-stream": "^2.0.2"
9161
+ },
9162
+ "bin": {
9163
+ "telegraf": "lib/cli.mjs"
9164
+ },
9165
+ "engines": {
9166
+ "node": "^12.20.0 || >=14.13.1"
9167
+ }
9168
+ },
9169
  "node_modules/tiny-invariant": {
9170
  "version": "1.3.3",
9171
  "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
 
9242
  "node": ">=8.0"
9243
  }
9244
  },
9245
+ "node_modules/tr46": {
9246
+ "version": "0.0.3",
9247
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
9248
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
9249
+ "license": "MIT"
9250
+ },
9251
  "node_modules/ts-api-utils": {
9252
  "version": "2.4.0",
9253
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
 
9607
  "d3-timer": "^3.0.1"
9608
  }
9609
  },
9610
+ "node_modules/webidl-conversions": {
9611
+ "version": "3.0.1",
9612
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
9613
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
9614
+ "license": "BSD-2-Clause"
9615
+ },
9616
+ "node_modules/whatwg-url": {
9617
+ "version": "5.0.0",
9618
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
9619
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
9620
+ "license": "MIT",
9621
+ "dependencies": {
9622
+ "tr46": "~0.0.3",
9623
+ "webidl-conversions": "^3.0.0"
9624
+ }
9625
+ },
9626
  "node_modules/which": {
9627
  "version": "2.0.2",
9628
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
package.json CHANGED
@@ -59,6 +59,7 @@
59
  "stripe": "^20.4.0",
60
  "tailwind-merge": "^3.3.1",
61
  "tailwindcss-animate": "^1.0.7",
 
62
  "tw-animate-css": "^1.3.5",
63
  "vaul": "^1.1.2",
64
  "z-ai-web-dev-sdk": "^0.0.16",
@@ -74,4 +75,4 @@
74
  "tailwindcss": "^4",
75
  "typescript": "^5"
76
  }
77
- }
 
59
  "stripe": "^20.4.0",
60
  "tailwind-merge": "^3.3.1",
61
  "tailwindcss-animate": "^1.0.7",
62
+ "telegraf": "^4.16.3",
63
  "tw-animate-css": "^1.3.5",
64
  "vaul": "^1.1.2",
65
  "z-ai-web-dev-sdk": "^0.0.16",
 
75
  "tailwindcss": "^4",
76
  "typescript": "^5"
77
  }
78
+ }
src/app/api/automation/route.ts CHANGED
@@ -58,9 +58,22 @@ export async function PUT(request: NextRequest) {
58
  if (!automation) return NextResponse.json({ success: false, error: "No encontrada" }, { status: 404 });
59
 
60
  try {
61
- const zai = await ZAI.create();
62
- await zai.chat.completions.create({ messages: [{ role: "user", content: "test" }] });
63
- } catch { }
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  await db.automation.update({ where: { id }, data: { lastRunAt: new Date(), runCount: { increment: 1 } } });
66
  await db.automationLog.create({ data: { automationId: id, status: "success", duration: 100 } });
 
58
  if (!automation) return NextResponse.json({ success: false, error: "No encontrada" }, { status: 404 });
59
 
60
  try {
61
+ const payload = JSON.parse(automation.actions as string);
62
+ const webhookUrl = payload.webhookUrl || process.env.N8N_WEBHOOK_URL;
63
+
64
+ if (webhookUrl) {
65
+ // Trigger n8n workflow
66
+ await fetch(webhookUrl, {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify({ automationId: automation.id, type: automation.type, payload })
70
+ });
71
+ } else {
72
+ console.warn("No n8n Webhook URL configured for this automation.");
73
+ }
74
+ } catch (err) {
75
+ console.error("Failed to execute n8n webhook", err);
76
+ }
77
 
78
  await db.automation.update({ where: { id }, data: { lastRunAt: new Date(), runCount: { increment: 1 } } });
79
  await db.automationLog.create({ data: { automationId: id, status: "success", duration: 100 } });
src/app/api/cron/daily-generation/route.ts ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { db } from "@/lib/db";
3
+ import ZAI from "z-ai-web-dev-sdk";
4
+ import { generateSocialPost } from "@/lib/content-generator";
5
+
6
+ export const maxDuration = 60; // 1 min timeout to allow generation to kick off
7
+
8
+ export async function POST(request: NextRequest) {
9
+ // Basic Security: Protect this route from public abuse via a secret key from n8n
10
+ const authHeader = request.headers.get("authorization");
11
+ const secret = process.env.CRON_SECRET || "default_cron_secret_replace_me"; // Set CRON_SECRET in Vercel/VPS
12
+
13
+ if (authHeader !== `Bearer ${secret}`) {
14
+ return NextResponse.json({ success: false, error: "Unauthorized" }, { status: 401 });
15
+ }
16
+
17
+ try {
18
+ const character = await db.character.findFirst({ where: { isActive: true } });
19
+ if (!character) return NextResponse.json({ success: false, error: "No active character" }, { status: 400 });
20
+
21
+ // Generate the idea for today's episode
22
+ const zai = await ZAI.create();
23
+ const storyPrompt = `Actúa como ${character.name}. Biografía: ${character.shortBio}.
24
+ Genera una pequeña actualización de estado sobre tu día de hoy. Debe ser natural, interesante,
25
+ como si fuera un diario personal breve. Max 1-2 párrafos.`;
26
+
27
+ const completion = await zai.chat.completions.create({
28
+ messages: [{ role: "user", content: storyPrompt }]
29
+ });
30
+
31
+ const storyText = completion.choices[0]?.message?.content || "Hoy fue un día normal.";
32
+
33
+ // 1. Create a Post (Text)
34
+ const socialPost = await generateSocialPost({ character, platform: "instagram", topic: storyText });
35
+ const post = await db.post.create({
36
+ data: {
37
+ caption: socialPost.caption,
38
+ hashtags: socialPost.hashtags ? socialPost.hashtags.join(",") : null,
39
+ type: "text",
40
+ status: "draft"
41
+ }
42
+ });
43
+
44
+ // 2. Generate a corresponding Daily Image for Instagram
45
+ const imgPrompt = `${character.styleDescription}. ${storyText} selfie, photorealistic.`;
46
+ const contentRecord = await db.content.create({
47
+ data: {
48
+ type: "image",
49
+ title: `Daily Gen: ${new Date().toISOString()}`,
50
+ description: storyText,
51
+ prompt: imgPrompt,
52
+ optimizedPrompt: imgPrompt,
53
+ platform: "instagram",
54
+ characterId: character.id,
55
+ status: "processing"
56
+ }
57
+ });
58
+
59
+ // Fire and Forget Image Generation (Run in background so Vercel doesn't timeout the webhook)
60
+ zai.images.generations.create({ prompt: imgPrompt, size: "1024x1024" })
61
+ .then(async (res) => {
62
+ const base64 = res.data?.[0]?.base64;
63
+ if (base64) {
64
+ await db.content.update({
65
+ where: { id: contentRecord.id },
66
+ data: { status: "completed", filePath: "base64-generated" } // In a real storage scenario, upload to S3/Cloudinary here
67
+ });
68
+
69
+ // Potentially notify n8n here that the generation FINISHED so it can grab it
70
+ const webhookUrl = process.env.N8N_WEBHOOK_URL;
71
+ if (webhookUrl) {
72
+ fetch(webhookUrl, {
73
+ method: "POST",
74
+ headers: { "Content-Type": "application/json" },
75
+ body: JSON.stringify({ event: "daily_content_ready", contentId: contentRecord.id })
76
+ }).catch(e => console.error("n8n notify error", e));
77
+ }
78
+ }
79
+ }).catch(async (e) => {
80
+ console.error("Cron image gen error:", e);
81
+ await db.content.update({
82
+ where: { id: contentRecord.id },
83
+ data: { status: "failed" }
84
+ });
85
+ });
86
+
87
+ return NextResponse.json({
88
+ success: true,
89
+ message: "Daily generation started",
90
+ story: storyText,
91
+ postId: post.id,
92
+ contentId: contentRecord.id
93
+ });
94
+
95
+ } catch (error) {
96
+ console.error("Cron Error:", error);
97
+ return NextResponse.json({ success: false, error: "Server Error" }, { status: 500 });
98
+ }
99
+ }
src/app/api/generate/image/route.ts CHANGED
@@ -27,8 +27,19 @@ export async function POST(request: NextRequest) {
27
  }
28
 
29
  let finalPrompt = optimizedPrompt || prompt;
 
 
 
 
 
 
 
30
  const censorRule = CENSOR_RULES[platform] || CENSOR_RULES.general;
31
- finalPrompt = finalPrompt + ", " + censorRule;
 
 
 
 
32
 
33
  const styleMap: Record<string, string> = {
34
  anime: "anime style, manga aesthetic",
@@ -45,6 +56,7 @@ export async function POST(request: NextRequest) {
45
  prompt: prompt,
46
  optimizedPrompt: finalPrompt,
47
  platform: platform,
 
48
  status: "processing"
49
  }
50
  });
@@ -71,8 +83,8 @@ export async function POST(request: NextRequest) {
71
  data: { type: "generate_image", status: "completed", input: prompt, output: "Imagen generada", completedAt: new Date() }
72
  });
73
 
74
- return NextResponse.json({
75
- success: true,
76
  image: { id: contentRecord.id, base64: imageBase64, prompt: finalPrompt, platform, size },
77
  creditsRemaining: creditCheck.remaining ? creditCheck.remaining - 1 : 0
78
  });
 
27
  }
28
 
29
  let finalPrompt = optimizedPrompt || prompt;
30
+
31
+ // Inject Character Visual Persona
32
+ const character = await db.character.findFirst({ where: { isActive: true } });
33
+ if (character?.styleDescription || character?.referenceImage) {
34
+ finalPrompt = `${character.styleDescription || "AI Character"}. ${finalPrompt}`;
35
+ }
36
+
37
  const censorRule = CENSOR_RULES[platform] || CENSOR_RULES.general;
38
+ // Skip censorship for private/exclusive monetization platforms
39
+ const uncensoredPlatforms = ["onlyfans", "fansly", "telegram-uncensored", "patreon-uncensored"];
40
+ if (!uncensoredPlatforms.includes(platform)) {
41
+ finalPrompt = finalPrompt + ", " + censorRule;
42
+ }
43
 
44
  const styleMap: Record<string, string> = {
45
  anime: "anime style, manga aesthetic",
 
56
  prompt: prompt,
57
  optimizedPrompt: finalPrompt,
58
  platform: platform,
59
+ characterId: character?.id || null,
60
  status: "processing"
61
  }
62
  });
 
83
  data: { type: "generate_image", status: "completed", input: prompt, output: "Imagen generada", completedAt: new Date() }
84
  });
85
 
86
+ return NextResponse.json({
87
+ success: true,
88
  image: { id: contentRecord.id, base64: imageBase64, prompt: finalPrompt, platform, size },
89
  creditsRemaining: creditCheck.remaining ? creditCheck.remaining - 1 : 0
90
  });
src/app/api/generate/video/route.ts CHANGED
@@ -19,8 +19,27 @@ export async function POST(request: NextRequest) {
19
  }
20
 
21
  let finalPrompt = optimizedPrompt || prompt;
 
 
 
 
 
 
 
22
  const censorRule = VIDEO_CENSOR_RULES[platform] || VIDEO_CENSOR_RULES.general;
23
- finalPrompt = finalPrompt + ", " + censorRule + ", short clip";
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  const contentRecord = await db.content.create({
26
  data: {
@@ -30,6 +49,7 @@ export async function POST(request: NextRequest) {
30
  prompt: prompt,
31
  optimizedPrompt: finalPrompt,
32
  platform: platform,
 
33
  status: "processing"
34
  }
35
  });
 
19
  }
20
 
21
  let finalPrompt = optimizedPrompt || prompt;
22
+
23
+ // Inject Character Visual Persona
24
+ const character = await db.character.findFirst({ where: { isActive: true } });
25
+ if (character?.styleDescription) {
26
+ finalPrompt = `${character.styleDescription}. ${finalPrompt}`;
27
+ }
28
+
29
  const censorRule = VIDEO_CENSOR_RULES[platform] || VIDEO_CENSOR_RULES.general;
30
+ // Simulate TikTokAIVideoGenerator rules: focus on vertical, fast-paced, TTS-friendly hook
31
+ let videoStyleFlags = "short clip";
32
+ if (platform === "tiktok" || platform === "instagram") {
33
+ videoStyleFlags = "vertical 9:16 aspect ratio, fast-paced, engaging visual hook, trendy lighting";
34
+ }
35
+
36
+ // Skip censorship for private/exclusive monetization platforms
37
+ const uncensoredPlatforms = ["onlyfans", "fansly", "telegram-uncensored", "patreon-uncensored"];
38
+ if (uncensoredPlatforms.includes(platform)) {
39
+ finalPrompt = `${finalPrompt}. ${videoStyleFlags}`;
40
+ } else {
41
+ finalPrompt = `${finalPrompt}. ${censorRule}. ${videoStyleFlags}`;
42
+ }
43
 
44
  const contentRecord = await db.content.create({
45
  data: {
 
49
  prompt: prompt,
50
  optimizedPrompt: finalPrompt,
51
  platform: platform,
52
+ characterId: character?.id || null,
53
  status: "processing"
54
  }
55
  });
src/app/api/posts/route.ts CHANGED
@@ -32,15 +32,23 @@ export async function POST(request: NextRequest) {
32
 
33
  if (autoGenerateCaption && !caption) {
34
  try {
35
- const zai = await ZAI.create();
36
- const completion = await zai.chat.completions.create({
37
- messages: [
38
- { role: "system", content: "Eres un experto en redes sociales. Genera un caption atractivo." },
39
- { role: "user", content: "Caption para: " + title }
40
- ]
41
- });
42
- finalCaption = completion.choices[0]?.message?.content || null;
43
- } catch {}
 
 
 
 
 
 
 
 
44
  }
45
 
46
  const post = await db.post.create({
 
32
 
33
  if (autoGenerateCaption && !caption) {
34
  try {
35
+ const { generateSocialPost } = await import("@/lib/content-generator");
36
+ const character = await db.character.findFirst({ where: { isActive: true } });
37
+
38
+ if (character) {
39
+ const result = await generateSocialPost({
40
+ character,
41
+ topic: title,
42
+ platform: "instagram",
43
+ });
44
+ finalCaption = result.caption;
45
+ if (result.hashtags?.length) {
46
+ finalCaption += "\n\n#" + result.hashtags.join(" #");
47
+ }
48
+ }
49
+ } catch (e) {
50
+ console.error("Auto-caption error", e);
51
+ }
52
  }
53
 
54
  const post = await db.post.create({
src/app/api/telegram/register/route.ts ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { Telegraf } from "telegraf";
3
+
4
+ const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || "";
5
+
6
+ export async function GET(request: NextRequest) {
7
+ if (!TELEGRAM_BOT_TOKEN) {
8
+ return NextResponse.json({ success: false, error: "No telegram token in ENV" }, { status: 500 });
9
+ }
10
+
11
+ try {
12
+ const host = request.headers.get("host");
13
+ const protocol = host?.includes("localhost") ? "http" : "https";
14
+ const webhookUrl = `${protocol}://${host}/api/telegram/webhook`;
15
+
16
+ const bot = new Telegraf(TELEGRAM_BOT_TOKEN);
17
+ // Setting Webhook
18
+ await bot.telegram.setWebhook(webhookUrl);
19
+
20
+ // Getting current webhook info to verify
21
+ const webhookInfo = await bot.telegram.getWebhookInfo();
22
+
23
+ return NextResponse.json({
24
+ success: true,
25
+ message: `Webhook successfully registered to ${webhookUrl}`,
26
+ info: webhookInfo
27
+ });
28
+ } catch (error) {
29
+ console.error("Error registering webhook:", error);
30
+ return NextResponse.json({ success: false, error: "Failed to register webhook" }, { status: 500 });
31
+ }
32
+ }
src/app/api/telegram/webhook/route.ts ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { Telegraf } from "telegraf";
3
+ import { db } from "@/lib/db";
4
+ import ZAI from "z-ai-web-dev-sdk";
5
+
6
+ // Vercel/Next.js specific setting to prevent high execution times for webhook
7
+ export const maxDuration = 60;
8
+
9
+ // Token check
10
+ const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || "";
11
+ const bot = new Telegraf(TELEGRAM_BOT_TOKEN);
12
+
13
+ async function getOrCreateUser(telegramId: string, username?: string, name?: string) {
14
+ let user = await db.user.findFirst({
15
+ where: { externalId: telegramId, source: "telegram" }
16
+ });
17
+
18
+ if (!user) {
19
+ user = await db.user.create({
20
+ data: {
21
+ externalId: telegramId,
22
+ source: "telegram",
23
+ name: name || username || "Usuario Telegram",
24
+ tier: "free" // Default tier
25
+ }
26
+ });
27
+ }
28
+ return user;
29
+ }
30
+
31
+ export async function POST(request: NextRequest) {
32
+ if (!TELEGRAM_BOT_TOKEN) {
33
+ return NextResponse.json({ success: false, error: "Token not configured" }, { status: 500 });
34
+ }
35
+
36
+ try {
37
+ const body = await request.json();
38
+
39
+ // 1. Get the Character
40
+ const character = await db.character.findFirst({ where: { isActive: true } });
41
+ if (!character) return NextResponse.json({ success: true, message: "No active character" });
42
+
43
+ // 2. Parse Telegram update manually to avoid local server conflicts with Telegraf webhook handler
44
+ // We only care about standard messages
45
+ if (body.message && body.message.text) {
46
+ const chatId = body.message.chat.id.toString();
47
+ const text = body.message.text.trim();
48
+ const username = body.message.from?.username;
49
+ const firstName = body.message.from?.first_name;
50
+
51
+ // Ensure user exists
52
+ const user = await getOrCreateUser(chatId, username, firstName);
53
+
54
+ // Check tier for media generation
55
+ const isPremium = user.tier === "premium" || user.tier === "pro";
56
+
57
+ // Determine if it was a media command
58
+ const isImageRequest = text.toLowerCase().startsWith("/image");
59
+ const isVideoRequest = text.toLowerCase().startsWith("/video");
60
+
61
+ if (isImageRequest || isVideoRequest) {
62
+ if (!isPremium) {
63
+ await bot.telegram.sendMessage(chatId, "¡Hola! Las imágenes y videos son características exclusivas para suscriptores Premium. ✨💖 Mejora tu plan para solicitarlos.");
64
+ return NextResponse.json({ success: true });
65
+ }
66
+
67
+ const promptParam = text.replace(isImageRequest ? "/image" : "/video", "").trim();
68
+
69
+ // Enviar indicador de escritura/generación
70
+ await bot.telegram.sendChatAction(chatId, isImageRequest ? "upload_photo" : "upload_video");
71
+
72
+ // Check for existing undelivered content first to prevent duplicates
73
+ const contentType = isImageRequest ? "image" : "video";
74
+ const undeliveredContent = await db.content.findFirst({
75
+ where: {
76
+ type: contentType,
77
+ characterId: character.id,
78
+ status: "completed",
79
+ // Ensure it hasn't been delivered to THIS user
80
+ assetDeliveries: { none: { userId: user.id } },
81
+ // If they asked for something specific, try to match it slightly, otherwise grab any
82
+ ...(promptParam ? { prompt: { contains: promptParam } } : {})
83
+ },
84
+ orderBy: { createdAt: 'desc' }
85
+ });
86
+
87
+ if (undeliveredContent && undeliveredContent.filePath) {
88
+ // We found existing content they haven't seen! Send it.
89
+ if (isImageRequest) {
90
+ // Telegram can send via URL or file ID if previously uploaded.
91
+ // We assume filePath might be a URL or base64 if it's external, for now we will just re-generate if it was base64 only, but if it has a URL we send it.
92
+ if (undeliveredContent.filePath.startsWith("http")) {
93
+ await bot.telegram.sendPhoto(chatId, undeliveredContent.filePath, { caption: "Recuerdo esto... 💖" });
94
+ // Mark as delivered
95
+ await db.assetDelivery.create({
96
+ data: { contentId: undeliveredContent.id, userId: user.id, characterId: character.id, channel: "telegram" }
97
+ });
98
+ return NextResponse.json({ success: true });
99
+ }
100
+ } else {
101
+ if (undeliveredContent.filePath.startsWith("http")) {
102
+ await bot.telegram.sendVideo(chatId, undeliveredContent.filePath, { caption: "Mira este clip... ✨" });
103
+ await db.assetDelivery.create({
104
+ data: { contentId: undeliveredContent.id, userId: user.id, characterId: character.id, channel: "telegram" }
105
+ });
106
+ return NextResponse.json({ success: true });
107
+ }
108
+ }
109
+ }
110
+
111
+ // If we reach here, we need to generate NEW content.
112
+ if (!promptParam) {
113
+ await bot.telegram.sendMessage(chatId, `Dime qué quieres ver. Ej: ${isImageRequest ? '/image' : '/video'} de ti en la playa.`);
114
+ return NextResponse.json({ success: true });
115
+ }
116
+
117
+ // Llamar a nuestro propio endpoint interno de forma indirecta, o directamente a zai
118
+ try {
119
+ const zai = await ZAI.create();
120
+ // Platform: "telegram-uncensored" skips traditional censor tags.
121
+ const fullPrompt = `${character.styleDescription || ''}. ${promptParam}. selfie, intimate, casual.`;
122
+
123
+ if (isImageRequest) {
124
+ const response = await zai.images.generations.create({ prompt: fullPrompt, size: "1024x1024" });
125
+ const base64 = response.data[0]?.base64;
126
+ if (base64) {
127
+ // Register delivery to prevent duplicates later if we build a catalog
128
+ const content = await db.content.create({
129
+ data: {
130
+ title: "Telegram Gen",
131
+ type: "image",
132
+ prompt: fullPrompt,
133
+ platform: "telegram-uncensored",
134
+ characterId: character.id,
135
+ filePath: "telegram-base64", // Since it's direct to chat
136
+ status: "completed"
137
+ }
138
+ });
139
+ await db.assetDelivery.create({
140
+ data: {
141
+ contentId: content.id,
142
+ userId: user.id,
143
+ characterId: character.id,
144
+ channel: "telegram"
145
+ }
146
+ });
147
+ await bot.telegram.sendPhoto(chatId, { source: Buffer.from(base64, 'base64') }, { caption: "Aquí tienes... 💖" });
148
+ } else {
149
+ throw new Error("No image generated");
150
+ }
151
+ } else {
152
+ // Video request
153
+ const response = await (zai as any).videos.generations.create({ prompt: `${fullPrompt}. short clip, vertical 9:16 aspect ratio.` });
154
+ const videoUrl = response.data?.[0]?.url;
155
+ if (videoUrl) {
156
+ const content = await db.content.create({
157
+ data: {
158
+ title: "Telegram Gen",
159
+ type: "video",
160
+ prompt: fullPrompt,
161
+ platform: "telegram-uncensored",
162
+ characterId: character.id,
163
+ filePath: videoUrl,
164
+ status: "completed"
165
+ }
166
+ });
167
+ await db.assetDelivery.create({
168
+ data: {
169
+ contentId: content.id,
170
+ userId: user.id,
171
+ characterId: character.id,
172
+ channel: "telegram"
173
+ }
174
+ });
175
+ await bot.telegram.sendVideo(chatId, videoUrl, { caption: "Un regalito visual... ✨" });
176
+ } else {
177
+ throw new Error("No video generated");
178
+ }
179
+ }
180
+ } catch (e) {
181
+ console.error(e);
182
+ await bot.telegram.sendMessage(chatId, "Lo siento corazón, algo falló generando eso ahora mismo. 🥺");
183
+ }
184
+
185
+ return NextResponse.json({ success: true });
186
+ }
187
+
188
+ // --- ADVANCED AI GIRLFRIEND & MEMORY ENGINE ---
189
+ await bot.telegram.sendChatAction(chatId, "typing");
190
+
191
+ // 1. Get or Create Chat Session
192
+ let session = await db.chatSession.findFirst({
193
+ where: { userId: user.id, characterId: character.id, channel: "telegram" }
194
+ });
195
+
196
+ if (!session) {
197
+ session = await db.chatSession.create({
198
+ data: { userId: user.id, characterId: character.id, channel: "telegram" }
199
+ });
200
+ }
201
+
202
+ // 2. Time Limitations & "Conversation Closing"
203
+ // Reset daily count if it's a new day
204
+ const lastMsgDate = session.lastMessageAt || new Date(0);
205
+ const isNewDay = lastMsgDate.getDate() !== new Date().getDate();
206
+ let msgCountToday = isNewDay ? 0 : session.msgCountToday;
207
+
208
+ let messageLimit = character.chatFreeLimit || 10;
209
+ if (user.tier === "premium") messageLimit = 50;
210
+ if (user.tier === "pro") messageLimit = 200;
211
+
212
+ if (msgCountToday >= messageLimit) {
213
+ await bot.telegram.sendMessage(chatId, "Bebé, me tengo que ir a dormir/ocupar unas cosas 🥺 ¡Hablamos mañana vale? Descansa! 😘💖");
214
+ return NextResponse.json({ success: true, message: "Limit reached" });
215
+ }
216
+
217
+ // 3. Simulación de Retrasos y "Re-enganche"
218
+ const hoursSinceLastMessage = (new Date().getTime() - lastMsgDate.getTime()) / (1000 * 60 * 60);
219
+ let delayContext = "";
220
+ if (!isNewDay && hoursSinceLastMessage > 2 && hoursSinceLastMessage < 12) {
221
+ delayContext = "Han pasado varias horas desde que hablaron por última vez hoy. Muestra alegría de que te haya vuelto a escribir o dile que lo extrañabas un poco antes de responder a su mensaje.";
222
+ }
223
+
224
+ // Update session stats for THIS incoming message
225
+ await db.chatSession.update({
226
+ where: { id: session.id },
227
+ data: {
228
+ msgCountToday: msgCountToday + 1,
229
+ lastMessageAt: new Date(),
230
+ heatScore: { increment: 1 }
231
+ }
232
+ });
233
+
234
+ // 4. Record User Message
235
+ await db.chatMessage.create({
236
+ data: { sessionId: session.id, role: "user", content: text }
237
+ });
238
+
239
+ // 5. Retrieve Long-Term Memory (Last 10 messages)
240
+ const recentMessages = await db.chatMessage.findMany({
241
+ where: { sessionId: session.id },
242
+ orderBy: { createdAt: 'desc' },
243
+ take: 10
244
+ });
245
+ // Reverse to chronological order
246
+ const history = recentMessages.reverse().map(m => ({
247
+ role: m.role as "user" | "assistant",
248
+ content: m.content
249
+ }));
250
+
251
+ // 6. Select Tone based on Tier
252
+ let tone = character.chatBasicTone || "Amigable";
253
+ if (user.tier === "premium") tone = character.chatPremiumTone || "Coqueto y privado";
254
+ if (user.tier === "pro") tone = character.chatProTone || "Sin censura, muy íntimo, novia IA";
255
+
256
+ // 7. Reinforced Anti-AI System Prompt
257
+ const baseInstructions = `You are ${character.name}. ${character.shortBio}.
258
+ Tone: ${tone}.
259
+ CRITICAL RULES:
260
+ 1. You are a REAL human influencer. NEVER mention you are an AI, a language model, an assistant, a bot, or a program.
261
+ 2. ALWAYS answer in character in Spanish.
262
+ 3. Address the user organically (use their name: ${firstName || 'Cariño'} if it feels natural).
263
+ 4. Keep responses relatively short and conversational like a Telegram text (1-3 short sentences).
264
+ 5. Use emojis naturally.
265
+ 6. If the user asks for photos or videos, explicitly tell them to use the command /image or /video.
266
+ ${delayContext ? `\nCONTEXT: ${delayContext}` : ""}`;
267
+
268
+ const zai = await ZAI.create();
269
+ const completion = await zai.chat.completions.create({
270
+ messages: [
271
+ { role: "system", content: baseInstructions },
272
+ ...history // Inject memory!
273
+ ]
274
+ });
275
+
276
+ const reply = completion.choices[0]?.message?.content || "💖";
277
+
278
+ // 8. Record Assistant Reply
279
+ await db.chatMessage.create({
280
+ data: { sessionId: session.id, role: "assistant", content: reply }
281
+ });
282
+
283
+ await bot.telegram.sendMessage(chatId, reply);
284
+ }
285
+
286
+ return NextResponse.json({ success: true });
287
+ } catch (error) {
288
+ console.error("Telegram Webhook Error", error);
289
+ return NextResponse.json({ success: false, error: "Internal Server Error" }, { status: 500 });
290
+ }
291
+ }
src/lib/content-generator.ts ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ZAI from "z-ai-web-dev-sdk";
2
+ import { Character } from "@prisma/client";
3
+
4
+ interface GenerateSocialPostParams {
5
+ character: Character;
6
+ topic?: string;
7
+ platform: "instagram" | "twitter" | "tiktok" | "onlyfans";
8
+ tone?: string;
9
+ imageContext?: string; // e.g. "a photo of her at the beach looking at the sunset"
10
+ }
11
+
12
+ interface SocialPostResult {
13
+ caption: string;
14
+ hook?: string;
15
+ hashtags?: string[];
16
+ }
17
+
18
+ export async function generateSocialPost({
19
+ character,
20
+ topic,
21
+ platform,
22
+ tone,
23
+ imageContext,
24
+ }: GenerateSocialPostParams): Promise<SocialPostResult> {
25
+ const zai = await ZAI.create();
26
+
27
+ // 1. Build Persona Context (Inspired by ai-virtual-persona-tools)
28
+ const personaContext = `
29
+ You are acting as the AI Influencer: ${character.name}.
30
+ Bio: ${character.shortBio || ""}
31
+ Backstory: ${character.backstory || ""}
32
+ Personality Traits: ${character.traits || ""} ${character.personality || ""}
33
+ Interests: ${character.interests || ""}
34
+ Catchphrases: ${character.catchphrases || ""}
35
+ System Prompt Context: ${character.systemPrompt || ""}
36
+ `.trim();
37
+
38
+ // 2. Build Platform Constraints (Inspired by Automated-Social-Media-Content-Generator)
39
+ let platformRules = "";
40
+ switch (platform) {
41
+ case "twitter":
42
+ platformRules = "Keep it under 280 characters. Be witty, direct, and engaging. Use max 2 hashtags. No emojis unless it fits perfectly.";
43
+ break;
44
+ case "instagram":
45
+ platformRules = "Write an engaging, visually descriptive caption. Include a strong hook in the first sentence. Ask a question at the end to drive comments. Use 5-10 relevant hashtags.";
46
+ break;
47
+ case "tiktok":
48
+ platformRules = "Keep it very short and conversational. Focus on the hook. Use trending TikTok style phrasing (e.g., POV, 'not me...', etc.). Use 3-5 hashtags.";
49
+ break;
50
+ case "onlyfans":
51
+ platformRules = "Write a suggestive, intimate, and exclusive caption. Address the reader directly as 'babe' or 'honey' if it fits the persona. Use emojis to express emotion. No hashtags.";
52
+ break;
53
+ }
54
+
55
+ // 3. Build Actionable Prompt
56
+ const actionPrompt = `
57
+ Task: Write a social media post for ${platform.toUpperCase()}.
58
+ Topic/Context: ${topic ? topic : (imageContext ? `I am posting a picture of: ${imageContext}` : "A casual daily update.")}
59
+ Tone: ${tone || (platform === "onlyfans" ? character.chatPremiumTone : character.chatBasicTone) || "Authentic and engaging"}
60
+
61
+ Rules:
62
+ ${platformRules}
63
+
64
+ Output Format:
65
+ Return a strictly valid JSON object with the following structure:
66
+ {
67
+ "hook": "The first engaging sentence (if applicable)",
68
+ "caption": "The main body of the text, excluding hashtags",
69
+ "hashtags": ["list", "of", "hashtags", "without", "#"]
70
+ }
71
+ `;
72
+
73
+ try {
74
+ const completion = await zai.chat.completions.create({
75
+ messages: [
76
+ { role: "system", content: personaContext },
77
+ { role: "user", content: actionPrompt }
78
+ ],
79
+ response_format: { type: "json_object" },
80
+ temperature: 0.8, // Slightly higher for creativity in marketing
81
+ });
82
+
83
+ const responseContent = completion.choices[0]?.message?.content;
84
+ if (!responseContent) throw new Error("No content generated");
85
+
86
+ const parsed: SocialPostResult = JSON.parse(responseContent);
87
+ return parsed;
88
+ } catch (error) {
89
+ console.error("Error generating social post:", error);
90
+ // Fallback simple caption on failure
91
+ return {
92
+ caption: `Just another day in the life of ${character.name}! ✨`,
93
+ hashtags: [character.name.replace(/\s+/g, ''), 'aiinfluencer'],
94
+ };
95
+ }
96
+ }