Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <h1>Analyseur Top iTunes et Podcastéo</h1>
<div id="container">
  <div id="selector">
  <select id="pays_selector">
  <option value="podcasteo">Annuaire Podcastéo</option>
   <option value="podcasteo200">Top 200 Podcastéo</option>
  <option value="fr">Top 200 iTunes France</option>
  <option value="ue_uk">Top 200 iTunes UE + UK</option>
  <option value="franco">Top 200 iTunes Francophonie</option>
  <option value="us">Top 200 iTunes US</option>
  <option value="all"> Top 200 iTunes US + UE + UK + Francophonie</option>
   <option value="podcloud">Catalogue podCloud (export du 18 avril 2019) (lent)</option>
</select>
  <button id="fetch_data">Analyser</button>
  <div id="top_itunes">
  <label for="pays">Top iTunes à analyser&nbsp;:</label>
  <textarea id="pays" rows=2 cols=40></textarea>
  <div><small> (séparés par des virgules)</small></div>
  </div>
  </div>
  <div id="results">
    <div id="progress">
    </div>
    <label>Résultat de l'analyse :</label>
    <div id="resultats">
    </div>
  </div>
</div>
            
          
!
            
              html {
  font-family: Lato, Helvetica, "sans-serif", sans;
  
}

label {
  display: block;
  margin: 10px 0;
  font-weight: bold;
}

textarea {
  margin: 5px;
}

#fetch_data {
  width: 150px;
  margin: 0 10px
  padding: 5px;
}

#container {
  padding: 0 20px;
}

li {
  list-style-type: none;
  padding-left: 20px;
}

li span {
  display: inline-block;
  cursor: pointer;
  line-height: 18px;
  font-size: 15px;
  height: 20px;
}

li span em {
  display: inline-block;
  margin: 0 5px;
}

li span::before {
  content: "▸";
}

li.open span::before {
  content: "▾";
}

li span:hover, li span:focus {
  opacity: 0.8;
}

li ul {
  margin: 2px;
  padding: 0;
  display: none;
}

li.open ul {
  display: block;
}

li ul li {
  padding-left: 10px;
}

#fetch_data {
  display: inline-block;
  margin: 15px;
}

#selector,
#results {
  padding: 15px;
}
            
          
!
            
              Queue.configure(window.Promise);

/**
 * Retourne une fonction qui, tant qu'elle est appelée,
 * n'est exécutée au plus qu'une fois toutes les N millisecondes.
 * Paramètres :
 *  - func : la fonction à contrôler
 *  - wait : le nombre de millisecondes (période N) à attendre avant
 *           de pouvoir exécuter à nouveau la function func()
 *  - leading (optionnel) : Appeler également func() à la première
 *                          invocation (Faux par défaut)
 *  - trailing (optionnel) : Appeler également func() à la dernière
 *                           invocation (Faux par défaut)
 *  - context (optionnel) : le contexte dans lequel appeler func()
 *                          (this par défaut)
 */
function throttle(func, wait, leading, trailing, context) {
  var ctx, args, result;
  var timeout = null;
  var previous = 0;
  var later = function() {
    previous = new Date();
    timeout = null;
    result = func.apply(ctx, args);
  };
  return function() {
    var now = new Date();
    if (!previous && !leading) previous = now;
    var remaining = wait - (now - previous);
    ctx = context || this;
    args = arguments;
    // Si la période d'attente est écoulée
    if (remaining <= 0) {
      // Réinitialiser les compteurs
      clearTimeout(timeout);
      timeout = null;
      // Enregistrer le moment du dernier appel
      previous = now;
      // Appeler la fonction
      result = func.apply(ctx, args);
    } else if (!timeout && trailing) {
      // Sinon on s’endort pendant le temps restant
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
}

const uniqueOnly = (value, index, self) => self.indexOf(value) === index;

const groupBy = (xs, key) =>
  xs.reduce((rv, x) => {
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});

const opened = {};

const pays_ue_uk = [
  "at",
  "be",
  "bg",
  "cy",
  "cz",
  "de",
  "dk",
  "ee",
  "es",
  "fi",
  "fr",
  "gb",
  "hr",
  "hu",
  "ie",
  "it",
  "lt",
  "lu",
  "mt",
  "nl",
  "pl"
];

const pays_franco = [
  "fr",
  "be",
  "ch",
  "lu",
  "ca",
  "tn",
  "dz",
  "lb",
  "mg",
  "sn"
];

const pays_full = pays_ue_uk
  .concat(pays_franco)
  .concat(["us"])
  .filter(uniqueOnly);

const select_list = e => {
  const list_code = e.target.options[e.target.selectedIndex].value;

  list_text.value = (list_code === "podcloud"
    ? ["podcloud"]
    : list_code === "podcasteo"
      ? ["podcasteo"]
      : list_code === "podcasteo200"
        ? ["podcasteo200"]
        : list_code == "fr"
          ? ["fr"]
          : list_code == "us"
            ? ["us"]
            : list_code == "franco"
              ? pays_franco
              : list_code == "ue_uk" ? pays_ue_uk : pays_full
  ).join(", ");

  document.getElementById(
    "top_itunes"
  ).style.display = /podcasteo|podcloud/.test(list_code) ? "none" : "block";
};

const list_text = document.getElementById("pays");
const list_selector = document.getElementById("pays_selector");

list_selector.addEventListener("change", select_list);
select_list({ target: list_selector });

// Utilise un proxy pour permettre les requêtes depuis le navigateur :
// Contenu du fichier :
// <?php header('Access-Control-Allow-Origin: *'); echo file_get_contents($_REQUEST['url']);
const cors_proxy = url =>
  `https://podshows.fr/45df412r.php?url=${encodeURIComponent(url)}`;

const getHosting = url => ({
  url,
  hoster: [
    { name: "podCloud", regex: /(podcloud|lepodcast)\.fr/ },
    { name: "Ausha", regex: /ausha\.co/ },
    { name: "Pippa", regex: /pippa\.io/ },
    { name: "SoundCloud", regex: /feeds\.soundcloud\.com/ },
    { name: "Art19", regex: /rss\.art19\.com/ },
    { name: "Libsyn", regex: /libsyn(pro)?\.com/ },
    { name: "Acast", regex: /rss\.acast\.com/ },
    { name: "Megaphone", regex: /megaphone\.fm/ },
    { name: "Anchor", regex: /anchor\.fm/ },
    { name: "Spreaker", regex: /spreaker\.com/ },
    { name: "FeedBurner", regex: /feedburner\.com/ },
    { name: "Buzzsprout", regex: /buzzsprout\.com/ },
    { name: "Omnycontent", regex: /(omnycontent\.com|omni\.fm)/ },
    { name: "Whooshkaa", regex: /whooshkaa\.com/ },
    { name: "Castfire", regex: /castfire\.com/ },
    { name: "Transistor", regex: /transistor\.fm/ },
    { name: "Castfire", regex: /castfire\.com/ },
    { name: "Audioboom", regex: /audioboom\.com/ },
    { name: "Podbean", regex: /podbean\.com/ },
    { name: "Squarespace", regex: /squarespace\.com/ },
    { name: "podomatic", regex: /pod(o|0)matic\.com/ },
    { name: "djpod", regex: /feeds\.djpod\.com/ },
    { name: "rss.com", regex: /rss\.com/ },
    { name: "JellyCast", regex: /jellycast\.com/ },
    { name: "PodcastMirror", regex: /podcastmirror\.com/ },
    { name: "BackdoorPodcasts", regex: /backdoorpodcasts\.com/ },
    { name: "Fireside", regex: /fireside\.fm/ },
    { name: "Podiant", regex: /podiant\.co/ },
    { name: "blubrry", regex: /blubrry\.com/ },
    { name: "rapidfeeds.com", regex: /rapidfeeds\.com/ },
    { name: "podcaster.de", regex: /(podcaster|podspot)\.de/ },
    { name: "hipcast.com", regex: /hipcast\.com/ },
    { name: "simplecast.com", regex: /(rss|feeds)\.simplecast\.com/ },
    { name: "podigee", regex: /podigee\.io/ },
    { name: "Podfm.ru", regex: /podfm\.ru/ },
    {
      name: "Apple",
      regex: /(applehosted\.podcasts|itunesu\.itunes)\.apple\.com/
    },
    { name: "podcasts.com", regex: /podcasts\.com\/rss_feed/ },
    { name: "podcast.co", regex: /(feed\.pod|podcast).co\// },
    { name: "ivoox.com", regex: /ivoox\.com/ },
    { name: "audioblog.arteradio.com", regex: /audioblog\.arteradio\.com/ },
    { name: "hearthis.at", regex: /hearthis\.at/ }
  ].reduce(
    (found_hoster, hoster) =>
      hoster.regex.test(url) ? hoster.name : found_hoster,
    "Autre/Interne"
  )
});

const progress = document.getElementById("progress");
const resultats = document.getElementById("resultats");

let running = false;
let result_urls = {};
let result_urls_by_hoster = {};
let errors = 0;

const queue = new Queue(15);

const immediateStatus = () =>
  window.setTimeout(() => {
    if (queue.pendingPromises > 0 || queue.queue.length > 0) {
      progress.innerHTML = "Opérations en cours : " + queue.pendingPromises;
      progress.innerHTML += "<br>Opérations en attente : " + queue.queue.length;
    } else {
      progress.innerHTML = "Terminé.";
      progress.innerHTML +=
        "<br>" + Object.keys(result_urls).length + " flux trouvés. ";
      if (errors) {
        progress.innerHTML +=
          "<br>" +
          errors +
          " erreur(s). (l'API iTunes ne renvoit pas l'URL du flux) ";
      }

      running = false;
    }

    const count_by_host = Object.keys(result_urls_by_hoster)
      .map(k => ({
        hoster: k,
        size: result_urls_by_hoster[k].length,
        percent: (
          result_urls_by_hoster[k].length *
          100 /
          Object.keys(result_urls).length
        )
          .toFixed(3)
          .replace(/0+$/, "")
          .replace(/\.$/, "")
      }))
      .sort((a, b) => b.size - a.size);

    resultats.innerHTML = `
      <ul id='hosters'>
        ${count_by_host
          .map(hoster => {
            const liid = "hoster-" + hoster.hoster.replace(/[^A-z]/, "-");
            return `
            <li id="${liid}" class="${opened[liid] ? "open" : ""}">
              <span onclick="toggleVisibility('${liid}');">
                ${hoster.hoster}<em>;</em>${hoster.size}<em>;</em>${
              hoster.percent
            }%
              </span>
              ${
                opened[liid]
                  ? `
              <ul>
                ${result_urls_by_hoster[hoster.hoster]
                  .map(
                    feed => `
                  <li>
                    <a href="${feed.url}" target="_blank">
                      <pre>${feed.url}</pre>
                    </a>
                  </li>
                `
                  )
                  .join("")}
              </ul>
              `
                  : ""
              }
            </li>
          `;
          })
          .join("")}
    </ul>
    ${
      !running
        ? `
      <label>Tous les flux:</label>
      <pre style="width: 50%; height: 150px; overflow: scroll;">${Object.keys(
        result_urls
      ).join("\n")}</pre>
    `
        : ""
    }
  `;
  }, 0);

const status = throttle(immediateStatus, 1000, true, true);

const analyze_data = () => {
  if (running) return alert("Déjà en cours");
  running = true;

  progress.innerHTML = "";
  result_urls = {};
  result_urls_by_hoster = {};
  errors = 0;
  resultats.innerHTML = "";

  const countries = list_text.value.split(",").map(c => c.trim());

  const fetch_itunes_top = country =>
    fetch(
      cors_proxy(
        `https://rss.itunes.apple.com/api/v1/${country}/podcasts/top-podcasts/all/200/explicit.json`
      )
    )
      .then(res => res.json())
      .then(res => {
        return res.feed.results.map(p => p.url);
      });

  const fetch_podcasteo = () =>
    fetch(cors_proxy(`http://www.podcasteo.fr/annuaire.php`))
      .then(body => body.text())
      .then(body =>
        body
          .match(/<span class="username">\s+<a href=".*" target="_blank">/g)
          .map(m => {
            const match = m.match(/\"(https?:\/\/[^"]+)\"/);
            return match && match[1];
          })
          .filter(Boolean)
      );

  const fetch_top_podcasteo = () =>
    fetch(cors_proxy(`http://www.podcasteo.fr/classement.php`))
      .then(body => body.text())
      .then(body =>
        body
          .match(
            /<div class="box box-widget widget-user">\s+<a href=".*" target="_blank" data-toggle="modal">/g
          )
          .map(m => {
            const match = m.match(/\"(https?:\/\/[^"]+)\"/);
            return match && match[1];
          })
          .filter(Boolean)
          .slice(0, 200)
      );

  const fetch_podcloud = () =>
    fetch(
      cors_proxy(
        `http://assets.podcloud.fr/catalogue/catalogue_podCloud_2019_04_18_6989_podcasts.json`
      )
    )
      .then(body => body.json())
      .then(list => list.map(p => p.RSS).filter(Boolean));

  const add_feed_to_result = feedUrl => {
    if (!result_urls[feedUrl]) {
      const feed = getHosting(feedUrl);
      result_urls[feedUrl] = feed;
      (result_urls_by_hoster[feed.hoster] =
        result_urls_by_hoster[feed.hoster] || []).push(feed);
    }

    status();
  };

  const add_itunes_feed_to_result = url => {
    var match = url.match(/id(\d+)/);
    if (!match) match = url.match(/\d+/); // 123456

    if (!match) {
      console.error(
        "Could not find Apple object ID for : " + url,
        `${++errors} error(s)`
      );
      return false;
    }

    var artID = match[1];

    queue.add(() => {
      return fetch(
        cors_proxy(
          `https://itunes.apple.com/lookup?id=${parseInt(artID)}&entity=podcast`
        )
      )
        .then(res => {
          try {
            return res.json();
          } catch (e) {
            console.error(
              "Could not parse iTunes response for feed : " + url,
              res,
              `${++errors} error(s)`,
              e
            );
          }

          return null;
        })
        .then(body => {
          if (!body)
            return console.error(
              "No match for feed : " + url,
              `${++errors} error(s)`
            );

          if (!body.results || !body.results[0])
            return console.error(
              "No result in body for feed : " + url,
              artID,
              body,
              `${++errors} error(s)`
            );

          const feedUrl = body.results[0].feedUrl;

          if (!(typeof feedUrl === "string"))
            return console.error(
              "Feed URL is not a string in result for feed  : " + url,
              body,
              `${++errors} error(s)`
            );

          add_feed_to_result(feedUrl);
        });
    });
  };

  countries.forEach(c =>
    queue.add(() => {
      status();

      const fetch_list =
        c === "podcloud"
          ? fetch_podcloud()
          : c === "podcasteo"
            ? fetch_podcasteo()
            : c === "podcasteo200"
              ? fetch_top_podcasteo()
              : fetch_itunes_top(c);

      return fetch_list.then(list => {
        list.forEach(url =>
          queue.add(() => {
            if (typeof url === "string") {
              if (/^https?:\/\/(podcasts|itunes)\.apple\.com/.test(url)) {
                add_itunes_feed_to_result(url);
              } else {
                add_feed_to_result(url);
              }
            } else {
              console.error("URL was not a string : ", url);
            }
            status();
          })
        );
      });
    })
  );

  status();
};

document.getElementById("fetch_data").addEventListener("click", analyze_data);

window.toggleVisibility = id => {
  console.log(id);
  const el = document.getElementById(id);
  console.log(el);
  el.classList.toggle("open");
  opened[id] = el.classList.contains("open");
  immediateStatus();
};

            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console