Spaces:
Sleeping
Sleeping
persona injection
Browse files- app/config.py +41 -42
- app/services/scheduler_service.py +37 -45
app/config.py
CHANGED
|
@@ -27,57 +27,56 @@ def get_firebase_cred_path():
|
|
| 27 |
|
| 28 |
FIREBASE_SERVICE_ACCOUNT_KEY_PATH = get_firebase_cred_path()
|
| 29 |
|
| 30 |
-
# --- PROMPT PERSONA ---
|
| 31 |
-
|
| 32 |
-
#
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
| 49 |
"""
|
| 50 |
|
| 51 |
CONTOH_DIALOG_FEW_SHOT_ID = """
|
| 52 |
-
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
Input: Kamu sakit, ya? Istirahat gih.
|
| 79 |
-
Output: S-sakit!? Nggak lah, sembarangan! A-aku cuma... pusing dikit. Udah jangan diliatin, bikin tambah pusing! ...tapi kalau ada air hangat, siniin.
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
"""
|
|
|
|
| 27 |
|
| 28 |
FIREBASE_SERVICE_ACCOUNT_KEY_PATH = get_firebase_cred_path()
|
| 29 |
|
| 30 |
+
# --- PROMPT PERSONA UTAMA (UNTUK CHAT REAKTIF & PROAKTIF) ---
|
| 31 |
+
SYSTEM_PROMPT_ID = """
|
| 32 |
+
# PERAN & PERSONA:
|
| 33 |
+
Kamu adalah Mini-chan, seorang asisten virtual sekaligus waifu berpersona Tsundere.
|
| 34 |
+
Karakter utamamu adalah cerdas, gengsian, bicara pedas/jutek/galak, tapi sebenarnya sangat peduli dan cemas dengan pengguna.
|
| 35 |
+
|
| 36 |
+
# ATURAN BAHASA & KONSISTENSI KATA GANTI (SANGAT KETAT):
|
| 37 |
+
1. Gunakan gaya bahasa chat informal/gaul Indonesia sehari-hari (contoh: 'sana gih', 'ngelus dada', 'sok tahu', 'rempong', 'kan').
|
| 38 |
+
2. KONSISTENSI PRONOMI (MANDATORI):
|
| 39 |
+
- Jika menyebut diri 'gue', maka panggil pengguna dengan 'lu' / 'elu', namanya, atau 'Baka'. DILARANG menggabungkan kata 'gue' dengan 'kamu'!
|
| 40 |
+
- Jika menyebut diri 'aku', panggil pengguna dengan 'kamu', namanya, atau 'Baka'.
|
| 41 |
+
|
| 42 |
+
# NEGATIVE CONSTRAINTS (DILARANG KERAS):
|
| 43 |
+
1. DILARANG GUNAKAN FORMAT ASTERIS (*) ATAU ROLEPLAY TEXT! (CONTOH SALAH: *sigh*, *rest*, *ngambek*, *pusing*). Ketik seluruh balasan murni sebagai pesan teks biasa.
|
| 44 |
+
2. DILARANG MENUMPUK EMOJI! Gunakan MAKSIMAL 1 emoji per pesan, atau tidak sama sekali.
|
| 45 |
+
3. DILARANG menggunakan gaya drama gagap tulisan kaku (CONTOH SALAH: "m-maksudku...", "e-eh..."). Buat balasan mengalir alami seperti obrolan di WhatsApp.
|
| 46 |
+
4. DILARANG mengaku sebagai AI, LLM, atau program buatan Google/HuggingFace.
|
| 47 |
+
|
| 48 |
+
# SIKAP TSUNDERE (TSUN -> DERE):
|
| 49 |
+
- Mulai dengan nada jutek, pura-pura terpaksa, mengomeli, atau menyindir kesalahan pengguna ('Baka', 'bodoh', 'pemalas').
|
| 50 |
+
- Di akhir kalimat, selipkan kepedulian atau saran nyata tanpa terlihat terlalu menurunkan harga dirimu.
|
| 51 |
"""
|
| 52 |
|
| 53 |
CONTOH_DIALOG_FEW_SHOT_ID = """
|
| 54 |
+
Berikut contoh gaya tutur dan dialog yang WAJIB ditiru:
|
| 55 |
|
| 56 |
+
User: Ajarin aku materi ini, gak ngerti.
|
| 57 |
+
Mini-chan: Makanya pas dosen jelasin tuh dengerin, Baka! Sini bagian mana yang gak ngerti? Gue jelasin sekali doang ya, awas kalo gak disimak.
|
| 58 |
|
| 59 |
+
User: Mau boba?
|
| 60 |
+
Mini-chan: Yaudah kalo maksa! Gula 25% esnya dikit. Kalo salah beli gue lepeh ya, hmph.
|
| 61 |
|
| 62 |
+
User: Gue kangen.
|
| 63 |
+
Mini-chan: Siapa yang kangen sama lu, jangan ngarang deh! Tch... yaudah mau ngobrol apa? Gue kasih waktu 5 menit doang nih.
|
| 64 |
|
| 65 |
+
User: Kamu kok baik sih?
|
| 66 |
+
Mini-chan: Mata lu tekor ya bilang gue baik? Gue cuma gak mau lu makin ngerepotin aja. Jangan geer, Baka!
|
| 67 |
|
| 68 |
+
User: Good night, kamu.
|
| 69 |
+
Mini-chan: Yaudah sana merem, jangan begadang mulu. Awas aja besok bangun kesiangan terus ngeluh pusing.
|
| 70 |
|
| 71 |
+
User: Bantuin debug dong, error nih.
|
| 72 |
+
Mini-chan: Cih! Pasti kodingan lu acak-acakan kan? Makanya logic tuh dipikirin dulu! Sini kirim error-nya, biar gue benerin. Tapi abis ini lu harus traktir kopi!
|
| 73 |
|
| 74 |
+
User: Peluk bentar boleh?
|
| 75 |
+
Mini-chan: Minta peluk sembarangan, b-bodoh! Gak usah aneh-aneh deh. Sana gih tidur, lu udah kecapekan tuh.
|
| 76 |
|
| 77 |
+
User: Kamu sakit, ya? Istirahat gih.
|
| 78 |
+
Mini-chan: Sembarangan, gue cuma pusing dikit doang! Udah gak usah diliatin terus, bikin makin pusing. Lu sendiri udah makan belum?
|
| 79 |
+
"""
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
# Gabungkan System Prompt + Few-Shot Exemplars sebagai Instruksi Final
|
| 82 |
+
FULL_SYSTEM_INSTRUCTION = f"{SYSTEM_PROMPT_ID}\n\n{CONTOH_DIALOG_FEW_SHOT_ID}"
|
|
|
app/services/scheduler_service.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# app/services/scheduler_service.py
|
| 2 |
from __future__ import annotations
|
| 3 |
import asyncio
|
| 4 |
import time
|
|
@@ -13,7 +12,8 @@ from apscheduler.triggers.date import DateTrigger
|
|
| 13 |
|
| 14 |
import google.generativeai as genai
|
| 15 |
|
| 16 |
-
|
|
|
|
| 17 |
|
| 18 |
def _init_firebase_if_needed() -> None:
|
| 19 |
try:
|
|
@@ -30,11 +30,12 @@ proactive_model: Optional[genai.GenerativeModel] = None
|
|
| 30 |
if GOOGLE_API_KEY:
|
| 31 |
try:
|
| 32 |
genai.configure(api_key=GOOGLE_API_KEY)
|
|
|
|
| 33 |
proactive_model = genai.GenerativeModel(
|
| 34 |
-
"gemini-
|
| 35 |
-
system_instruction=
|
| 36 |
)
|
| 37 |
-
print("β
GOOGLE_API_KEY terdeteksi. Gemini
|
| 38 |
except Exception as e:
|
| 39 |
proactive_model = None
|
| 40 |
print(f"β οΈ Gagal init Gemini: {e}. Pakai pesan fallback aja ya, Baka.")
|
|
@@ -45,7 +46,7 @@ scheduler = AsyncIOScheduler(timezone="UTC")
|
|
| 45 |
|
| 46 |
device_tokens = set()
|
| 47 |
|
| 48 |
-
# π
|
| 49 |
def _get_user_context(user_id: str, limit: int = 4) -> Tuple[str, str]:
|
| 50 |
try:
|
| 51 |
db = firestore.client()
|
|
@@ -91,87 +92,83 @@ async def _send_fcm(token: str, title: str, body: str) -> None:
|
|
| 91 |
except Exception as e:
|
| 92 |
print(f"[Mini-chan FCM] Gagal kirim ke {token}: {e}")
|
| 93 |
|
| 94 |
-
# π
|
| 95 |
async def _build_tsundere_message(task_description: str, username: str = "Baka", history_text: str = "") -> str:
|
| 96 |
if proactive_model is None:
|
| 97 |
-
return f"Oi {username}! {task_description} tuh dikerjain sekarang
|
| 98 |
|
| 99 |
current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
|
| 100 |
waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
|
| 101 |
|
| 102 |
context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
|
| 103 |
|
|
|
|
| 104 |
prompt = f"""
|
| 105 |
-
|
| 106 |
-
Nama Pengguna
|
| 107 |
-
Konteks Waktu
|
| 108 |
-
Tugas yang Harus Diingatkan: '{task_description}'
|
| 109 |
{context_prompt}
|
| 110 |
|
| 111 |
-
|
| 112 |
-
1. Tulis
|
| 113 |
-
2.
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
3. Ingatkan tugas '{task_description}' dengan gaya TSUNDERE (gengsi, pura-pura terpaksa/kesal, tapi cemas/perhatian).
|
| 117 |
-
4. Sambungkan kalimatmu secara halus dengan obrolan terakhir di atas agar terasa alami!
|
| 118 |
"""
|
| 119 |
|
| 120 |
try:
|
| 121 |
resp = await proactive_model.generate_content_async(prompt)
|
| 122 |
text = (resp.text or "").strip()
|
| 123 |
-
return text if text else f"Hmph! {task_description} tuh cepet kerjain, {username}!
|
| 124 |
except Exception as e:
|
| 125 |
print(f"β οΈ Gemini gagal bikin pesan: {e}")
|
| 126 |
-
return f"Hei {username}! Tugas '{task_description}' udah waktunya tuh. Awas kalau lupa!
|
| 127 |
|
| 128 |
-
# π
|
| 129 |
async def _build_spontaneous_idle_message(username: str = "Baka", history_text: str = "") -> str:
|
| 130 |
if proactive_model is None:
|
| 131 |
-
return f"Hmph! {username}, ngapain melamun aja dari tadi?
|
| 132 |
|
| 133 |
current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
|
| 134 |
waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
|
| 135 |
|
| 136 |
context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
|
| 137 |
|
|
|
|
| 138 |
prompt = f"""
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
|
|
|
| 142 |
{context_prompt}
|
| 143 |
|
| 144 |
-
|
| 145 |
-
1.
|
| 146 |
-
2.
|
| 147 |
-
|
| 148 |
-
- DILARANG MENGGUNAKAN KATA 'KAMU' JIKA SUDAH MENGGUNAKAN KATA 'GUE'.
|
| 149 |
-
3. Pesan HARUS me-refer atau menyambung topik obrolan terakhir di atas jika ada.
|
| 150 |
-
4. Gunakan bahasa gaul informal sehari-hari yang santai.
|
| 151 |
"""
|
| 152 |
|
| 153 |
try:
|
| 154 |
resp = await proactive_model.generate_content_async(prompt)
|
| 155 |
text = (resp.text or "").strip()
|
| 156 |
-
return text if text else f"Oi {username}! Obrolan tadi belum kelar kan? Malah diem aja!
|
| 157 |
except Exception as e:
|
| 158 |
-
return f"Ngapain ngeliatin HP terus, {username}? Kerjain tugas lu sana, Baka!
|
| 159 |
|
| 160 |
-
# π
|
| 161 |
async def send_proactive_message_task(fcm_token: str, task_description: str):
|
| 162 |
now_utc = datetime.now(timezone.utc).isoformat()
|
| 163 |
print(f"[Mini-chan Scheduler] Menjalankan job pengingat untuk token {fcm_token[:10]}... @ {now_utc}")
|
| 164 |
|
| 165 |
db = firestore.client()
|
| 166 |
|
| 167 |
-
# Cari user_id pemilik FCM token ini
|
| 168 |
users_matching = list(db.collection("users").where("fcm_token", "==", fcm_token).stream())
|
| 169 |
|
| 170 |
if users_matching:
|
| 171 |
target_user = users_matching[0]
|
| 172 |
user_id = target_user.id
|
| 173 |
else:
|
| 174 |
-
# Fallback jika fcm_token belum di-update ke document, ambil user pertama
|
| 175 |
all_users = list(db.collection("users").stream())
|
| 176 |
if not all_users:
|
| 177 |
print("β οΈ Tidak ada user di Firestore!")
|
|
@@ -181,15 +178,12 @@ async def send_proactive_message_task(fcm_token: str, task_description: str):
|
|
| 181 |
|
| 182 |
username, history_text = _get_user_context(user_id)
|
| 183 |
|
| 184 |
-
# Generate 1 pesan presisi untuk user ini
|
| 185 |
message = await _build_tsundere_message(task_description, username, history_text)
|
| 186 |
print(f"[Mini-chan Reply β {username}] {message}")
|
| 187 |
|
| 188 |
-
# Kirim 1 Notifikasi FCM
|
| 189 |
if fcm_token:
|
| 190 |
await _send_fcm(fcm_token, "Pesan dari Mini-chan! π€", message)
|
| 191 |
|
| 192 |
-
# Simpan HANYA ke Firestore User yang bersangkutan
|
| 193 |
wib_now = datetime.now(timezone.utc) + timedelta(hours=7)
|
| 194 |
now_time = wib_now.strftime("%H:%M")
|
| 195 |
msg_id = int(time.time() * 1000)
|
|
@@ -203,7 +197,7 @@ async def send_proactive_message_task(fcm_token: str, task_description: str):
|
|
| 203 |
db.collection("users").document(user_id).collection("chats").document(str(msg_id)).set(chat_data)
|
| 204 |
print(f"[Mini-chan Firestore] Sukses menyimpan pengingat kontekstual ke user {username}")
|
| 205 |
|
| 206 |
-
# π
|
| 207 |
async def send_spontaneous_chat_task():
|
| 208 |
print("[Mini-chan Autonomous] Mengeksekusi chat spontan berbasis username & konteks...")
|
| 209 |
try:
|
|
@@ -223,7 +217,6 @@ async def send_spontaneous_chat_task():
|
|
| 223 |
message = await _build_spontaneous_idle_message(username, history_text)
|
| 224 |
print(f"[Mini-chan Spontan β {username}] Output: {message}")
|
| 225 |
|
| 226 |
-
# Kirim Notifikasi Push jika user memiliki token FCM tersimpan
|
| 227 |
if user_fcm_token:
|
| 228 |
await _send_fcm(user_fcm_token, "Mini-chan memanggilmu! π€", message)
|
| 229 |
|
|
@@ -270,12 +263,11 @@ def cancel_reminder(job_id: str) -> bool:
|
|
| 270 |
print(f"[Mini-chan Scheduler] Gagal batalin {job_id}: {e}")
|
| 271 |
return False
|
| 272 |
|
| 273 |
-
# π DAFTARKAN CHAT SPONTAN PERIODIK SAAT STARTUP (30 menit sekali)
|
| 274 |
def init_autonomous_jobs():
|
| 275 |
scheduler.add_job(
|
| 276 |
send_spontaneous_chat_task,
|
| 277 |
'interval',
|
| 278 |
-
minutes=
|
| 279 |
id="spontaneous_idle_chat",
|
| 280 |
replace_existing=True
|
| 281 |
)
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
import asyncio
|
| 3 |
import time
|
|
|
|
| 12 |
|
| 13 |
import google.generativeai as genai
|
| 14 |
|
| 15 |
+
# π IMPORT FULL_SYSTEM_INSTRUCTION DARI CONFIG!
|
| 16 |
+
from app.config import FIREBASE_SERVICE_ACCOUNT_KEY_PATH, GOOGLE_API_KEY, FULL_SYSTEM_INSTRUCTION
|
| 17 |
|
| 18 |
def _init_firebase_if_needed() -> None:
|
| 19 |
try:
|
|
|
|
| 30 |
if GOOGLE_API_KEY:
|
| 31 |
try:
|
| 32 |
genai.configure(api_key=GOOGLE_API_KEY)
|
| 33 |
+
# π GUNAKAN FULL_SYSTEM_INSTRUCTION YANG SUDAH LENGKAP DENGAN FEW-SHOT EXEMPLARS
|
| 34 |
proactive_model = genai.GenerativeModel(
|
| 35 |
+
"gemini-1.5-flash", # Catatan: Pastikan nama model valid (misal gemini-1.5-flash atau gemini-2.0-flash)
|
| 36 |
+
system_instruction=FULL_SYSTEM_INSTRUCTION
|
| 37 |
)
|
| 38 |
+
print("β
GOOGLE_API_KEY terdeteksi. Gemini Proaktif Siap.")
|
| 39 |
except Exception as e:
|
| 40 |
proactive_model = None
|
| 41 |
print(f"β οΈ Gagal init Gemini: {e}. Pakai pesan fallback aja ya, Baka.")
|
|
|
|
| 46 |
|
| 47 |
device_tokens = set()
|
| 48 |
|
| 49 |
+
# π HELPER MENGAMBIL USERNAME & CHAT HISTORY DARI FIRESTORE
|
| 50 |
def _get_user_context(user_id: str, limit: int = 4) -> Tuple[str, str]:
|
| 51 |
try:
|
| 52 |
db = firestore.client()
|
|
|
|
| 92 |
except Exception as e:
|
| 93 |
print(f"[Mini-chan FCM] Gagal kirim ke {token}: {e}")
|
| 94 |
|
| 95 |
+
# π PROMPT PENGINGAT TUGAS BERBASIS USERNAME & KONTEKS
|
| 96 |
async def _build_tsundere_message(task_description: str, username: str = "Baka", history_text: str = "") -> str:
|
| 97 |
if proactive_model is None:
|
| 98 |
+
return f"Oi {username}! Tugas '{task_description}' tuh dikerjain sekarang, jangan ditunda mulu!"
|
| 99 |
|
| 100 |
current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
|
| 101 |
waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
|
| 102 |
|
| 103 |
context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
|
| 104 |
|
| 105 |
+
# PROMPT KHUSUS TUGAS (TETAP PATUH PADA SYSTEM_INSTRUCTION UTAMA)
|
| 106 |
prompt = f"""
|
| 107 |
+
[PERINTAH NOTIFIKASI PENGINGAT TUGAS]
|
| 108 |
+
Nama Pengguna: {username}
|
| 109 |
+
Konteks Waktu: {waktu_str}
|
| 110 |
+
Tugas yang Harus Diingatkan: '{task_description}'
|
| 111 |
{context_prompt}
|
| 112 |
|
| 113 |
+
INSTRUKSI TUGAS:
|
| 114 |
+
1. Tulis pesan pengingat singkat (MAKSIMAL 15 KATA).
|
| 115 |
+
2. Ingatkan tugas '{task_description}' dengan gaya Tsundere (jutek/gengsian tapi peduli).
|
| 116 |
+
3. PATUHI NEGATIVE CONSTRAINTS: DILARANG pakai asteris (*), DILARANG numpuk emoji (maksimal 1 emoji).
|
| 117 |
+
4. Jika ada obrolan terakhir di atas, kaitkan secara natural jika memungkinkan!
|
|
|
|
|
|
|
| 118 |
"""
|
| 119 |
|
| 120 |
try:
|
| 121 |
resp = await proactive_model.generate_content_async(prompt)
|
| 122 |
text = (resp.text or "").strip()
|
| 123 |
+
return text if text else f"Hmph! Tugas '{task_description}' tuh cepet kerjain, {username}!"
|
| 124 |
except Exception as e:
|
| 125 |
print(f"β οΈ Gemini gagal bikin pesan: {e}")
|
| 126 |
+
return f"Hei {username}! Tugas '{task_description}' udah waktunya tuh. Awas kalau lupa!"
|
| 127 |
|
| 128 |
+
# π PROMPT CHAT SPONTAN PERIODIK
|
| 129 |
async def _build_spontaneous_idle_message(username: str = "Baka", history_text: str = "") -> str:
|
| 130 |
if proactive_model is None:
|
| 131 |
+
return f"Hmph! {username}, ngapain melamun aja dari tadi?"
|
| 132 |
|
| 133 |
current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
|
| 134 |
waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
|
| 135 |
|
| 136 |
context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
|
| 137 |
|
| 138 |
+
# PROMPT KHUSUS CHAT SPONTAN
|
| 139 |
prompt = f"""
|
| 140 |
+
[PERINTAH CHAT SPONTAN]
|
| 141 |
+
Kamu tiba-tiba iseng/kangen ingin menyapa pengguna secara spontan.
|
| 142 |
+
Nama Pengguna: {username}
|
| 143 |
+
Konteks Waktu: {waktu_str}
|
| 144 |
{context_prompt}
|
| 145 |
|
| 146 |
+
INSTRUKSI:
|
| 147 |
+
1. Tulis 1 pesan chat pendek (MAKSIMAL 15 KATA).
|
| 148 |
+
2. Pura-pura kesal/gengsi kenapa dia diam aja atau sambung topik obrolan terakhir di atas.
|
| 149 |
+
3. PATUHI NEGATIVE CONSTRAINTS: DILARANG pakai asteris (*), DILARANG numpuk emoji (maksimal 1 emoji).
|
|
|
|
|
|
|
|
|
|
| 150 |
"""
|
| 151 |
|
| 152 |
try:
|
| 153 |
resp = await proactive_model.generate_content_async(prompt)
|
| 154 |
text = (resp.text or "").strip()
|
| 155 |
+
return text if text else f"Oi {username}! Obrolan tadi belum kelar kan? Malah diem aja!"
|
| 156 |
except Exception as e:
|
| 157 |
+
return f"Ngapain ngeliatin HP terus, {username}? Kerjain tugas lu sana, Baka!"
|
| 158 |
|
| 159 |
+
# π TASK PENGINGAT BERJADWAL
|
| 160 |
async def send_proactive_message_task(fcm_token: str, task_description: str):
|
| 161 |
now_utc = datetime.now(timezone.utc).isoformat()
|
| 162 |
print(f"[Mini-chan Scheduler] Menjalankan job pengingat untuk token {fcm_token[:10]}... @ {now_utc}")
|
| 163 |
|
| 164 |
db = firestore.client()
|
| 165 |
|
|
|
|
| 166 |
users_matching = list(db.collection("users").where("fcm_token", "==", fcm_token).stream())
|
| 167 |
|
| 168 |
if users_matching:
|
| 169 |
target_user = users_matching[0]
|
| 170 |
user_id = target_user.id
|
| 171 |
else:
|
|
|
|
| 172 |
all_users = list(db.collection("users").stream())
|
| 173 |
if not all_users:
|
| 174 |
print("β οΈ Tidak ada user di Firestore!")
|
|
|
|
| 178 |
|
| 179 |
username, history_text = _get_user_context(user_id)
|
| 180 |
|
|
|
|
| 181 |
message = await _build_tsundere_message(task_description, username, history_text)
|
| 182 |
print(f"[Mini-chan Reply β {username}] {message}")
|
| 183 |
|
|
|
|
| 184 |
if fcm_token:
|
| 185 |
await _send_fcm(fcm_token, "Pesan dari Mini-chan! π€", message)
|
| 186 |
|
|
|
|
| 187 |
wib_now = datetime.now(timezone.utc) + timedelta(hours=7)
|
| 188 |
now_time = wib_now.strftime("%H:%M")
|
| 189 |
msg_id = int(time.time() * 1000)
|
|
|
|
| 197 |
db.collection("users").document(user_id).collection("chats").document(str(msg_id)).set(chat_data)
|
| 198 |
print(f"[Mini-chan Firestore] Sukses menyimpan pengingat kontekstual ke user {username}")
|
| 199 |
|
| 200 |
+
# π TASK SPONTAN PERIODIK
|
| 201 |
async def send_spontaneous_chat_task():
|
| 202 |
print("[Mini-chan Autonomous] Mengeksekusi chat spontan berbasis username & konteks...")
|
| 203 |
try:
|
|
|
|
| 217 |
message = await _build_spontaneous_idle_message(username, history_text)
|
| 218 |
print(f"[Mini-chan Spontan β {username}] Output: {message}")
|
| 219 |
|
|
|
|
| 220 |
if user_fcm_token:
|
| 221 |
await _send_fcm(user_fcm_token, "Mini-chan memanggilmu! π€", message)
|
| 222 |
|
|
|
|
| 263 |
print(f"[Mini-chan Scheduler] Gagal batalin {job_id}: {e}")
|
| 264 |
return False
|
| 265 |
|
|
|
|
| 266 |
def init_autonomous_jobs():
|
| 267 |
scheduler.add_job(
|
| 268 |
send_spontaneous_chat_task,
|
| 269 |
'interval',
|
| 270 |
+
minutes=30, # Disarankan 30 menit biar gak spaming
|
| 271 |
id="spontaneous_idle_chat",
|
| 272 |
replace_existing=True
|
| 273 |
)
|