/* @since 2024-03-05 NicoCache_nl シス�?��ファイル. Domand用にURLインジェクションする.
 * 2023年11月に部�?���?され2024年2月から本格導�?された�?動画配信仕�?(Domand,CMAF,DMS)では�?
 * 要求URLに動画ID(smXXX)が乗らな�?���?��jsonにも現れな�?. リファラにも乗らな�?.
 * nicocache_nlのサーバ�?側に伝えるために、要�?APIを乗っ取ってURLに動画IDなど�??�を乗せ�?.
 * searchに乗った情報はニコ動サーバ�?に伝わる前にDefaultRequestFilter.javaで消す.
 * �?onRequestではrequestHeader.getParameter("nicocachenl_XXX")の形式で値を取れる.
 */

// Web Workerでも動くよ�?��window変数ではなくself変数を使�?.

// こ�?コード�? NicoCache_nl.watch, NicoCache_nl.watch.apiData.video.id を使�?��
// とを�??して�?���?. 使った方が綺麗かど�?��の検証をして�?���?.


(function() {
  'use strict';

  /*
   * - threadId: コメントスレ�?��idの意味. /watch/以降に現れることがある数字�?�??id.
   * - videoId: sm,nm,soで始まるよ�?��id.
   * - 他JSがこれを読みたい場合あるか�?.
   */
  const threadIdToVideoId = {};

  // - 2024-09-03: 機�?して�?���?. js-initial-watch-dataと�?��idを持つhtml elementは
  //   jsが実行される前�?htmlにも、jsが実行された後�?htmlにも存在しな�?.
  /* jsonから上記連想配�?を追�?. */
  function initThreadIdToVideoId() {
    /* 「お探し�?動画は視�?できません」ではnull. */
    const watch_data = document.getElementById("js-initial-watch-data");
    if (watch_data === null) {
      return;
    };

    const api_data = watch_data.getAttribute("data-api-data");
    if (api_data == null) {
      return; /* 異常. */
    };

    let api_json;
    try {
      api_json = JSON.parse(api_data);
    } catch (e) {
      return; /* 異常. */
    };

    if (api_json === null ||
        api_json.comment === undefined ||
        api_json.comment === null ||
        api_json.comment.threads === undefined ||
        api_json.comment.threads === null ||
        !(api_json.comment.threads instanceof Array)) {
      return; /* 正常 | 異常. */
    };

    for (const thread of api_json.comment.threads) {
      if (thread.id !== undefined &&
          thread.videoId !== undefined) {
        threadIdToVideoId[thread.id] = thread.videoId;
      };
    };
  };


  function log_bottom(s) {
    const div = document.createElement("div");
    const br = document.createElement("br");
    div.textContent = s;
    if (document.body != null) {
      document.body.appendChild(div);
      document.body.appendChild(br);
    };
  };

  function add_search(url, search) {
    if (url.includes('?')) {
      return url + '&' + search;
    };
    return url + '?' + search;
  };


  function domain_starts_with(url, x) {
    if (url.startsWith("https://" + x + "/")) {
      return true;
    };
    return url.startsWith("//" + x + "/");
  };

  /*
   * - [NicoCacheのjava側]に伝える情報を付け足したurlを返す.
   * - 動作非対象のurlはそ�?まま返す.
   */
  function might_add_video_info(url) {

    /*
     * if (url.includes(".key")) {log_bottom(url);};
     */

    /* インジェクション動作対象URL. �??�なのはマスターm3u8を示すURL. */

    if (domain_starts_with(url, 'asset.domand.nicovideo.jp')) {
      /*
       * - "CMAF付与情報取得失�?"エラーの出力を抑制する.
       */
      return add_search(url, 'nicocachenl_noerror=true');
    };

    if (! domain_starts_with(url, 'delivery.domand.nicovideo.jp')) {
      return url;
    };

    /*
     * embed.nicovideo.jpなどを除外す�?.
     */
    if (location.host === 'www.nicovideo.jp'
        && location.pathname.startsWith('/watch/')) {
      // do nothing.
    } else {
      return add_search(url, 'nicocachenl_noerror=true');
    };


    const [video_type, video_id] = (() => {
      let rest = location.pathname.substring("/watch/".length);

      if (rest.match(/^[0-9]/)) {
        /* - restはコメントスレ�?��ID. */
        /* - それをsmXXXへ.            */
        if (undefined === threadIdToVideoId[rest]) {
          return [null, null]; /* 失�?. 異常. */
        };
        rest = threadIdToVideoId[rest];
      };

      /* 英字部と数字部に�?���?. */
      const m = rest.match(/^(nm|sm|so)([0-9]+)/);
      if (m === null) {
        return [null, null];
      };
      return [m[1], m[2]];
    })();

    if (video_id === null) {
      return url;
    };

    const asearch = 'nicocachenl_video_id=' + video_id
            + '&nicocachenl_video_type=' + video_type;

    return add_search(url, asearch);
  };


  function wrap_fetch() {
    const original_fetch = self.fetch;
    const fetch = function fetch(input, ...rest) {
      if (input === undefined
          || input === null
          || input.constructor === undefined) {
        // do nothing.
      }
      else if (input.constructor.name === 'Request') {
        // console.log('fetch req url "' + input.url + '"');
        // do nothing.
      }
      else {
        /* タイ�?シフト予�?��そ�?削除がここを通る */
        // console.log('fetch txt url "' + input + '"');
        input = might_add_video_info(`${input}`);
      };
      return original_fetch(input, ...rest);
    };
    self.fetch = fetch;
  };


  function wrap_XMLHttpRequest_open() {
    const original_open = XMLHttpRequest.prototype.open;
    const open = function open(method, url, ...rest) {
      /*
       * - 2024年8月時点、ここを通ってdelivery.domand.nicovideo.jpへのリクエスト�?実行される.
       */
      url = might_add_video_info(url);
      // console.log(`xml url "${url}"`);
      return original_open.call(this, method, url, ...rest);
    };
    XMLHttpRequest.prototype.open = open;
  };

  function wrap_Request() {
    const original_Request = self.Request;
    // 2024-08 watchペ�?ジはRequestを使って取得される.

    self.Request = new Proxy(Request, {
      construct: function(target, argumentsList, newTarget) {
        // console.log("//Request.[[constructor]]");
        if (0 !== argumentsList.length) {
          const url = argumentsList[0];
          // console.log(`Req url "${url}"`);
          if (null !== url && undefined === url) {
            // do nothing.
          }
          else if(url.constructor === String) {
            argumentsList[0] = might_add_video_info(url);
          };
        };
        return new target(...argumentsList);
      }
    });

  };


  if (location.pathname.startsWith("/watch/") &&
      location.host === 'www.nicovideo.jp') {
    // do nothing
  }
  else {
    // Request�?fetchの例で予想外�?エラーを引き起こして�?��.
    // 被害縮小�?ためにwatchペ�?ジ以外では動作させな�?.
    return;
  };

  wrap_Request();
  wrap_XMLHttpRequest_open();
  wrap_fetch();

  if (document.readyState === 'complete') {
    initThreadIdToVideoId();
  } else {
    document.addEventListener("DOMContentLoaded", initThreadIdToVideoId);
  };

})();