{
  "name": "Telegram Expense Tracker",
  "nodes": [
    {
      "id": "telegram-trigger",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        500
      ],
      "parameters": {
        "updates": [
          "message",
          "callback_query"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      }
    },
    {
      "id": "router-node",
      "name": "Router",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        500
      ],
      "parameters": {
        "jsCode": "const update = $json;\nconst isCallback = !!update.callback_query;\nlet chatId, text, callbackId, callbackData, messageId;\nif (isCallback) {\n  const cb = update.callback_query;\n  chatId = String(cb.message.chat.id);\n  text = '';\n  callbackId = cb.id;\n  callbackData = cb.data || '';\n  messageId = cb.message.message_id;\n} else {\n  const msg = update.message || {};\n  chatId = String(msg.chat?.id || '');\n  text = (msg.text || '').trim();\n  callbackId = '';\n  callbackData = '';\n  messageId = msg.message_id;\n}\nif (!chatId) return [{ json: { action: 'noop' } }];\nconst sd = $getWorkflowStaticData('global');\nif (!sd.users) sd.users = {};\nif (!sd.users[chatId]) sd.users[chatId] = {\n  state: 'lang_select',\n  lang: 'en',\n  expense: { name: '', isWeight: false, qty: 0 },\n  shopping: { listName: '', selected: [], customProducts: [], msgId: 0 },\n  managing: { listIdx: -1 },\n  lists: []\n};\nconst u = sd.users[chatId];\nif (!u.lists) u.lists = [];\nconst lists = u.lists;\nconst STRINGS = {\n  en: {\n    lang_prompt: '🌐 Choose your language / בחר שפה:',\n    lang_btn_en: '🇬🇧 English',\n    lang_btn_he: '🇮🇱 עברית',\n    main_welcome: '👋 What would you like to do?',\n    main_btn_expense: '📝 Log Expense',\n    main_btn_shopping: '🛒 New Shopping List',\n    main_btn_lists: '📋 My Lists',\n    expense_ask_name: '🛍️ What did you buy? (type the product name)',\n    expense_ask_type: '📦 How is *{name}* sold?',\n    expense_btn_weight: '⚖️ By Weight (kg)',\n    expense_btn_qty: '🔢 By Quantity',\n    expense_ask_kg: '⚖️ How many kg of *{name}*?',\n    expense_ask_price_kg: '💰 Price per kg for *{name}* ({qty} kg)?',\n    expense_ask_qty: '🔢 How many *{name}*?',\n    expense_ask_price_item: '💰 Price per item for *{name}* ({qty} items)?',\n    expense_invalid_number: '❌ Please send a valid number.',\n    expense_success: '✅ *{name}* logged\\n\\n💰 {detail} = ₪{amount}\\n📁 {category}\\n📅 {date}',\n    btn_log_another: '📝 Log Another',\n    btn_main_menu: '🏠 Menu',\n    shopping_ask_name: '🛒 Enter a name for your shopping list:\\n(e.g. Weekly Shop, BBQ Party)',\n    shopping_picker_title: '🛒 Select products for *{listName}*:\\nTap to select/deselect, then Confirm.',\n    shopping_confirm_btn: '✅ Done',\n    shopping_empty_error: 'Please select at least one product!',\n    shopping_saved: '✅ Shopping list *{listName}* saved!\\n\\n{items}',\n    shopping_add_product_btn: '➕ Add product',\n    shopping_add_product_ask: '✏️ Type the product name to add:',\n    lists_empty: '📋 No saved lists yet. Create one first!',\n    lists_title: '📋 Your shopping lists:\\n_(active first, completed below)_',\n    list_manage_title: '🛒 *{listName}* — tap to mark as bought:',\n    list_back_btn: '⬅️ Back to lists',\n    list_group_title: '📋 *{name}* — select a list:',\n    btn_home: '🏠 Main Menu'\n  },\n  he: {\n    lang_prompt: '🌐 בחר שפה:',\n    lang_btn_en: '🇬🇧 English',\n    lang_btn_he: '🇮🇱 עברית',\n    main_welcome: '👋 מה תרצה לעשות?',\n    main_btn_expense: '📝 הוסף הוצאה',\n    main_btn_shopping: '🛒 רשימת קניות חדשה',\n    main_btn_lists: '📋 הרשימות שלי',\n    expense_ask_name: '🛍️ מה קנית? (הכנס שם מוצר)',\n    expense_ask_type: '📦 איך נמכר *{name}*?',\n    expense_btn_weight: '⚖️ לפי משקל (kg)',\n    expense_btn_qty: '🔢 לפי כמות',\n    expense_ask_kg: '⚖️ כמה kg של *{name}*?',\n    expense_ask_price_kg: '💰 מחיר לkg של *{name}* ({qty} kg)?',\n    expense_ask_qty: '🔢 כמה יחידות של *{name}*?',\n    expense_ask_price_item: '💰 מחיר ליחידה של *{name}* ({qty} יחידות)?',\n    expense_invalid_number: '❌ אנא שלח מספר תקין.',\n    expense_success: '✅ *{name}* נרשם\\n\\n💰 {detail} = ₪{amount}\\n📁 {category}\\n📅 {date}',\n    btn_log_another: '📝 הוסף עוד',\n    btn_main_menu: '🏠 תפריט',\n    shopping_ask_name: '🛒 הכנס שם לרשימת הקניות:\\n(למשל: קניות שבועיות)',\n    shopping_picker_title: '🛒 בחר מוצרים עבור *{listName}*:\\nלחץ לבחירה/ביטול, ואז אישור.',\n    shopping_confirm_btn: '✅ סיום',\n    shopping_empty_error: 'אנא בחר לפחות מוצר אחד!',\n    shopping_saved: '✅ רשימת הקניות *{listName}* נשמרה!\\n\\n{items}',\n    shopping_add_product_btn: '➕ הוסף מוצר',\n    shopping_add_product_ask: '✏️ הכנס את שם המוצר להוספה:',\n    lists_empty: '📋 אין רשימות עדיין. צור רשימה ראשונה!',\n    lists_title: '📋 הרשימות שלך:\\n_(פעילות למעלה, הושלמו למטה)_',\n    list_manage_title: '🛒 *{listName}* — לחץ לסימון כנקנה:',\n    list_back_btn: '⬅️ חזרה לרשימות',\n    list_group_title: '📋 *{name}* — בחר רשימה:',\n    btn_home: '🏠 תפריט ראשי'\n  }\n};\nfunction t(key, vars) {\n  let s = (STRINGS[u.lang || 'en'] || STRINGS.en)[key] || STRINGS.en[key] || key;\n  if (vars) Object.keys(vars).forEach(k => { s = s.replace(new RegExp('{' + k + '}', 'g'), String(vars[k])); });\n  return s;\n}\nconst PRODUCTS = [\n  'Milk','Bread','Eggs','Butter','Cheese','Yogurt',\n  'Tomatoes','Cucumbers','Onions','Potatoes','Carrots','Peppers',\n  'Chicken','Beef','Fish','Rice','Pasta','Oil','Sugar','Salt',\n  'Water','Juice','Coffee','Tea','Soap','Shampoo','Detergent'\n];\nconst PRODUCT_HE = {\n  'Milk':'חלב','Bread':'לחם','Eggs':'ביצים','Butter':'חמאה','Cheese':'גבינה','Yogurt':'יוגורט',\n  'Tomatoes':'עגבניות','Cucumbers':'מלפפונים','Onions':'בצל','Potatoes':'תפוחי אדמה','Carrots':'גזר','Peppers':'פלפלים',\n  'Chicken':'עוף','Beef':'בקר','Fish':'דגים','Rice':'אורז','Pasta':'פסטה','Oil':'שמן','Sugar':'סוכר','Salt':'מלח',\n  'Water':'מים','Juice':'מיץ','Coffee':'קפה','Tea':'תה','Soap':'סבון','Shampoo':'שמפו','Detergent':'אבקת כביסה'\n};\nfunction productLabel(p) {\n  return (u.lang === 'he' && PRODUCT_HE[p]) ? PRODUCT_HE[p] : p;\n}\nfunction buildPickerKb(selected, customProducts) {\n  const custom = customProducts || [];\n  const allProducts = [...PRODUCTS, ...custom];\n  const rows = [];\n  for (let i = 0; i < allProducts.length; i += 3) {\n    rows.push(allProducts.slice(i, i+3).map(p => ({\n      text: (selected.includes(p) ? '✅ ' : '⬜ ') + productLabel(p),\n      callback_data: 'toggle_' + p\n    })));\n  }\n  rows.push([{ text: t('shopping_add_product_btn'), callback_data: 'shopping_add_product' }]);\n  rows.push([{ text: t('shopping_confirm_btn'), callback_data: 'shopping_confirm' }]);\n  return { inline_keyboard: rows };\n}\nfunction buildListsKb(userLists) {\n  if (!userLists || !userLists.length) return null;\n  const groupMap = {};\n  userLists.forEach((l, i) => {\n    if (!groupMap[l.listName]) groupMap[l.listName] = [];\n    groupMap[l.listName].push({ ...l, _idx: i });\n  });\n  const groupNames = Object.keys(groupMap);\n  groupNames.sort((a, b) => {\n    const aActive = groupMap[a].some(l => l.items.some(item => item.status === 'Pending'));\n    const bActive = groupMap[b].some(l => l.items.some(item => item.status === 'Pending'));\n    if (aActive !== bActive) return aActive ? -1 : 1;\n    const aDate = groupMap[a].map(l => l.date).sort().slice(-1)[0];\n    const bDate = groupMap[b].map(l => l.date).sort().slice(-1)[0];\n    return bDate.localeCompare(aDate);\n  });\n  if (!u.managing) u.managing = { listIdx: -1 };\n  u.managing.groups = groupNames;\n  const rows = groupNames.map((name, gi) => {\n    const group = groupMap[name];\n    const isActive = group.some(l => l.items.some(item => item.status === 'Pending'));\n    const icon = isActive ? '🔄' : '✅';\n    const pending = group.reduce((s, l) => s + l.items.filter(item => item.status === 'Pending').length, 0);\n    const total = group.reduce((s, l) => s + l.items.length, 0);\n    const countSuffix = isActive ? ` (${pending}/${total})` : '';\n    const multiSuffix = group.length > 1 ? ` · ${group.length}` : '';\n    return [{ text: `${icon} ${name}${countSuffix}${multiSuffix}`, callback_data: 'grp_' + gi }];\n  });\n  rows.push([{ text: t('btn_home'), callback_data: 'menu_main_reset' }]);\n  return { inline_keyboard: rows };\n}\nfunction buildGroupKb(groupLists) {\n  const sorted = [...groupLists].sort((a, b) => b.date.localeCompare(a.date));\n  const rows = sorted.map(l => {\n    const isActive = l.items.some(item => item.status === 'Pending');\n    const icon = isActive ? '🔄' : '✅';\n    const pending = l.items.filter(item => item.status === 'Pending').length;\n    const total = l.items.length;\n    const label = isActive ? `${icon} ${l.date} (${pending}/${total})` : `${icon} ${l.date}`;\n    return [{ text: label, callback_data: 'select_list_' + l._idx }];\n  });\n  rows.push([{ text: t('list_back_btn'), callback_data: 'menu_lists' }]);\n  return { inline_keyboard: rows };\n}\nfunction buildItemPickerKb(list, listIdx) {\n  const rows = [];\n  for (let i = 0; i < list.items.length; i += 2) {\n    rows.push(list.items.slice(i, i+2).map(item => ({\n      text: (item.status === 'Purchased' ? '✅ ' : '⬜ ') + productLabel(item.product),\n      callback_data: 'lit_' + listIdx + '_' + item.product\n    })));\n  }\n  rows.push([{ text: t('list_back_btn'), callback_data: 'menu_lists' }]);\n  return { inline_keyboard: rows };\n}\nfunction detectCategory(name) {\n  const tl = name.toLowerCase();\n  if (/coffee|tea|cafe|lunch|dinner|breakfast|restaurant|pizza|burger|food|sushi|meal|snack|drink|juice|pub|bar|takeaway/.test(tl)) return 'Food & Drink';\n  if (/gas|fuel|uber|lyft|taxi|cab|bus|train|metro|subway|parking|toll|petrol|waze|bolt/.test(tl)) return 'Transport';\n  if (/grocery|groceries|supermarket|market|milk|bread|eggs|butter|cheese|yogurt|tomato|cucumber|onion|potato|carrot|chicken|beef|fish|rice|pasta|oil|sugar|salt/.test(tl)) return 'Groceries';\n  if (/netflix|spotify|cinema|movie|game|steam|hbo|disney|prime|entertainment/.test(tl)) return 'Entertainment';\n  if (/pharmacy|doctor|medicine|gym|fitness|health|dental|hospital|vitamin|clinic/.test(tl)) return 'Health';\n  if (/rent|mortgage|electricity|water|internet|wifi|insurance|utility/.test(tl)) return 'Bills & Utilities';\n  if (/clothes|clothing|shoes|mall|zara|shopping/.test(tl)) return 'Shopping';\n  if (/phone|laptop|computer|software|tech|samsung|gadget/.test(tl)) return 'Tech';\n  return 'Other';\n}\nconst now = new Date();\nconst date = now.toISOString().split('T')[0];\nconst time = now.toTimeString().substring(0, 8);\nconst LANG_KB = { inline_keyboard: [[\n  { text: STRINGS.en.lang_btn_en, callback_data: 'lang_en' },\n  { text: STRINGS.he.lang_btn_he, callback_data: 'lang_he' }\n]] };\nconst mainKb = () => ({ inline_keyboard: [\n  [{ text: t('main_btn_expense'), callback_data: 'menu_expense' }, { text: t('main_btn_shopping'), callback_data: 'menu_shopping' }],\n  [{ text: t('main_btn_lists'), callback_data: 'menu_lists' }]\n] });\nif (!isCallback && (text === '/start' || text === '/menu')) {\n  u.state = 'lang_select';\n  return [{ json: { action: 'send_kb', chatId, callbackId, text: STRINGS.en.lang_prompt, keyboard: LANG_KB } }];\n}\nif (isCallback) {\n  if (callbackData === 'lang_en') {\n    u.lang = 'en'; u.state = 'main';\n    return [{ json: { action: 'send_kb', chatId, callbackId, text: t('main_welcome'), keyboard: mainKb() } }];\n  }\n  if (callbackData === 'lang_he') {\n    u.lang = 'he'; u.state = 'main';\n    return [{ json: { action: 'send_kb', chatId, callbackId, text: t('main_welcome'), keyboard: mainKb() } }];\n  }\n  if (callbackData === 'menu_expense') {\n    u.state = 'expense_name';\n    u.expense = { name: '', isWeight: false, qty: 0 };\n    return [{ json: { action: 'send_text', chatId, callbackId, text: t('expense_ask_name') } }];\n  }\n  if (callbackData === 'menu_shopping') {\n    u.state = 'shopping_name';\n    u.shopping = { listName: '', selected: [], customProducts: [], msgId: 0 };\n    return [{ json: { action: 'send_text', chatId, callbackId, text: t('shopping_ask_name') } }];\n  }\n  if (callbackData === 'menu_main_reset') {\n    u.state = 'main';\n    return [{ json: { action: 'send_kb', chatId, callbackId, text: t('main_welcome'), keyboard: mainKb() } }];\n  }\n  if (callbackData === 'menu_lists') {\n    u.state = 'main';\n    if (!lists.length) {\n      return [{ json: { action: 'send_kb', chatId, callbackId, text: t('lists_empty'), keyboard: { inline_keyboard: [[{ text: t('btn_home'), callback_data: 'menu_main_reset' }]] } } }];\n    }\n    return [{ json: { action: 'send_kb', chatId, callbackId, text: t('lists_title'), keyboard: buildListsKb(lists) } }];\n  }\n  if (callbackData.startsWith('grp_')) {\n    const gi = parseInt(callbackData.slice('grp_'.length));\n    const groupNames = u.managing && u.managing.groups ? u.managing.groups : [];\n    const groupName = groupNames[gi];\n    if (!groupName) return [{ json: { action: 'ack_callback', chatId, callbackId, toast: '' } }];\n    const groupLists = lists.map((l, i) => ({ ...l, _idx: i })).filter(l => l.listName === groupName);\n    if (groupLists.length === 1) {\n      const list = lists[groupLists[0]._idx];\n      u.managing.listIdx = groupLists[0]._idx;\n      u.state = 'list_manage';\n      return [{ json: { action: 'send_picker', chatId, callbackId, listName: list.listName,\n        pickerText: t('list_manage_title', { listName: list.listName }),\n        keyboard: buildItemPickerKb(list, groupLists[0]._idx) } }];\n    }\n    return [{ json: { action: 'send_kb', chatId, callbackId,\n      text: t('list_group_title', { name: groupName }),\n      keyboard: buildGroupKb(groupLists) } }];\n  }\n  if (callbackData.startsWith('select_list_')) {\n    const listIdx = parseInt(callbackData.slice('select_list_'.length));\n    if (listIdx >= 0 && listIdx < lists.length) {\n      const list = lists[listIdx];\n      u.managing.listIdx = listIdx;\n      u.state = 'list_manage';\n      return [{ json: { action: 'send_picker', chatId, callbackId, listName: list.listName,\n        pickerText: t('list_manage_title', { listName: list.listName }),\n        keyboard: buildItemPickerKb(list, listIdx) } }];\n    }\n    return [{ json: { action: 'ack_callback', chatId, callbackId, toast: '' } }];\n  }\n  if (callbackData.startsWith('lit_')) {\n    const rest = callbackData.slice('lit_'.length);\n    const sep = rest.indexOf('_');\n    const listIdx = parseInt(rest.slice(0, sep));\n    const product = rest.slice(sep + 1);\n    if (listIdx >= 0 && listIdx < lists.length) {\n      const list = lists[listIdx];\n      const item = list.items.find(i => i.product === product);\n      if (item) item.status = item.status === 'Purchased' ? 'Pending' : 'Purchased';\n      return [{ json: { action: 'edit_picker', chatId, callbackId, msgId: u.shopping.msgId, keyboard: buildItemPickerKb(list, listIdx) } }];\n    }\n    return [{ json: { action: 'ack_callback', chatId, callbackId, toast: '' } }];\n  }\n  if (callbackData === 'expense_weight') {\n    u.expense.isWeight = true; u.state = 'expense_weight';\n    return [{ json: { action: 'send_text', chatId, callbackId, text: t('expense_ask_kg', { name: u.expense.name }) } }];\n  }\n  if (callbackData === 'expense_qty') {\n    u.expense.isWeight = false; u.state = 'expense_qty';\n    return [{ json: { action: 'send_text', chatId, callbackId, text: t('expense_ask_qty', { name: u.expense.name }) } }];\n  }\n  if (callbackData === 'shopping_add_product') {\n    u.state = 'shopping_add_product';\n    return [{ json: { action: 'send_text', chatId, callbackId, text: t('shopping_add_product_ask') } }];\n  }\n  if (callbackData.startsWith('toggle_')) {\n    const product = callbackData.slice(7);\n    if (!u.shopping.customProducts) u.shopping.customProducts = [];\n    const idx = u.shopping.selected.indexOf(product);\n    if (idx === -1) u.shopping.selected = u.shopping.selected.concat([product]);\n    else u.shopping.selected = u.shopping.selected.filter((_, i) => i !== idx);\n    return [{ json: { action: 'edit_picker', chatId, callbackId, msgId: u.shopping.msgId, keyboard: buildPickerKb(u.shopping.selected, u.shopping.customProducts) } }];\n  }\n  if (callbackData === 'shopping_confirm') {\n    if (!u.shopping.selected.length) {\n      return [{ json: { action: 'ack_callback', chatId, callbackId, toast: t('shopping_empty_error') } }];\n    }\n    const listName = u.shopping.listName;\n    const selected = [...u.shopping.selected];\n    const rows = selected.map(p => ({ listName, createdDate: date, product: p, status: 'Pending', lastUpdated: date }));\n    const replyText = t('shopping_saved', { listName, items: selected.map(p => '• ' + productLabel(p)).join('\\n') });\n    const replyKeyboard = { inline_keyboard: [[{ text: t('main_btn_lists'), callback_data: 'menu_lists' }, { text: t('btn_home'), callback_data: 'menu_main_reset' }]] };\n    u.lists = (u.lists || []).concat([{ listName, date, items: selected.map(p => ({ product: p, status: 'Pending' })) }]);\n    u.state = 'main'; u.shopping = { listName: '', selected: [], customProducts: [], msgId: 0 };\n    return [{ json: { action: 'save_list', chatId, callbackId, listName, selected, rows, replyText, replyKeyboard } }];\n  }\n  return [{ json: { action: 'ack_callback', chatId, callbackId, toast: '' } }];\n}\nif (u.state === 'lang_select') {\n  return [{ json: { action: 'send_kb', chatId, callbackId, text: STRINGS.en.lang_prompt, keyboard: LANG_KB } }];\n}\nif (u.state === 'expense_name') {\n  u.expense.name = text; u.state = 'expense_type';\n  return [{ json: { action: 'send_kb', chatId,\n    text: t('expense_ask_type', { name: text }),\n    keyboard: { inline_keyboard: [[\n      { text: t('expense_btn_weight'), callback_data: 'expense_weight' },\n      { text: t('expense_btn_qty'), callback_data: 'expense_qty' }\n    ]] }\n  } }];\n}\nif (u.state === 'expense_weight') {\n  const kg = parseFloat(text.replace(',', '.'));\n  if (isNaN(kg) || kg <= 0) return [{ json: { action: 'send_text', chatId, text: t('expense_invalid_number') } }];\n  u.expense.qty = kg; u.state = 'expense_price_kg';\n  return [{ json: { action: 'send_text', chatId, text: t('expense_ask_price_kg', { name: u.expense.name, qty: kg }) } }];\n}\nif (u.state === 'expense_price_kg') {\n  const price = parseFloat(text.replace(',', '.'));\n  if (isNaN(price) || price <= 0) return [{ json: { action: 'send_text', chatId, text: t('expense_invalid_number') } }];\n  const total = Math.round(u.expense.qty * price * 100) / 100;\n  const name = u.expense.name;\n  const qty = u.expense.qty;\n  const category = detectCategory(name);\n  const detail = qty + 'kg x \\u20aa' + price.toFixed(2) + '/kg';\n  const replyText = t('expense_success', { name, detail, amount: total.toFixed(2), category, date });\n  const replyKeyboard = { inline_keyboard: [[{ text: t('btn_log_another'), callback_data: 'menu_expense' }, { text: t('btn_main_menu'), callback_data: 'menu_main_reset' }]] };\n  u.state = 'main';\n  return [{ json: { action: 'log_expense', chatId, date, time, description: name, amount: total, quantity: 0, category, detail, rawMessage: name + ' ' + qty + 'kg ' + price, replyText, replyKeyboard } }];\n}\nif (u.state === 'expense_qty') {\n  const qty = parseInt(text);\n  if (isNaN(qty) || qty <= 0) return [{ json: { action: 'send_text', chatId, text: t('expense_invalid_number') } }];\n  u.expense.qty = qty; u.state = 'expense_price_item';\n  return [{ json: { action: 'send_text', chatId, text: t('expense_ask_price_item', { name: u.expense.name, qty: qty }) } }];\n}\nif (u.state === 'expense_price_item') {\n  const price = parseFloat(text.replace(',', '.'));\n  if (isNaN(price) || price <= 0) return [{ json: { action: 'send_text', chatId, text: t('expense_invalid_number') } }];\n  const total = Math.round(u.expense.qty * price * 100) / 100;\n  const name = u.expense.name;\n  const qty = u.expense.qty;\n  const category = detectCategory(name);\n  const detail = qty + ' x \\u20aa' + price.toFixed(2);\n  const replyText = t('expense_success', { name, detail, amount: total.toFixed(2), category, date });\n  const replyKeyboard = { inline_keyboard: [[{ text: t('btn_log_another'), callback_data: 'menu_expense' }, { text: t('btn_main_menu'), callback_data: 'menu_main_reset' }]] };\n  u.state = 'main';\n  return [{ json: { action: 'log_expense', chatId, date, time, description: name, amount: total, quantity: qty, category, detail, rawMessage: name + ' ' + qty + ' ' + price, replyText, replyKeyboard } }];\n}\nif (u.state === 'shopping_name') {\n  u.shopping.listName = text; u.state = 'shopping_select';\n  if (!u.shopping.customProducts) u.shopping.customProducts = [];\n  return [{ json: {\n    action: 'send_picker', chatId, listName: text,\n    pickerText: t('shopping_picker_title', { listName: text }),\n    keyboard: buildPickerKb([], [])\n  } }];\n}\nif (u.state === 'shopping_add_product') {\n  u.shopping.customProducts = (u.shopping.customProducts || []).concat([text]);\n  u.state = 'shopping_select';\n  return [{ json: {\n    action: 'send_picker', chatId, listName: u.shopping.listName,\n    pickerText: t('shopping_picker_title', { listName: u.shopping.listName }),\n    keyboard: buildPickerKb(u.shopping.selected, u.shopping.customProducts)\n  } }];\n}\nu.state = 'main';\nreturn [{ json: { action: 'send_kb', chatId, text: t('main_welcome'), keyboard: mainKb() } }];\n"
      }
    },
    {
      "id": "if-send-kb",
      "name": "Send Keyboard?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        200
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "send_kb",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "http-send-kb",
      "name": "HTTP: Send with Keyboard",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        140
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendMessage' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ chat_id: $json.chatId, text: $json.text, parse_mode: 'Markdown', reply_markup: $json.keyboard }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      }
    },
    {
      "id": "if-send-text",
      "name": "Send Text?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        380
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "send_text",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "telegram-send-text",
      "name": "Telegram: Send Text",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        660,
        320
      ],
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.text }}",
        "additionalFields": {
          "parse_mode": "Markdown",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      }
    },
    {
      "id": "ack-send-text-cb",
      "name": "Ack Send Text Callback",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        320
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/answerCallbackQuery' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ callback_query_id: $('Router').item.json.callbackId || '', text: '' }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      },
      "continueOnFail": true
    },
    {
      "id": "if-log-expense",
      "name": "Log Expense?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        560
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "log_expense",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "sheets-expense",
      "name": "Log to Expenses Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        660,
        500
      ],
      "parameters": {
        "resource": "sheet",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1tbZFdv1r5VmZnQqri1nXPxGkR9uNGHu2FxeDWcq8EYU"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Expenses"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Date": "={{ $json.date }}",
            "Time": "={{ $json.time }}",
            "Description": "={{ $json.description }}",
            "Amount": "={{ $json.amount }}",
            "Quantity": "={{ $json.quantity }}",
            "Category": "={{ $json.category }}",
            "Raw Message": "={{ $json.rawMessage }}"
          },
          "schema": [
            {
              "id": "Date",
              "displayName": "Date",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Time",
              "displayName": "Time",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Description",
              "displayName": "Description",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Amount",
              "displayName": "Amount",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Quantity",
              "displayName": "Quantity",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Category",
              "displayName": "Category",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Raw Message",
              "displayName": "Raw Message",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            }
          ]
        },
        "options": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "YHgWKyrOlH78ghQD",
          "name": "Google Sheets account"
        }
      },
      "continueOnFail": true
    },
    {
      "id": "http-reply-expense",
      "name": "HTTP: Reply Expense OK",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        500
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendMessage' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ chat_id: $('Router').item.json.chatId, text: $('Router').item.json.replyText, parse_mode: 'Markdown', reply_markup: $('Router').item.json.replyKeyboard }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      }
    },
    {
      "id": "if-send-picker",
      "name": "Send Picker?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        740
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "send_picker",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "http-send-picker",
      "name": "HTTP: Send Product Picker",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        680
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendMessage' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ chat_id: $json.chatId, text: $json.pickerText, parse_mode: 'Markdown', reply_markup: $json.keyboard }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      }
    },
    {
      "id": "store-picker-msgid",
      "name": "Store Picker MsgId",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        680
      ],
      "parameters": {
        "jsCode": "const result = $input.first().json;\nconst chatId = String($('Router').item.json.chatId || '');\nif (chatId) {\n  const sd = $getWorkflowStaticData('global');\n  if (sd.users && sd.users[chatId]) {\n    sd.users[chatId].shopping.msgId = result.result?.message_id || 0;\n  }\n}\nreturn [{ json: result }];"
      }
    },
    {
      "id": "if-edit-picker",
      "name": "Edit Picker?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        920
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "edit_picker",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "http-edit-picker",
      "name": "HTTP: Edit Picker",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        860
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/editMessageReplyMarkup' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ chat_id: $json.chatId, message_id: $json.msgId, reply_markup: $json.keyboard }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      }
    },
    {
      "id": "http-ack-toggle",
      "name": "HTTP: Ack Toggle",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        860
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/answerCallbackQuery' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ callback_query_id: $json.callbackId, text: '' }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      },
      "continueOnFail": true
    },
    {
      "id": "if-save-list",
      "name": "Save List?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        1100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "save_list",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "http-reply-list-saved",
      "name": "HTTP: Reply List Saved",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        1040
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/sendMessage' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ chat_id: $json.chatId, text: $json.replyText, parse_mode: 'Markdown', reply_markup: $json.replyKeyboard }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      }
    },
    {
      "id": "split-rows",
      "name": "Split to Rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        1040
      ],
      "parameters": {
        "jsCode": "const item = $('Router').item.json;\nreturn (item.rows || []).map(r => ({ json: r }));"
      }
    },
    {
      "id": "sheets-shopping",
      "name": "Save to Shopping Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        1100,
        1040
      ],
      "parameters": {
        "resource": "sheet",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1tbZFdv1r5VmZnQqri1nXPxGkR9uNGHu2FxeDWcq8EYU"
        },
        "sheetName": {
          "__rl": true,
          "value": "Shopping List",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "List Name": "={{ $json.listName }}",
            "Created Date": "={{ $json.createdDate }}",
            "Product": "={{ $json.product }}",
            "Status": "={{ $json.status }}",
            "Last Updated": "={{ $json.lastUpdated }}"
          },
          "schema": [
            {
              "id": "List Name",
              "displayName": "List Name",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Created Date",
              "displayName": "Created Date",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Product",
              "displayName": "Product",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Last Updated",
              "displayName": "Last Updated",
              "required": false,
              "removed": false,
              "canBeUsedToMatch": false
            }
          ]
        },
        "options": {}
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "YHgWKyrOlH78ghQD",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "id": "if-ack-callback",
      "name": "Ack Callback?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        1280
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.action }}",
              "rightValue": "ack_callback",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "http-ack-callback",
      "name": "HTTP: Ack Callback",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        1220
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.telegram.org/bot' + $env.TELEGRAM_BOT_TOKEN + '/answerCallbackQuery' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "telegramApi",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ callback_query_id: $json.callbackId, text: $json.toast || '' }) }}"
      },
      "credentials": {
        "telegramApi": {
          "id": "0vnrBUm3coYjYkdp",
          "name": "Telegram account"
        }
      },
      "continueOnFail": true
    }
  ],
  "connections": {
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Router": {
      "main": [
        [
          {
            "node": "Send Keyboard?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Keyboard?": {
      "main": [
        [
          {
            "node": "HTTP: Send with Keyboard",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Text?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Text?": {
      "main": [
        [
          {
            "node": "Telegram: Send Text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Expense?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: Send Text": {
      "main": [
        [
          {
            "node": "Ack Send Text Callback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Expense?": {
      "main": [
        [
          {
            "node": "Log to Expenses Sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Picker?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Expenses Sheet": {
      "main": [
        [
          {
            "node": "HTTP: Reply Expense OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Picker?": {
      "main": [
        [
          {
            "node": "HTTP: Send Product Picker",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Edit Picker?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Send Product Picker": {
      "main": [
        [
          {
            "node": "Store Picker MsgId",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Picker?": {
      "main": [
        [
          {
            "node": "HTTP: Edit Picker",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Save List?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Edit Picker": {
      "main": [
        [
          {
            "node": "HTTP: Ack Toggle",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save List?": {
      "main": [
        [
          {
            "node": "HTTP: Reply List Saved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ack Callback?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Reply List Saved": {
      "main": [
        [
          {
            "node": "Split to Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split to Rows": {
      "main": [
        [
          {
            "node": "Save to Shopping Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ack Callback?": {
      "main": [
        [
          {
            "node": "HTTP: Ack Callback",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}