Skip to content

Instantly share code, notes, and snippets.

@SamadiPour
Last active August 12, 2025 06:21
Show Gist options
  • Save SamadiPour/a18b1e186deac76c4a95fa108d02a6ea to your computer and use it in GitHub Desktop.
Save SamadiPour/a18b1e186deac76c4a95fa108d02a6ea to your computer and use it in GitHub Desktop.
Snappfood Spent money
// Copy paste this line in your browser console to run the script and get the result
fetch('https://gist.githubusercontent.com/SamadiPour/a18b1e186deac76c4a95fa108d02a6ea/raw/snappfood_enhanced.js').then(r => r.text()).then(code => eval(code));
(async function() {
function getAuthTokens() {
try {
const cookies = Object.fromEntries(document.cookie.split('; ').filter(c => c.includes('=')).map(c => c.split('=')));
let UDID = cookies.UDID || null;
if (!UDID) {
UDID = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
const jwt = cookies['jwt-access_token'] || JSON.parse(localStorage.getItem('JWT') || '{}').access_token;
if (!jwt) {
throw new Error('Required authentication tokens not found. Please make sure you are logged in.');
}
return { UDID, jwt };
} catch (error) {
console.error('❌ Authentication Error:', error.message);
return null;
}
}
async function fetchAllOrders() {
let pageSize = 1000;
let pageNumber = 0;
let allOrders = [];
let count = 1;
console.log('🔄 Starting order fetch...');
const startTime = Date.now();
// Get tokens
const tokens = getAuthTokens();
if (!tokens) return;
const { UDID, jwt } = tokens;
const myHeaders = new Headers({
"authority": "snappfood.ir",
"accept": "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9,fa;q=0.8",
"authorization": `Bearer ${jwt}`,
"content-type": "application/x-www-form-urlencoded",
"referer": "https://snappfood.ir/profile/orders",
"sec-ch-ua": '"Chromium";v="120", "Not_A Brand";v="24", "Google Chrome";v="120"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": navigator.userAgent
});
const requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
try {
while (pageNumber * pageSize < count) {
const startOrder = pageNumber * pageSize + 1;
const endOrder = Math.min((pageNumber + 1) * pageSize, count === 1 ? pageSize : count);
console.log(`📥 Fetching page ${pageNumber + 1}: orders ${startOrder} - ${endOrder}`);
const response = await fetch(
`https://snappfood.ir/mobile/v1/order/reorder?optionalClient=WEBSITE&client=WEBSITE&deviceType=WEBSITE&appVersion=8.1.1&UDID=${UDID}&page=${pageNumber}&size=${pageSize}&locale=fa`,
requestOptions
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const responseData = await response.json();
const orders = responseData.data?.orders || [];
count = responseData.data?.count || 0;
if (orders.length === 0) break;
allOrders = allOrders.concat(orders);
pageNumber++;
// Rate limiting to be respectful to the API
await new Promise(resolve => setTimeout(resolve, 100));
}
const fetchTime = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`✅ Fetch completed in ${fetchTime}s. Total orders: ${allOrders.length}`);
return allOrders;
} catch (error) {
console.error('❌ Error fetching orders:', error.message);
return [];
}
}
async function getConversionRates() {
try {
console.log('💱 Fetching currency conversion rates...');
const response = await fetch('https://raw.githubusercontent.com/SamadiPour/rial-exchange-rates-archive/data/gregorian_imp.min.json');
if (!response.ok) {
throw new Error('Failed to fetch conversion rates');
}
const data = await response.json();
const rates = new Map(
Object.entries(data).map(([key, value]) => [
key,
value.usd?.buy || value.usd?.sell || 0
])
);
const lastRate = [...rates.values()].filter(rate => rate > 0).at(-1) || 50000; // Fallback rate
console.log('✅ Conversion rates loaded');
return { rates, lastRate, conversionAvailable: true };
} catch (error) {
console.warn('⚠️ Could not fetch conversion rates, USD calculations will be skipped:', error.message);
return null;
}
}
function analyzeOrders(orders, conversionRates) {
const conversionAvailable = conversionRates !== null;
const { rates, lastRate } = conversionRates || { rates: new Map(), lastRate: 0 };
let totalPrice = 0;
let totalDiscount = 0;
let totalUSD = 0;
let completedOrders = 0;
const monthlySpending = new Map();
const yearlySpending = new Map();
const yearlySpendingUSD = new Map();
const restaurantStats = new Map();
const ordersByHour = new Array(24).fill(0);
const ordersByDayOfWeek = new Array(7).fill(0);
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
orders.forEach(order => {
if (order.orderCanceled) {
return;
}
completedOrders++;
totalPrice += order.totalPrice || 0;
totalDiscount += order.sumAllDiscount || 0;
// USD conversion for this specific order (only if conversion rates are available)
let orderUSD = 0;
if (conversionAvailable) {
const orderDate = new Date(order.startedAt);
const dateString = orderDate.toLocaleDateString('en-CA').replace(/-/g, '/');
const usdRate = rates.get(dateString) || lastRate;
orderUSD = (order.totalPrice || 0) / usdRate;
totalUSD += orderUSD;
}
// Monthly and yearly analysis
const orderDate = new Date(order.startedAt);
const monthKey = `${orderDate.getFullYear()}-${String(orderDate.getMonth() + 1).padStart(2, '0')}`;
const yearKey = orderDate.getFullYear().toString();
monthlySpending.set(monthKey, (monthlySpending.get(monthKey) || 0) + order.totalPrice);
yearlySpending.set(yearKey, (yearlySpending.get(yearKey) || 0) + order.totalPrice);
// Only track USD spending if conversion is available
if (conversionAvailable) {
yearlySpendingUSD.set(yearKey, (yearlySpendingUSD.get(yearKey) || 0) + orderUSD);
}
// Restaurant analysis
const restaurantName = order.vendorTitle || 'Unknown';
const restaurantData = restaurantStats.get(restaurantName) || { orders: 0, spending: 0 };
restaurantData.orders++;
restaurantData.spending += order.totalPrice || 0;
restaurantStats.set(restaurantName, restaurantData);
// Time analysis
ordersByHour[orderDate.getHours()]++;
ordersByDayOfWeek[orderDate.getDay()]++;
});
return {
totalOrders: orders.length,
completedOrders,
totalPrice,
totalDiscount,
totalUSD: conversionAvailable ? totalUSD : null,
conversionAvailable,
monthlySpending,
yearlySpending,
yearlySpendingUSD: conversionAvailable ? yearlySpendingUSD : new Map(),
restaurantStats,
ordersByHour,
ordersByDayOfWeek,
dayNames
};
}
// Enhanced reporting with beautiful console output
function displayResults(orders, analytics) {
const {
totalOrders, completedOrders, canceledOrders,
totalPrice, totalDiscount, totalUSD, conversionAvailable,
monthlySpending, yearlySpending, yearlySpendingUSD, restaurantStats,
ordersByHour, ordersByDayOfWeek, dayNames
} = analytics;
if (orders.length === 0) {
console.log('%c❌ No orders found', 'color: #e74c3c; font-size: 16px; font-weight: bold;');
return;
}
// Time period calculation
const today = new Date();
const oldestOrderDate = new Date(orders.at(-1).startedAt);
const timeDiff = today.getTime() - oldestOrderDate.getTime();
const daysDiff = Math.floor(timeDiff / (1000 * 3600 * 24));
const yearsDiff = Math.floor(daysDiff / 365);
const remainingDays = daysDiff % 365;
// Main statistics
console.log('%c🍽️ SNAPPFOOD ORDER ANALYTICS REPORT', 'color: #ff6b35; font-size: 18px; font-weight: bold; background: #1a1a1a; padding: 10px; border-radius: 5px;');
console.log('%c═══════════════════════════════════', 'color: #888; font-weight: bold;');
console.log('%c📊 OVERVIEW', 'color: #3498db; font-size: 14px; font-weight: bold;');
console.log('%cTotal Orders:%c %d', 'color: #3498db; font-weight: bold;', 'color: #bdc3c7;', totalOrders);
// Only show USD conversion if available
if (conversionAvailable && totalUSD !== null) {
console.log(`%cTotal Spent:%c %s Toman ($%s)`, 'color: #e74c3c; font-weight: bold;', 'color: #bdc3c7;', totalPrice.toLocaleString(), totalUSD.toFixed(2).toLocaleString());
} else {
console.log('%cTotal Spent:%c %s Toman', 'color: #e74c3c; font-weight: bold;', 'color: #bdc3c7;', totalPrice.toLocaleString());
}
console.log('%cTotal Discount:%c %s Toman (%s%%)', 'color: #27ae60; font-weight: bold;', 'color: #bdc3c7;', totalDiscount.toLocaleString(), ((totalDiscount / (totalPrice + totalDiscount)) * 100).toFixed(1));
console.log('%cAverage per Order:%c %s Toman', 'color: #9b59b6; font-weight: bold;', 'color: #bdc3c7;', Math.round(totalPrice / completedOrders).toLocaleString());
if (daysDiff > 0) {
console.log('%cDaily Average:%c %s Toman', 'color: #9b59b6; font-weight: bold;', 'color: #bdc3c7;', Math.round(totalPrice / daysDiff).toLocaleString());
console.log('%cMonthly Average:%c %s Toman', 'color: #9b59b6; font-weight: bold;', 'color: #bdc3c7;', Math.round(totalPrice / (daysDiff / 30.44)).toLocaleString());
}
console.log('%cTime Period:%c %s (%d years, %d days)', 'color: #f39c12; font-weight: bold;', 'color: #bdc3c7;', oldestOrderDate.toLocaleDateString('en-CA'), yearsDiff, remainingDays);
// Top restaurants
const topRestaurants = [...restaurantStats.entries()]
.sort((a, b) => b[1].spending - a[1].spending)
.slice(0, 5);
console.log('\n%c🏪 TOP 5 RESTAURANTS', 'color: #f39c12; font-size: 14px; font-weight: bold;');
topRestaurants.forEach(([name, data], index) => {
console.log(`%c${index + 1}. ${name}:%c ${data.orders} orders, ${data.spending.toLocaleString()} Toman`,
'color: #f39c12; font-weight: bold;', 'color: #c0c0c0;');
});
// Time patterns
const peakHour = ordersByHour.indexOf(Math.max(...ordersByHour));
const peakDay = ordersByDayOfWeek.indexOf(Math.max(...ordersByDayOfWeek));
console.log('\n%c⏰ ORDER PATTERNS', 'color: #9b59b6; font-size: 14px; font-weight: bold;');
console.log('%cPeak Hour:%c %d:00 (%d orders)', 'color: #9b59b6; font-weight: bold;', 'color: #c0c0c0;', peakHour, ordersByHour[peakHour]);
console.log('%cPeak Day:%c %s (%d orders)', 'color: #9b59b6; font-weight: bold;', 'color: #c0c0c0;', dayNames[peakDay], ordersByDayOfWeek[peakDay]);
// Yearly breakdown if multiple years (only show USD if conversion is available)
if (yearlySpending.size > 1) {
console.log('\n%c📅 YEARLY BREAKDOWN', 'color: #1abc9c; font-size: 14px; font-weight: bold;');
[...yearlySpending.entries()]
.sort()
.forEach(([year, spending]) => {
if (conversionAvailable && yearlySpendingUSD.has(year)) {
const usdAmount = yearlySpendingUSD.get(year) || 0;
console.log(`%c${year}:%c ${spending.toLocaleString()} Toman ($${usdAmount.toFixed(2).toLocaleString()})`,
'color: #1abc9c; font-weight: bold;', 'color: #c0c0c0;');
} else {
console.log(`%c${year}:%c ${spending.toLocaleString()} Toman`,
'color: #1abc9c; font-weight: bold;', 'color: #c0c0c0;');
}
});
}
console.log('%c═══════════════════════════════════', 'color: #888; font-weight: bold;');
// Export data to global variable for further analysis
window.snapfoodAnalytics = {
orders,
analytics,
exportCSV: () => {
const csv = orders.map(order => ({
date: new Date(order.startedAt).toLocaleDateString('en-CA'),
restaurant: order.vendorTitle || 'Unknown',
amount: order.totalPrice || 0,
discount: order.sumAllDiscount || 0,
}));
console.log('CSV data prepared:', csv);
console.log('Use: copy(JSON.stringify(window.snapfoodAnalytics.exportCSV()))');
}
};
}
// Main execution
try {
const orders = await fetchAllOrders();
const conversionRates = await getConversionRates();
const analytics = analyzeOrders(orders, conversionRates);
displayResults(orders, analytics);
console.log('\n%c💡 TIP: Access window.snapfoodAnalytics for raw data and CSV export', 'color: #95a5a6; font-style: italic;');
} catch (error) {
console.error('❌ Script execution failed:', error.message);
}
})();
cookies = Object.fromEntries(document.cookie.split('; ').map(c => c.split('=')));
let UDID = cookies.UDID;
let jwt = cookies[['jwt-access_token']] ?? JSON.parse(window.localStorage.JWT ?? '{}').access_token;
var myHeaders = new Headers();
myHeaders.append("authority", "snappfood.ir");
myHeaders.append("accept", "application/json, text/plain, */*");
myHeaders.append("accept-language", "en-US,en;q=0.9,fa;q=0.8");
myHeaders.append("authorization", "Bearer " + jwt);
myHeaders.append("content-type", "application/x-www-form-urlencoded");
myHeaders.append("referer", "https://snappfood.ir/profile/orders");
myHeaders.append("sec-ch-ua", "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"");
myHeaders.append("sec-ch-ua-mobile", "?1");
myHeaders.append("sec-ch-ua-platform", "\"Android\"");
myHeaders.append("sec-fetch-dest", "empty");
myHeaders.append("sec-fetch-mode", "cors");
myHeaders.append("sec-fetch-site", "same-origin");
myHeaders.append("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36");
const requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
let pageSize = 1000;
let pageNumber = 0;
let allOrders = [];
let count = 1;
while (pageNumber * pageSize < count) {
console.log(`Getting ${pageNumber * pageSize} - ${(pageNumber + 1) * pageSize}...`);
let response = await fetch(`https://snappfood.ir/mobile/v1/order/reorder?optionalClient=WEBSITE&client=WEBSITE&deviceType=WEBSITE&appVersion=8.1.1&UDID=${UDID}&page=${pageNumber}&size=${pageSize}&locale=fa`, requestOptions);
const responseData = await response.json();
const orders = responseData.data.orders;
count = responseData.data.count ?? 0;
if (orders.length === 0) break;
allOrders = allOrders.concat(orders);
pageNumber++;
}
let price = 0;
let discount = 0;
for (let i = 0; i < allOrders.length; i++) {
let order = allOrders[i];
if (!order.orderCanceled) {
price += order.totalPrice;
discount += order.sumAllDiscount;
}
}
const today = new Date();
const oldestOrderDate = new Date(allOrders.at(-1).startedAt);
const timeDiff = today.getTime() - oldestOrderDate.getTime();
const daysDiff = Math.floor(timeDiff / (1000 * 3600 * 24));
const yearsDiff = Math.floor(daysDiff / 365);
const remainingDays = daysDiff % 365;
const conversionResponse = await fetch(`https://raw.githubusercontent.com/SamadiPour/rial-exchange-rates-archive/data/gregorian_imp.min.json`);
const conversionResponseData = await conversionResponse.json();
const conversionRates = new Map(
Object.entries(conversionResponseData).map(([key, value]) => [key, value.usd.buy ?? value.usd.sell])
);
const lastConversionRate = [...conversionRates.values()].at(-1)
let usd = 0;
for (let i = 0; i < allOrders.length; i++) {
let order = allOrders[i];
if (!order.orderCanceled) {
let dateString = new Date(order.startedAt).toLocaleDateString('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/-/g, '/');
let usdRate = conversionRates.get(dateString) ?? lastConversionRate;
usd += order.totalPrice / usdRate;
}
}
console.log('%c---------------', 'color: #888; font-weight: bold;');
console.log('%cTotal order:%c %s', 'color: #3498db; font-weight: bold;', 'color: #c0c0c0;', allOrders.length);
console.log('%cTotal spent:%c %s Toman', 'color: #3498db; font-weight: bold;', 'color: #e74c3c;', price.toLocaleString());
console.log('%cTotal spent in USD:%c $%s', 'color: #3498db; font-weight: bold;', 'color: #e74c3c;', usd.toLocaleString());
console.log('%cTotal discount:%c %s Toman', 'color: #3498db; font-weight: bold;', 'color: #2ecc71;', discount.toLocaleString());
console.log('%cOldest order date:%c %s (%d years and %d days ago)', 'color: #3498db; font-weight: bold;', 'color: #f39c12;', oldestOrderDate.toLocaleDateString('en-CA'), yearsDiff, remainingDays);
@shervintech
Copy link

For snapp.express use this snippet code:

let jwtJsonString = localStorage.getItem("JWT");
let jwt = JSON.parse(jwtJsonString).access_token;


let response = await fetch("https://api.snapp.express/mobile/v1/order/reorder?size=2000&page=0&vendorSuperType=SUPERMARKET&split_page=0&client=PWA&optionalClient=PWA&deviceType=PWA&appVersion=5.6.6&clientVersion=2.8.7&optionalVersion=5.6.6&UDID=3b778aa6-5266-4d08-a94a-074939993d13", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "en-US,en;q=0.9",
    "authorization": "Bearer " + jwt,
    "cache-control": "no-cache",
    "content-type": "application/x-www-form-urlencoded",
    "pragma": "no-cache",
    "sec-ch-ua": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
    "sec-ch-ua-mobile": "?1",
    "sec-ch-ua-platform": "\"Android\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site"
  },
  "referrer": "https://m.snapp.express/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
});

const content = (await response.json()).data.orders;

let price = 0;
let discount = 0;
for (let i = 0; i < content.length; i++) {
    let order = content[i];
    if (!order.orderCanceled) {
        price += order['totalPrice'];
        discount += order['sumAllDiscount'];
    }
}

console.log(`Total order: ${content.length}`)
console.log(`Total spent: ${price.toLocaleString()}`);
console.log(`Total discount: ${discount.toLocaleString()}`);
console.log(`Oldest order date: ${content.at(-1).startedAt}`);

@Heshmatkhah
Copy link

راهنمای استفاده از اسکریپت:

  • تو مرورگر دسکتاپ اسنپ فود رو باز کنید (نسخه موبایلشو باز نکنید)
  • اگر لاگین نیستید، لاگین کنید.
  • بعد راست کلیک کنید و گزینه Inspect رو انتخاب کنید
  • برید تب کنسول
  • اسکریپت رو paste کنید

@rezad1393
Copy link

For snapp.express use this snippet code:

let jwtJsonString = localStorage.getItem("JWT");
let jwt = JSON.parse(jwtJsonString).access_token;


let response = await fetch("https://api.snapp.express/mobile/v1/order/reorder?size=2000&page=0&vendorSuperType=SUPERMARKET&split_page=0&client=PWA&optionalClient=PWA&deviceType=PWA&appVersion=5.6.6&clientVersion=2.8.7&optionalVersion=5.6.6&UDID=3b778aa6-5266-4d08-a94a-074939993d13", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "en-US,en;q=0.9",
    "authorization": "Bearer " + jwt,
    "cache-control": "no-cache",
    "content-type": "application/x-www-form-urlencoded",
    "pragma": "no-cache",
    "sec-ch-ua": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
    "sec-ch-ua-mobile": "?1",
    "sec-ch-ua-platform": "\"Android\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site"
  },
  "referrer": "https://m.snapp.express/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
});

const content = (await response.json()).data.orders;

let price = 0;
let discount = 0;
for (let i = 0; i < content.length; i++) {
    let order = content[i];
    if (!order.orderCanceled) {
        price += order['totalPrice'];
        discount += order['sumAllDiscount'];
    }
}

console.log(`Total order: ${content.length}`)
console.log(`Total spent: ${price.toLocaleString()}`);
console.log(`Total discount: ${discount.toLocaleString()}`);
console.log(`Oldest order date: ${content.at(-1).startedAt}`);

برای اسنپ اکسپرس کار نمیکنه.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment