Meta Snitch

Print page meta data like title, meta keywords, meta description, canonical URL, hreflang tags, OG tags and twitter cards to the console. Also detects different versions of Google Analytics and prints the measurement ID if it can find it.

  1. // ==UserScript==
  2. // @name Meta Snitch
  3. // @namespace https://github.com/appel/userscripts
  4. // @version 0.2.1
  5. // @description Print page meta data like title, meta keywords, meta description, canonical URL, hreflang tags, OG tags and twitter cards to the console. Also detects different versions of Google Analytics and prints the measurement ID if it can find it.
  6. // @author Ap
  7. // @match *://*/*
  8. // @grant none
  9. // @run-at document-end
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. "use strict";
  15.  
  16. const labelColor = "color: #00bcd4";
  17. const labelColorError = "color: #f44336";
  18.  
  19. // Ensure that it's running in the top-level browsing context and not inside an iframe
  20. if (window !== window.top) {
  21. return;
  22. }
  23.  
  24. // Only check html docs
  25. if (!document.contentType.startsWith("text/html")) {
  26. return;
  27. }
  28.  
  29. function isDevToolsOpen() {
  30. const devtools = function () { };
  31. devtools.toString = function () {
  32. this.opened = true;
  33. };
  34. console.debug(devtools);
  35. return devtools.opened || false;
  36. }
  37.  
  38. if (!isDevToolsOpen()) {
  39. return;
  40. }
  41.  
  42. console.group("Meta Tags");
  43.  
  44. // Print title
  45. console.log("%cPage Title%c " + document.title, labelColor, "");
  46.  
  47. // Print meta keywords
  48. var metaKeywords = document.querySelector('meta[name="keywords"]');
  49. if (metaKeywords) {
  50. console.log("%cMeta Keywords%c " + metaKeywords.content, labelColor, "");
  51. }
  52.  
  53. // Print meta description
  54. var metaDescription = document.querySelector('meta[name="description"]');
  55. if (metaDescription) {
  56. var fullDescription = metaDescription.content;
  57. var truncatedDescription = fullDescription.slice(0, 300);
  58. var displayDescription = fullDescription.length > 300 ? truncatedDescription + "..." : truncatedDescription;
  59. console.log("%cMeta Description%c " + displayDescription, labelColor, "");
  60. } else {
  61. console.log("%cNo meta description found.", labelColorError);
  62. }
  63.  
  64. // Print canonical URL
  65. var canonical = document.querySelector('link[rel="canonical"]');
  66. if (canonical) {
  67. console.log("%cCanonical%c " + canonical.href, labelColor, "");
  68. } else {
  69. console.log("%cNo canonical URL found.", labelColorError);
  70. }
  71.  
  72. // Print hreflang tags
  73. var hreflangTags = document.querySelectorAll('link[rel="alternate"][hreflang]');
  74. if (hreflangTags.length > 0) {
  75. hreflangTags.forEach(function (tag) {
  76. console.log("%cHreflang (" + tag.hreflang + ")%c " + tag.href, labelColor, "");
  77. });
  78. }
  79.  
  80. console.groupEnd();
  81.  
  82.  
  83. function printOgTag(properties) {
  84. let ogTagsFound = false;
  85. properties.forEach((property) => {
  86. const ogTag = document.querySelector(`meta[property="og:${property}"]`);
  87. if (ogTag) {
  88. ogTagsFound = true;
  89. console.log(`%cog:${property}%c ${ogTag.content}`, labelColor, "");
  90. }
  91. });
  92. }
  93.  
  94. function printTwitterTag(names) {
  95. let twitterTagsFound = false;
  96. names.forEach((name) => {
  97. const twitterTag = document.querySelector(`meta[name="twitter:${name}"]`);
  98. if (twitterTag) {
  99. twitterTagsFound = true;
  100. console.log(`%ctwitter:${name}%c ${twitterTag.content}`, labelColor, "");
  101. }
  102. });
  103. }
  104.  
  105. console.groupCollapsed("Open Graph / Twitter");
  106.  
  107. // Print Open Graph (OG) tags
  108. printOgTag(["title", "type", "image", "url", "description"]);
  109.  
  110. // Print Twitter tags
  111. printTwitterTag(["card", "title", "description", "image"]);
  112.  
  113. console.groupEnd(); // Open Graph / Twitter
  114. console.groupCollapsed("Structured data");
  115.  
  116. // Print Microdata
  117. const microdataItems = document.querySelectorAll("[itemscope]");
  118. if (microdataItems.length > 0) {
  119. microdataItems.forEach((item, index) => {
  120. console.groupCollapsed(`%cItem (${index + 1})%c ${item.getAttribute("itemtype")}`, labelColor, "");
  121. const itemProps = item.querySelectorAll("[itemprop]");
  122. itemProps.forEach((prop) => {
  123. console.log(`%c${prop.getAttribute("itemprop")}%c ${prop.content || prop.textContent.trim()}`, labelColor, "");
  124. });
  125. console.groupEnd();
  126. });
  127. }
  128.  
  129. // Find all ld+json script tags
  130. const ldJsonScripts = document.querySelectorAll('script[type="application/ld+json"]');
  131.  
  132. if (ldJsonScripts.length > 0) {
  133. ldJsonScripts.forEach((script, index) => {
  134. console.group(`%cLD+JSON (${index + 1})%c`, labelColor, "");
  135. try {
  136. // Parse and then stringify the JSON with indentation
  137. const json = JSON.parse(script.innerText);
  138. const formattedJson = JSON.stringify(json, null, 2);
  139. console.log(formattedJson);
  140. } catch (e) {
  141. console.log("%cError parsing JSON%c" + e.message, labelColorError, "");
  142. }
  143. console.groupEnd();
  144. });
  145. } else {
  146. console.log("%cNo LD+JSON scripts found.", labelColorError);
  147. }
  148.  
  149. console.groupEnd(); // Structured data
  150.  
  151.  
  152.  
  153. // Get all script tags
  154. const scripts = Array.from(document.getElementsByTagName("script"));
  155.  
  156. // Regular expressions for different versions of Google Analytics
  157. const versions = [
  158. {
  159. key: "GASC",
  160. name: "Google Analytics Synchronous Code (ga.js/2009)",
  161. regex: /_gat\._getTracker\(["'](UA-[^"']+)["']\)/
  162. },
  163. {
  164. key: "GAAC",
  165. name: "Google Analytics Asynchronous Code (ga.js/2009)",
  166. regex: /_gaq\.push\(\['_setAccount', ['"](UA-[^"']+)['"]\]\)/
  167. },
  168. {
  169. key: "UAT",
  170. name: "Universal Analytics Tag (analytics.js/2013)",
  171. regex: /ga\('create', ['"](UA-[^"']+)['"],/
  172. },
  173. {
  174. key: "GST",
  175. name: "Global Site Tag (gtag.js/2017)",
  176. regex: /gtag\('config', ['"](UA-[^"']+)['"]/
  177. },
  178. {
  179. key: "GA4",
  180. name: "Google Analytics 4 (GA4/2020)",
  181. regex: /gtag\('config', ['"](G-[^"']+)['"]/
  182. },
  183. {
  184. key: "GTM",
  185. name: "Google Tag Manager",
  186. regex: /(GTM-\w+)/
  187. }
  188. ];
  189.  
  190. // Iterate over all script tags
  191. scripts.forEach((script) => {
  192. // Convert HTMLScriptElement to string
  193. const scriptString = script.innerHTML;
  194.  
  195. // Iterate over all versions of Google Analytics
  196. versions.forEach((version) => {
  197. const match = scriptString.match(version.regex);
  198. if (match) {
  199. if (version.key === "GA4" || version.key === "GTM") {
  200. console.log(`[GA] %c${version.name}%c ${match[1]}`, labelColor, "");
  201. } else {
  202. console.log(`[GA] %c${version.name} ${match[1]}`, labelColorError);
  203. }
  204. }
  205. });
  206. });
  207. })();