Normal JavaScript mode

Note

For more examples see the Modules Repo

In the normal Javascript mode Sora will scrape the HTML of a link and provide it to the function. You are then required to scrape the necessary detail from the HTML and rewrite it into the follow specified JSON format.

Functions

searchResults

Input: HTML
Output:JSON

Extracts the search results from the provided HTML.

{
   "title": "Example Title",
   "image": "https://example.com/image.jpg",
   "href": "https://grani.me/example"
}

extractDetails

Input: HTML
Output:JSON

Extracts the details from the provided HTML.

{
   "description": "An exciting anime series about adventures.",
   "aliases": "Alternate Name",
   "airdate": "2022"
}

extractEpisodes

Input: HTML
Output:JSON

Extracts the episodes from the provided HTML.

{
   "href": "https://grani.me/episode/123",
   "number": "1"
}

extractStreamUrl

Input: HTML
Output:URL

Extracts the stream from the provided HTML.

https://example.com/stream/video.mp4

Example

function cleanTitle(title) {
    //Module specefic function, ignore
    return title
        .replace(/’/g, "'")  
        .replace(/–/g, "-")  
        .replace(/&#[0-9]+;/g, ""); 
}

function searchResults(html) {
    const results = [];
    const baseUrl = "https://grani.me/";

    const filmListRegex = /<div class="content_episode"[\s\S]*?<\/div>\s*<\/div>\s*<\/div>/g;
    const items = html.match(filmListRegex) || [];


    items.forEach((itemHtml, index) => {
      const titleMatch = itemHtml.match(/<a class="cona" href="([^"]+)">([^<]+)<\/a>/);
      const href = titleMatch ? titleMatch[1] : '';
      let title = titleMatch ? titleMatch[2] : '';  
      title = cleanTitle(title);
      const imgMatch = itemHtml.match(/<img[^>]*class="coveri"[^>]*src="([^"]+)"[^>]*>/);
      const imageUrl = imgMatch ? imgMatch[1] : '';
      
      if (title && href) {
          results.push({
              title: title.trim(),
              image: imageUrl.trim(),
              href: href.trim()
          });
      }
  });
  
  return results;
}

function extractDetails(html) {
   const details = [];

   const descriptionMatch = html.match(/<div class="infodes2 entry-content entry-content-single" itemprop="description">[\s\S]*?<p>([\s\S]*?)<\/p>/);
   let description = descriptionMatch ? descriptionMatch[1] : '';

   const aliasesMatch = html.match(/<h1 class="entry-title" itemprop="name""([^"]+)">/);
   let aliases = aliasesMatch ? aliasesMatch[1] : '';

   const airdateMatch = html.match(/<div class="textd">Year:<\/div>\s*<div class="textc">([^<]+)<\/div>/);
   let airdate = airdateMatch ? airdateMatch[1] : '';

   if (description && airdate) {
       details.push({
           description: description,
           aliases: aliases || 'N/A',
           airdate: airdate
       });
   }

   return details;
}

function extractEpisodes(html) {
   const episodes = [];
   const baseUrl = "https://grani.me/";

   const episodeLinks = html.match(/<a class="infovan"[^>]*href="([^"]+)"[\s\S]*?<div class="centerv">(\d+)<\/div>/g);
   
   if (!episodeLinks) {
       return episodes;
   }

   episodeLinks.forEach(link => {
       const hrefMatch = link.match(/href="([^"]+)"/);
       const numberMatch = link.match(/<div class="centerv">(\d+)<\/div>/);

       if (hrefMatch && numberMatch) {
           let href = hrefMatch[1];
           const number = numberMatch[1];

           if (!href.startsWith("https")) {
               href = href.startsWith("/") ? baseUrl + href.slice(1) : baseUrl + href;
           }

           episodes.push({
               href: href,
               number: number
           });
       }
   });
   episodes.reverse();
   return episodes;
}

function extractStreamUrl(html) {
    const sourceRegex = /<source[^>]+id="iframevideo"[^>]+src="([^"]+)"/;
    const match = html.match(sourceRegex);
    return match ? match[1] : null;
}