URL Checker

127 views
Skip to first unread message

Rahil Shah

unread,
Aug 27, 2024, 1:24:58 AM8/27/24
to Google Ads Scripts Forum
Hi there,

Trying to run this script at MCC level. When i run the preview it comes with many errors fetching urls even though they are valid urls. Also comes up with the below error (have checked the access and naming of the sheet to make sure its correct). 
Exception: Unexpected error while getting the method or property openById on object SpreadsheetApp. at updateSpreadsheet (Code:296:36) at main (Code:49:7) at Object.<anonymous> (adsapp_compiled:20552:54)


// Configuration Settings
var config = {
  checkLoadTime: true,
  checkMobileFriendly: true,
  checkRedirects: false,
  checkGTM: true,
  checkHTTPS: true,
  sendEmail: true,
  updateSpreadsheet: true,
  accountLabel: '' // Add the label of the accounts to process. Leave blank to process all accounts.
};

// Thresholds and Limits
var LOAD_TIME_THRESHOLD = 4000; // milliseconds
var MAX_REDIRECTS = 3;
var MAX_URLS_PER_ACCOUNT = Infinity;
var MAX_ACCOUNTS_TO_CHECK = Infinity;
var EMAIL_ADDRESS = 'rahil...@phdmedia.com';
var SPREADSHEET_ID = 'https://docs.google.com/spreadsheets/d/1aWTtcVWDfOhYNjEoGxi-OAC1o8IQ1epz1i_haM6BrT8/edit?pli=1&gid=0#gid=0';
var SHEET_NAME = 'Landing Page Health';

function main() {
  var accountIterator = AdsManagerApp.accounts().get();
  if (config.accountLabel) {
    accountIterator = AdsManagerApp.accounts()
      .withCondition("LabelNames CONTAINS '" + config.accountLabel + "'")
      .get();
  }
 
  var accountsChecked = 0;
  var allResults = {};

  while (accountIterator.hasNext() && accountsChecked < MAX_ACCOUNTS_TO_CHECK) {
    var account = accountIterator.next();
    AdsManagerApp.select(account);
    var urlsToCheck = getAllLandingPageUrls();
    var results = checkUrls(urlsToCheck);
    if (Object.keys(results).some(key => results[key].length > 0)) {
      allResults[account.getName()] = results;
    }
    accountsChecked++;
  }

  if (Object.keys(allResults).length > 0) {
    if (config.sendEmail) {
      sendEmail(allResults, accountsChecked);
    }
    if (config.updateSpreadsheet) {
      updateSpreadsheet(allResults, accountsChecked);
    }
  } else {
    console.log("No issues detected with URLs across all checked accounts.");
  }
}

function getAllLandingPageUrls() {
  var urls = {};
 
  // Get all enabled campaigns
  var campaignIterator = AdsApp.campaigns()
    .withCondition("Status = ENABLED")
    .get();
 
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
   
    // Get enabled ads from enabled campaigns
    var adIterator = campaign.ads()
      .withCondition("Status = ENABLED")
      .get();
   
    while (adIterator.hasNext()) {
      var ad = adIterator.next();
      var finalUrl = ad.urls().getFinalUrl();
      if (finalUrl) urls[finalUrl] = true;
    }
   
    // Get enabled keywords from enabled campaigns
    var keywordIterator = campaign.keywords()
      .withCondition("Status = ENABLED")
      .get();
   
    while (keywordIterator.hasNext()) {
      var keyword = keywordIterator.next();
      var finalUrl = keyword.urls().getFinalUrl();
      if (finalUrl) urls[finalUrl] = true;
    }
  }

  return Object.keys(urls).slice(0, MAX_URLS_PER_ACCOUNT);
}

function checkUrls(urls) {
  var results = {
    slowUrls: [],
    httpErrors: [],
    mobileUnfriendly: [],
    tooManyRedirects: [],
    dnsErrors: [],
    redirects: [],
    deadLinks: [],
    missingTags: [],
    notHTTPS: []
  };

  urls.forEach(url => {
    var checkResult = checkUrl(url);
    if (!checkResult) {
      console.log('Failed to check URL: ' + url);
      return;
    }

    if ((config.checkRedirects && checkResult.redirectDetected) || checkResult.httpStatus >= 400 || !checkResult.isHTTPS) {
      if (config.checkRedirects && checkResult.redirectDetected) {
        results.redirects.push({
          url: url,
          redirectDetails: checkResult.redirectDetails
        });
      }
      if (checkResult.httpStatus >= 400) {
        results.httpErrors.push({url: url, status: checkResult.httpStatus});
      }
      if (!checkResult.isHTTPS) {
        results.notHTTPS.push({url: url});
      }
      // Skip adding results related to tags if major issues are detected
    } else {
      if (config.checkMobileFriendly && !checkResult.mobileFriendly) {
        results.mobileUnfriendly.push({url: url});
      }
      if (checkResult.dnsError) {
        results.dnsErrors.push({url: url});
      }
      if (config.checkGTM && !checkResult.hasGTM && !checkResult.hasGA && !checkResult.hasConversionPixel) {
        var missingTags = [];
        if (!checkResult.hasGTM) missingTags.push("GTM");
        if (!checkResult.hasGA) missingTags.push("GA");
        if (!checkResult.hasConversionPixel) missingTags.push("Ads Pixel");
        results.missingTags.push({url: url, missing: missingTags.join(", ")});
      }
    }
  });

  return results;
}

function checkUrl(url) {
  var result = {
    loadTime: 0,
    httpStatus: 200,
    mobileFriendly: true,
    redirectCount: 0,
    redirectDetected: false,
    finalUrl: url,
    hasGTM: false,
    gtmDebugInfo: '',
    hasGA: false,
    gaDebugInfo: '',
    hasConversionPixel: false,
    conversionPixelDebugInfo: '',
    dnsError: false,
    isHTTPS: false,
    sslCheck: "Not Checked",
    redirectDetails: []
  };

  try {
    var response = UrlFetchApp.fetch(url, {followRedirects: !config.checkRedirects, muteHttpExceptions: true});
    result.httpStatus = response.getResponseCode();
    result.finalUrl = response.getHeaders()['Location'] || url;

    // Handle redirects only if checkRedirects is true
    if (config.checkRedirects) {
      while (response.getResponseCode() >= 300 && response.getResponseCode() < 400 && result.redirectCount < MAX_REDIRECTS) {
        result.redirectCount++;
        result.redirectDetails.push({
          url: result.finalUrl,
          status: response.getResponseCode()
        });
        url = result.finalUrl;
        response = UrlFetchApp.fetch(url, {followRedirects: false, muteHttpExceptions: true});
        result.finalUrl = response.getHeaders()['Location'] || url;
      }
    }

    result.isHTTPS = result.finalUrl.toLowerCase().startsWith('https://');
    result.redirectDetected = result.redirectCount > 0;
   
    // Return early if a redirect is detected (and we're checking for redirects), or there is an HTTP error or HTTPS issue
    if ((config.checkRedirects && result.redirectDetected) || result.httpStatus >= 400 || !result.isHTTPS) {
      return result;
    }

    // Proceed with other checks if no significant issues are detected
    var content = response.getContentText();
    performOtherChecks(content, result);
  } catch (e) {
    console.log("Error fetching URL: " + url + ". Error: " + e.message);
    result.httpStatus = 500; // Set a default error HTTP status
    if (e.message.indexOf("DNS") !== -1) {
      result.dnsError = true;
    }
    return result; // Return result early due to an exception
  }

  return result;
}

function performOtherChecks(content, result) {
  var lowerContent = content.toLowerCase();

  // Check for GTM, GA, and conversion pixels
  result.hasGTM = hasGTM(lowerContent);
  if (result.hasGTM) {
    result.gtmDebugInfo = "GTM detected in response content.";
  } else {
    detectGAAndConversionPixel(lowerContent, result);
  }
}

function hasGTM(content) {
    var gtmPatterns = [
        'googletagmanager\\.com',
        'google tag manager',
        'gtm4wp\\.com',
        'gtm\\.js',
        'gtag/js',
        'gtag/js\\?id=',
        '\\bgtm-',
        '\\bgtag-',
        '<!-- Google Tag Manager -->',
        '"Google Gtag Pixel"', // Pattern for Shopify Google & YouTube app
        '<!-- Google Tag Manager (noscript) -->',
        '\\bGTM\\b',
        'src="data:text/javascript;base64,KGZ1bmN0aW9uKHcsZCxzLGwsaSl7d1tsXT13W2xdfHxbXTt3W2xdLnB1c2goeydndG0uc3RhcnQnOm5ldyBEYXRlKCkuZ2V0VGltZSgpLGV2ZW50OidndG0uanMnfSk7dmFyIGY9ZC5nZXRFbGVtZW50c0J5VGFnTmFtZShzKVswXSxqPWQuY3JlYXRlRWxlbWVudChzKSxkbD1sIT0nZGF0YUxheWVyJz8nJmw9JytsOicnO2ouYXN5bmM9ITA7ai5zcmM9J2h0dHBzOi8vd3d3Lmdvb2dsZXRhZ21hbmFnZXIuY29tL2d0bS5qcz9pZD0nK2krZGw7Zi5wYXJlbnROb2RlLmluc2VydEJlZm9yZShqLGYpfSkod2luZG93LGRvY3VtZW50LCdzY3JpcHQnLCdkYXRhTGF5ZXInLCdHVE0t' // Pattern for LSCWP base64 encoded GTM
    ];
    var regex = new RegExp(gtmPatterns.join('|'), 'i');
    return regex.test(content);
}

function detectGAAndConversionPixel(content, result) {
    var gaPatterns = [
        'google-analytics\\.com/analytics.js',
        'googletagmanager\\.com/gtag/js\\?id=',
        '\\bga\\('
    ];
    result.hasGA = gaPatterns.some(pattern => new RegExp(pattern, 'i').test(content));
    if (result.hasGA) {
        result.gaDebugInfo = "GA detected in response content.";
    }

    var conversionPatterns = [
        'googleadservices\\.com/pagead/conversion\\.js',
        'googleads\\.g\\.doubleclick\\.net/pagead/viewthroughconversion',
        'gtag\\(\'config\', \'AW-',
        'gtag\\(\'event\', \'conversion\', {'
    ];
    result.hasConversionPixel = conversionPatterns.some(pattern => new RegExp(pattern, 'i').test(content));
    if (result.hasConversionPixel) {
        result.conversionPixelDebugInfo = "Ads Pixel detected in response content.";
    }
}

function sendEmail(allResults, accountsChecked) {
  var subject = 'Google Ads: URL Issues Detected (MCC Level Report)';
  var body = '<html><body>';
  body += '<h2>Google Ads URL Issues Report (MCC Level)</h2>';
  body += '<p>Accounts checked: ' + accountsChecked + '</p>';
  body += '<table border="1" cellpadding="5" style="border-collapse: collapse;">';
  body += '<tr><th>Account Name</th><th>URL</th><th>Issue</th><th>Details</th></tr>';

  var sortedAccounts = Object.keys(allResults).sort();

  sortedAccounts.forEach(function(accountName) {
    var results = allResults[accountName];
    Object.keys(results).forEach(function(issueType) {
      results[issueType].forEach(function(issue) {
        var issueDetails = getIssueDetails(issueType, issue);
        body += '<tr><td>' + accountName + '</td><td>' + issue.url + '</td><td>' + issueType + '</td><td>' + issueDetails + '</td></tr>';
      });
    });
  });

  body += '</table>';
  body += '<p><a href="https://docs.google.com/spreadsheets/d/' + SPREADSHEET_ID + '/edit">View in Google Sheets</a></p>';
  body += '</body></html>';

  MailApp.sendEmail({
    to: EMAIL_ADDRESS,
    subject: subject,
    htmlBody: body
  });
}

function updateSpreadsheet(allResults, accountsChecked) {
  var spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
  var sheet = spreadsheet.getSheetByName(SHEET_NAME);

  if (!sheet) {
    sheet = spreadsheet.insertSheet(SHEET_NAME);
  }

  var lastRow = sheet.getLastRow();
  if (lastRow < 1) {
    lastRow = 1;
  }

  if (lastRow === 1 && sheet.getRange(1, 1).getValue() === "") {
    var headers = ['Date', 'Account Name', 'URL', 'Issue Type', 'Details'];
    sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
    lastRow = 1;
  }

  var currentDate = new Date().toISOString().split('T')[0];
  var data = [];

  var sortedAccounts = Object.keys(allResults).sort();
  sortedAccounts.forEach(function(accountName) {
    var results = allResults[accountName];
    Object.keys(results).forEach(function(issueType) {
      results[issueType].forEach(function(issue) {
        var issueDetails = getIssueDetails(issueType, issue);
        data.push([currentDate, accountName, issue.url, issueType, issueDetails]);
      });
    });
  });

  data.sort(function(a, b) {
    return a[1].localeCompare(b[1]);
  });

  if (data.length > 0) {
    sheet.getRange(lastRow + 1, 1, data.length, data[0].length).setValues(data);
  }

  var infoStartRow = lastRow + data.length + 2;
  sheet.getRange(infoStartRow, 1).setValue('Summary');
  sheet.getRange(infoStartRow + 1, 1).setValue('Date:');
  sheet.getRange(infoStartRow + 1, 2).setValue(currentDate);
  sheet.getRange(infoStartRow + 2, 1).setValue('Accounts Checked:');
  sheet.getRange(infoStartRow + 2, 2).setValue(accountsChecked);
  sheet.getRange(infoStartRow + 3, 1).setValue('Issues Found:');
  sheet.getRange(infoStartRow + 3, 2).setValue(data.length);
}

function getIssueDetails(key, item) {
  var details = '';
  if (key === 'missingTags') {
    details = 'Missing Tags: ' + item.missing;
  } else if (key === 'redirects') {
    var redirectPath = item.redirectDetails.map(r => r.url + ' (' + r.status + ')').join(' -> ');
    details = 'Redirect Path: ' + redirectPath;
  } else if (key === 'slowUrls') {
    details = 'Slow Load Time: ' + item.loadTime + ' ms';
  } else if (key === 'httpErrors') {
    details = 'HTTP Error: Status ' + item.status;
  } else if (key === 'mobileUnfriendly') {
    details = 'Not Mobile Friendly';
  } else if (key === 'dnsErrors') {
    details = 'DNS Error: Unable to resolve domain';
  } else if (key === 'notHTTPS') {
    details = 'Not using HTTPS';
  } else if (key === 'notFound') {
    details = '404 Not Found';
  } else {
    details = key + ': ' + JSON.stringify(item);
  }
  return details;
}

Any help will be appreciated!

Thanks,
Rahil

Google Ads Scripts Forum

unread,
Sep 5, 2024, 4:57:53 AM9/5/24
to Google Ads Scripts Forum

Hi Rahil,

Thank you for reaching out to the Google Ads Scripts support team.

I would like to inform you that the spreadsheet ID you have defined in the code is not a valid format. If you are defining a spreadsheet ID instead of a URL, then enter the ID that will be in between d/ and /edit (https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxx/edit#gid=0).

I hope this helps! Kindly get back to us with the below details if you still face any issues.

  • Google Ads account ID or CID
  • Name of the affected script
  • Shareable spreadsheet link that you are using any in the script

You can send the details via Reply privately to the author option, or direct private reply to this email.

Thanks,
Google Ads Scripts team

Rahil Shah (PHD Media)

unread,
Sep 10, 2024, 4:41:25 AM9/10/24
to Google Ads Scripts Forum
Hey Team,

Thanks for getting back to me.

So I have made sure the URL is the correct one. Not sure if I can make it shareable as my organization limits how I can share. So let me know if there are issues accessing it. 

Google Ads account is a MCC: 899-767-6533
Script is called: URL Checker

Thanks for your help.

Rahil Shah
Activation Account Director
+61 42 3313 915
Banner

From: Google Ads Scripts Forum <adwords...@googlegroups.com>
Sent: Thursday, 5 September 2024 18:57
To: Google Ads Scripts Forum <adwords...@googlegroups.com>
Subject: Re: URL Checker
 

CAUTION: This email originated from the Internet

--
-- You received this message because you are subscribed to the Google Groups AdWords Scripts Forum group. Please do not reply to this email. To post to this group or unsubscribe please visit https://developers.google.com/adwords/scripts/community.
---
You received this message because you are subscribed to a topic in the Google Groups "Google Ads Scripts Forum" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/adwords-scripts/bdUearkl4K8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to adwords-scrip...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/adwords-scripts/82333fea-9f1a-40cf-884a-745aa4e1d917n%40googlegroups.com.

This email is intended only for the person or entity to which it is addressed and may contain information that is privileged, confidential or otherwise protected from disclosure. Dissemination, distribution, or copying of this email or the information herein by anyone other than the intended recipient, or an employee or agent responsible for delivering the message to the intended recipient, is prohibited. If you have received this email in error, please notify the sender immediately.

Google Ads Scripts Forum Advisor

unread,
Sep 10, 2024, 8:30:42 AM9/10/24
to adwords...@googlegroups.com

Hi Rahil,

I would like to inform you that you have added the complete url of the spreadsheet in the SPREADSHEET_ID variable at line 19. You have to add a part of the url between d/ and /edit as you are taking ID as input instead of URL. Kindly add “1aWTtcVWDfOhYNjEoGxi-OAC1o8IQ1epz1i_haM6BrT8” instead of the complete url.

I hope this helps! Feel free to get back to us if you still face any issues. 

This message is in relation to case "ref:!00D1U01174p.!5004Q02vFzCz:ref" (ADR-00267534)

Thanks,
 
Google Logo Google Ads Scripts Team


hiền hoàng

unread,
Sep 11, 2024, 4:32:16 AM9/11/24
to Google Ads Scripts Forum on behalf of adsscripts
ok

--
-- You received this message because you are subscribed to the Google Groups AdWords Scripts Forum group. Please do not reply to this email. To post to this group or unsubscribe please visit https://developers.google.com/adwords/scripts/community.
---
You received this message because you are subscribed to the Google Groups "Google Ads Scripts Forum" group.
To unsubscribe from this group and stop receiving emails from it, send an email to adwords-scrip...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/adwords-scripts/vmMpi000000000000000000000000000000000000000000000SJLK2G005QIP2l53S5-NY7VZR5PDFw%40sfdc.net.

Guillermo Caruana

unread,
Sep 12, 2024, 4:56:50 AM9/12/24
to Google Ads Scripts Forum
Hi guys, could I ask you for the code of the repaired MCC link checker?
Or is it updated on the website?
Yours sincerely,
Reply all
Reply to author
Forward
0 new messages