Last active
August 12, 2025 06:21
-
-
Save SamadiPour/a18b1e186deac76c4a95fa108d02a6ea to your computer and use it in GitHub Desktop.
Snappfood Spent money
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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); | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
- تو مرورگر دسکتاپ اسنپ فود رو باز کنید (نسخه موبایلشو باز نکنید)
- اگر لاگین نیستید، لاگین کنید.
- بعد راست کلیک کنید و گزینه Inspect رو انتخاب کنید
- برید تب کنسول
- اسکریپت رو paste کنید
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
For snapp.express use this snippet code: