var CONFIG = { EMAIL_TO: 'e.ghasimi@gmail.com', LOOKBACK_DAYS: 30, BRAND_NAME: 'webinew.com', SCRIPT_NAME: 'webinew.com - Monthly Hourly & Budget Pacing Report', }; var FONT_STACK = "'Estedad','Vazirmatn',Tahoma,Arial,sans-serif"; var FONT_LINK = ''+ ''+ ''; function main() { var tz = AdsApp.currentAccount().getTimeZone(); var today = new Date(); var yesterday = new Date(today); yesterday.setDate(today.getDate() - 1); var startDate = new Date(today); startDate.setDate(today.getDate() - CONFIG.LOOKBACK_DAYS); var fmt = function(d) { return Utilities.formatDate(d, tz, 'yyyy-MM-dd'); }; var sdate = fmt(startDate); var edate = fmt(yesterday); // ── 1) Hourly + Daily performance — for budget exhaust per day ───── var hourlyQuery = 'SELECT segments.date, segments.hour, campaign.name, campaign_budget.amount_micros, '+ ' metrics.clicks, metrics.impressions, metrics.cost_micros, metrics.conversions '+ 'FROM campaign '+ 'WHERE segments.date BETWEEN \''+sdate+'\' AND \''+edate+'\' '+ ' AND campaign.status = \'ENABLED\''; Logger.log('Query: ' + hourlyQuery); var hourly = AdsApp.search(hourlyQuery); var hours = {}; for (var h = 0; h < 24; h++) hours[h] = {clicks:0, cost:0, conv:0, impr:0}; // ساختار per-day per-campaign per-hour برای محاسبه exhaust // campaigns[name] = {budget, totalCost, totalConv, totalClicks, days: {date: {hourCosts: {h: cost}}}} var campaigns = {}; var totalDays = {}; while (hourly.hasNext()) { var row = hourly.next(); var date = row.segments.date; var hr = parseInt(row.segments.hour) || 0; var name = row.campaign.name; var budget = (parseFloat(row.campaignBudget.amountMicros) || 0) / 1000000; var clicks = parseInt(row.metrics.clicks) || 0; var impr = parseInt(row.metrics.impressions) || 0; var cost = (parseFloat(row.metrics.costMicros) || 0) / 1000000; var convs = parseFloat(row.metrics.conversions) || 0; hours[hr].clicks += clicks; hours[hr].cost += cost; hours[hr].conv += convs; hours[hr].impr += impr; totalDays[date] = true; if (!campaigns[name]) { campaigns[name] = {budget:budget, totalCost:0, totalConv:0, totalClicks:0, days:{}}; } // به‌روزرسانی budget با آخرین مقدار دیده‌شده if (budget > 0) campaigns[name].budget = budget; campaigns[name].totalCost += cost; campaigns[name].totalConv += convs; campaigns[name].totalClicks += clicks; if (!campaigns[name].days[date]) campaigns[name].days[date] = {}; campaigns[name].days[date][hr] = (campaigns[name].days[date][hr] || 0) + cost; } var dayCount = Object.keys(totalDays).length || 1; Logger.log('Days analyzed: ' + dayCount); // ── محاسبه exhaust hour میانگین برای هر کمپین ───────────── var campList = []; for (var cn in campaigns) { var d = campaigns[cn]; var exhTimes = []; var daysExhausted = 0; var daysAnalyzed = 0; for (var dt in d.days) { daysAnalyzed++; var dayCum = 0, dayExh = null; var dayHours = d.days[dt]; for (var ch = 0; ch < 24; ch++) { var prev = dayCum; dayCum += (dayHours[ch] || 0); if (d.budget > 0 && dayCum >= d.budget && dayExh === null) { var need = d.budget - prev; var min = dayHours[ch] > 0 ? Math.round((need / dayHours[ch]) * 60) : 0; dayExh = ch + (min / 60); } } if (dayExh !== null) { exhTimes.push(dayExh); daysExhausted++; } } var avgExh = null; if (exhTimes.length > 0) { var sum = 0; for (var et = 0; et < exhTimes.length; et++) sum += exhTimes[et]; avgExh = sum / exhTimes.length; } var avgDailySpend = daysAnalyzed > 0 ? d.totalCost / daysAnalyzed : 0; var avgUtil = d.budget > 0 ? (avgDailySpend / d.budget * 100) : 0; campList.push({ name: cn, budget: d.budget, totalCost: d.totalCost, avgDailySpend: avgDailySpend, totalConv: d.totalConv, avgExhaust: avgExh, daysExhausted: daysExhausted, daysAnalyzed: daysAnalyzed, avgUtil: avgUtil }); } campList.sort(function(a,b){ return b.totalCost - a.totalCost; }); // ── 2) Day of week pattern (همان ۳۰ روز) ─────────────────── var dowQuery = 'SELECT segments.day_of_week, metrics.clicks, metrics.cost_micros, metrics.conversions '+ 'FROM campaign '+ 'WHERE segments.date BETWEEN \''+sdate+'\' AND \''+edate+'\' '+ ' AND campaign.status = \'ENABLED\''; var dowRes = AdsApp.search(dowQuery); var dows = {}; var dowOrder = ['SATURDAY','SUNDAY','MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY']; var dowFa = {SATURDAY:'شنبه', SUNDAY:'یک‌شنبه', MONDAY:'دوشنبه', TUESDAY:'سه‌شنبه', WEDNESDAY:'چهارشنبه', THURSDAY:'پنج‌شنبه', FRIDAY:'جمعه'}; for (var dk = 0; dk < dowOrder.length; dk++) dows[dowOrder[dk]] = {clicks:0, cost:0, conv:0}; while (dowRes.hasNext()) { var dr = dowRes.next(); var dow = dr.segments.dayOfWeek; if (!dows[dow]) dows[dow] = {clicks:0, cost:0, conv:0}; dows[dow].clicks += parseInt(dr.metrics.clicks) || 0; dows[dow].cost += (parseFloat(dr.metrics.costMicros) || 0) / 1000000; dows[dow].conv += parseFloat(dr.metrics.conversions) || 0; } // ── 3) آمار کلی و پیشنهادات bid adjustment ──────────────── var tClicks=0, tConv=0, tCost=0; for (var hk = 0; hk < 24; hk++) { tClicks+=hours[hk].clicks; tConv+=hours[hk].conv; tCost+=hours[hk].cost; } var avgConvRate = tClicks > 0 ? tConv / tClicks : 0; var avgCpa = tConv > 0 ? tCost / tConv : 0; var hourSugs = []; var maxHourCost = 0; for (var hi = 0; hi < 24; hi++) if (hours[hi].cost > maxHourCost) maxHourCost = hours[hi].cost; for (var ho = 0; ho < 24; ho++) { var hd = hours[ho]; var cr = hd.clicks > 0 ? hd.conv / hd.clicks : 0; var sug='', adj=0, color='#6B7280', bg='#F9FAFB'; if (hd.clicks < 5) { sug = 'داده کم'; color = '#9CA3AF'; bg = '#F9FAFB'; } else if (cr > avgConvRate * 1.5 && hd.conv > 0) { sug = '⬆ افزایش +25%'; adj = 25; color = '#16A34A'; bg = '#DCFCE7'; } else if (cr > avgConvRate * 1.2 && hd.conv > 0) { sug = '⬆ افزایش +15%'; adj = 15; color = '#16A34A'; bg = '#ECFCCB'; } else if (hd.clicks > 10 && hd.conv === 0) { sug = '⬇ کاهش −30%'; adj = -30; color = '#DC2626'; bg = '#FEE2E2'; } else if (cr < avgConvRate * 0.5 && hd.clicks > 5) { sug = '⬇ کاهش −15%'; adj = -15; color = '#D97706'; bg = '#FEF3C7'; } else { sug = '— نرمال'; color = '#6B7280'; bg = '#F9FAFB'; } hourSugs.push({ hour: ho, clicks: hd.clicks, cost: hd.cost, conv: hd.conv, impr: hd.impr, convRate: cr, sug: sug, adj: adj, color: color, bg: bg }); } var html = buildEmail(hourSugs, campList, dows, dowOrder, dowFa, startDate, yesterday, tz, tCost, tClicks, tConv, avgConvRate, avgCpa, maxHourCost, dayCount); MailApp.sendEmail({ to: CONFIG.EMAIL_TO, subject: CONFIG.BRAND_NAME + ' | Monthly Report | ' + Utilities.formatDate(yesterday, tz, 'dd MMM yyyy'), htmlBody: html, }); Logger.log('✅ Email sent'); } function buildEmail(hourSugs, campList, dows, dowOrder, dowFa, sd, ed, tz, tCost, tClicks, tConv, avgConvRate, avgCpa, maxHourCost, dayCount) { var fDate = function(d){ return Utilities.formatDate(d, tz, 'dd MMM yyyy'); }; var fAED = function(n){ return 'AED ' + n.toFixed(2); }; var fmtHour = function(h) { var hh = Math.floor(h); var mm = Math.round((h - hh) * 60); return (hh < 10 ? '0'+hh : hh) + ':' + (mm < 10 ? '0'+mm : mm); }; var bestHour = null, worstHour = null; for (var bi = 0; bi < hourSugs.length; bi++) { if (hourSugs[bi].adj > 0 && (bestHour === null || hourSugs[bi].convRate > bestHour.convRate)) bestHour = hourSugs[bi]; if (hourSugs[bi].adj < 0 && (worstHour === null || hourSugs[bi].cost > worstHour.cost)) worstHour = hourSugs[bi]; } var cards = [ ['کل هزینه ماه', fAED(tCost), '#1D4ED8'], ['میانگین روزانه', fAED(tCost / dayCount), '#0EA5E9'], ['کل کلیک', tClicks+'', '#374151'], ['Conversions', tConv.toFixed(1), '#16A34A'], ['میانگین Conv Rate', (avgConvRate*100).toFixed(1)+'%', '#7C3AED'], ['میانگین CPA', avgCpa > 0 ? fAED(avgCpa) : '—', '#DC2626'], ]; var cardHtml=''; for(var c=0;c'+cards[c][0]+'
'+cards[c][1]+'
'; } // Hourly rows var hRows=''; for (var hi = 0; hi < hourSugs.length; hi++) { var hs = hourSugs[hi]; var barW = maxHourCost > 0 ? Math.round((hs.cost / maxHourCost) * 100) : 0; var barColor = hs.adj > 0 ? '#16A34A' : hs.adj < 0 ? '#DC2626' : '#9CA3AF'; var rowBg = hi % 2 === 0 ? '#FFFFFF' : '#F9FAFB'; var avgClicks = (hs.clicks / dayCount).toFixed(1); hRows+=''+ ''+(hs.hour<10?'0'+hs.hour:hs.hour)+':00'+ ''+hs.clicks+''+ ''+avgClicks+''+ ''+ '
'+fAED(hs.cost)+'
'+ ''+ ''+hs.conv.toFixed(1)+''+ ''+(hs.convRate*100).toFixed(1)+'%'+ ''+hs.sug+''+ ''; } // Budget rows var bRows=''; for (var bk = 0; bk < campList.length; bk++) { var cs = campList[bk]; var exhText, exhColor; if (cs.avgExhaust !== null) { exhText = '⏰ ' + fmtHour(cs.avgExhaust); exhColor = cs.avgExhaust < 18 ? '#DC2626' : '#D97706'; } else if (cs.avgUtil >= 95) { exhText = '~۲۳:۵۹'; exhColor = '#D97706'; } else { exhText = 'تموم نمی‌شه'; exhColor = '#16A34A'; } var exhPct = cs.daysAnalyzed > 0 ? Math.round((cs.daysExhausted / cs.daysAnalyzed) * 100) : 0; var utilBarW = Math.min(cs.avgUtil, 100); var utilColor = cs.avgUtil >= 100 ? '#DC2626' : cs.avgUtil >= 80 ? '#D97706' : '#16A34A'; bRows+=''+ ''+cs.name+''+ ''+fAED(cs.budget)+''+ ''+fAED(cs.avgDailySpend)+''+ ''+ '
'+cs.avgUtil.toFixed(0)+'%
'+ ''+ ''+exhText+''+ ''+cs.daysExhausted+'/'+cs.daysAnalyzed+' ('+exhPct+'%)'+ ''+cs.totalConv.toFixed(1)+''+ ''; } // DOW rows var dowRows = ''; var maxDowConv = 0; for (var dn = 0; dn < dowOrder.length; dn++) { if (dows[dowOrder[dn]] && dows[dowOrder[dn]].conv > maxDowConv) maxDowConv = dows[dowOrder[dn]].conv; } for (var dx = 0; dx < dowOrder.length; dx++) { var dk2 = dowOrder[dx]; var dd = dows[dk2] || {clicks:0, cost:0, conv:0}; var cr2 = dd.clicks > 0 ? dd.conv / dd.clicks : 0; var cpa2 = dd.conv > 0 ? dd.cost / dd.conv : 0; var convBarW = maxDowConv > 0 ? Math.round((dd.conv / maxDowConv) * 100) : 0; var dowSug = '— نرمال', dowColor = '#6B7280', dowBg = '#F9FAFB'; if (cr2 > avgConvRate * 1.3 && dd.conv > 0) { dowSug = '⬆ افزایش'; dowColor = '#16A34A'; dowBg = '#DCFCE7'; } else if (cr2 < avgConvRate * 0.5 && dd.clicks > 5) { dowSug = '⬇ کاهش'; dowColor = '#DC2626'; dowBg = '#FEE2E2'; } dowRows+=''+ ''+dowFa[dk2]+''+ ''+dd.clicks+''+ ''+fAED(dd.cost)+''+ ''+ '
'+dd.conv.toFixed(1)+'
'+ ''+ ''+(cr2*100).toFixed(1)+'%'+ ''+(cpa2 > 0 ? fAED(cpa2) : '—')+''+ ''+dowSug+''+ ''; } // Top recommendations var topActions = '
🎯 توصیه‌های کلیدی (تحلیل ۳۰ روزه)
'; var scriptInfo = '
'+ '📋 اطلاعات این گزارش:
'+ 'این گزارش توسط اسکریپت «'+CONFIG.SCRIPT_NAME+'» در Google Ads تولید شده است.
'+ 'بازه تحلیل: '+fDate(sd)+' تا '+fDate(ed)+' ('+dayCount+' روز) | داده‌های ساعتی و روزانه: جمع کل بازه'+ '
'; return ''+FONT_LINK+ ''+ ''+ '
'+ ''+ ''+ ''+ ''+ ''+ '
'+ ''+ ''+ ''+ '
🚗 '+CONFIG.BRAND_NAME+' — گزارش ماهانه
'+ '
تحلیل ساعتی، روز هفته و بودجه — ۳۰ روز گذشته
'+fDate(sd)+' – '+fDate(ed)+'
'+ '
'+dayCount+' روز
'+cardHtml+'
'+ topActions + '
⏰ تحلیل ساعتی — جمع ۳۰ روز
'+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+hRows+ '
ساعتکل کلیکمیانگین روزانهکل هزینهConvConv Rateپیشنهاد
'+ '
💰 الگوی بودجه — به‌طور میانگین کی تموم می‌شه؟
'+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+bRows+ '
Campaignبودجه روزانهمیانگین خرج روزانهمصرف میانگینمیانگین ساعت پایان بودجهروزهای تموم‌شدهConv
'+ '
📅 الگوی روز هفته
'+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ ''+dowRows+ '
روزکلیکهزینهConversionsConv RateCPAپیشنهاد
'+ scriptInfo + '
'+ '
گزارش خودکار Google Ads Script | Powered by Webinew
'+ '
'; }