DuoFarmer

[ LITE ] DuoFarmer is a tool that helps you earn XP in Duolingo at blazing speed.

Asenna tämä skripti?
Author's suggested script

Saatat myös pitää

Asenna tämä skripti
// ==UserScript==
// @name         DuoFarmer
// @namespace    https://duo-farmer.vercel.app
// @version      1.2
// @description  [ LITE ] DuoFarmer is a tool that helps you earn XP in Duolingo at blazing speed.
// @description:vi [ LITE ] DuoFarmer là một công cụ giúp bạn hack XP trong Duolingo với tốc độ cực nhanh.
// @description:fr [ LITE ] DuoFarmer est un outil qui vous aide à gagner de l'XP sur Duolingo à une vitesse fulgurante.
// @description:es [ LITE ] DuoFarmer es una herramienta que te ayuda a ganar XP en Duolingo a una velocidad asombrosa.
// @description:de [ LITE ] DuoFarmer ist ein Tool, das Ihnen hilft, in Duolingo blitzschnell XP zu verdienen.
// @description:it [ LITE ] DuoFarmer è uno strumento che ti aiuta a guadagnare XP su Duolingo a una velocità incredibile.
// @description:ja [ LITE ] DuoFarmer は、Duolingo で驚異的なスピードで XP を獲得するのに役立つツールです。
// @description:ko [ LITE ] DuoFarmer는 Duolingo에서 엄청난 속도로 XP를 얻을 수 있도록 도와주는 도구입니다.
// @description:ru [ LITE ] DuoFarmer - это инструмент, который помогает вам зарабатывать XP в Duolingo с невероятной скоростью.
// @description:zh-CN [ LITE ] DuoFarmer 是一款可帮助您以惊人的速度在 Duolingo 中赚取 XP 的工具。
// @author       Lamduck
// @match        https://*.duolingo.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=duolingo.com
// @grant        none
// @license      none
// ==/UserScript==

const VERSION = "1.2";
const DELAY = 500;
var jwt, defaultHeaders, userInfo, sub;
let isRunning = false;

const initInterface = () => {
  const containerHTML = `
  <link rel="stylesheet" href="xp.css" />
<div id="_overlay"></div>
<div id="_container">
  <div id="_header">
    <span class="_label">Duofarmer minimal UI</span>
  </div>
  <div id="_body">
    <table id="_table_main" class="_table">
      <thead>
        <tr>
          <th>Username</th>
          <th>From</th>
          <th>Learning</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td id="_username">duofarmer</td>
          <td id="_from">any</td>
          <td id="_learn">any</td>
        </tr>
      </tbody>
    </table>
    <table id="_table_progress" class="_table">
      <thead>
        <tr>
          <th>Streak</th>
          <th>Gem</th>
          <th>XP</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td id="_streak">0</td>
          <td id="_gem">0</td>
          <td id="_xp">0</td>
        </tr>
      </tbody>
    </table>
    <div id="_action_row">
      <select id="_select_option">
        <!-- <option value="option1">Option 1</option> -->
        <!-- <option value="option2">Option 2</option> -->
      </select>
      <button id="_start_btn">Start</button>
      <button id="_stop_btn" hidden>Stop</button>
    </div>
    <div id="_notify">
      This is a minimal version that reduces device resources. <br />
      Only support learning language is English. <br />
      (will not work if from language is also English) <br>
      <br>
      Recommended to go to blank page for max performance + reduce resource usage.
    </div>
    <a id="_blank_page_link" href="https://www.duolingo.com/errors/404.html">Blank page (click here)</a>
  </div>
  <div id="_footer">
    <a href="https://greasyfork.org/vi/scripts/528621-duofarmer" target="_blank"
      >Greasyfork</a
    >
    <a href="https://t.me/duofarmer" target="_blank">Telegram</a>
    <a>Version <span id="_version">1.0</span></a>
  </div>
</div>
<div id="_floating_btn">😼</div>
`;

  const style = document.createElement("style");
  style.innerHTML = `#_container {
  width: 90vw;
  max-width: 800px;
  min-height: 40vh;
  max-height: 90vh;
  background: #222;
  color: #fff;
  border-radius: 10px;
  box-shadow: 0 2px 12px #0008;
  font-family: sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 9999;
}

#_header {
  height: 60px;
  background: #333;
  display: flex;
  align-items: center;
  justify-content: center;
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
  width: 100%;
}

#_body {
  min-height: 40vh;
  max-height: 100%;
  min-width: 0;
  background: #282828;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  overflow-y: auto;
  flex: 1;
  flex-direction: column;
}

#_footer {
  height: 50px;
  background: #222;
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  border-bottom-left-radius: 10px;
  border-bottom-right-radius: 10px;
  width: 100%;
}

._label {
  font-size: 1em;
}

#_header ._label {
  font-size: 1.5em;
  font-style: italic;
  font-weight: bold;
  color: #fac8ff;
}

#_body ._label {
  font-size: 1.2em;
}

._table {
  width: 100%;
  background: #232323;
  color: #fff;
  border-radius: 8px;
  padding: 8px 12px;
  text-align: center;
  table-layout: fixed;
}

._table th,
._table td {
  padding: 9px 12px;
  text-align: center;
  border-bottom: 1px solid #444;
  width: 1%;
}

._table tbody tr:last-child td {
  border-bottom: none;
}

#_body h3 {
  margin: 0;
  color: #fff;
  font-size: 1.1em;
  font-weight: bold;
  letter-spacing: 1px;
}

#_action_row {
  width: 90%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 8px 0;
  gap: 8px;
}
#_select_option {
  width: 90%;
  max-width: 90%;
  margin-right: 8px;
  padding: 8px 12px;
  border-radius: 6px;
  border: 1px solid #444;
  background: #232323;
  color: #fff;
  font-size: 1em;
  outline: none;
}

#_start_btn, #_stop_btn {
  width: auto;
  margin-left: 0;
  padding: 8px 18px;
  border-radius: 6px;
  border: none;
  background: #229100;
  color: #fff;
  font-size: 1em;
  font-weight: bold;
  cursor: pointer;
  box-shadow: 0 2px 8px #0003;
}
#_stop_btn {
  background: #af0303;
}
._disable_btn {
  background: #52454560 !important;
  cursor: not-allowed !important;
}

#_notify {
  width: 90%;
  max-width: 90%;
  min-height: 10vh;
  margin: 8px 0;
  padding: 8px 12px;
  border-radius: 6px;
  background: #333;
  color: #c8ff00;
  font-size: 1em;
  word-wrap: break-word;
  /* text-align: center; */
}

#_blank_page_link {
  margin-bottom: 8px;
  color: #fce6ff;
  font-weight: bold;
  font-style: italic;
}

#_footer a,
#_footer span {
  text-decoration: none;
  color: #00aeff;
  font-size: 1em;
  font-weight: bold;
  font-style: italic;
}

#_overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0,0,0,0.8);
  z-index: 9998;
  pointer-events: all;
}

#_floating_btn {
  position: fixed;
  bottom: 10%;
  right: 2%;
  width: 40px;
  height: 40px;
  background: #35bd00;
  border-radius: 50%;
  box-shadow: 0 2px 8px #0005;
  z-index: 10000;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hidden{
  display: none;
}`;
  document.head.appendChild(style);

  const container = document.createElement("div");
  container.innerHTML = containerHTML;
  document.body.appendChild(container);

  // doi phien ban
  const version = document.getElementById("_version");
  version.innerText = VERSION;
};

const setInterfaceVisible = (visible) => {
  const container = document.getElementById("_container");
  const overlay = document.getElementById("_overlay");
  container.style.display = visible ? "flex" : "none";
  overlay.style.display = visible ? "block" : "none";
};

const isInterfaceVisible = () => {
  const container = document.getElementById("_container");
  return container.style.display !== "none" && container.style.display !== "";
};

const toggleInterface = () => {
  setInterfaceVisible(!isInterfaceVisible());
};

const addEventFloatingBtn = () => {
  const floatingBtn = document.getElementById("_floating_btn");
  const startBtn = document.getElementById("_start_btn");
  const stopBtn = document.getElementById("_stop_btn");
  const select = document.getElementById("_select_option");
  floatingBtn.addEventListener("click", () => {
    if (isRunning) {
      if (confirm("Duofarmer is farming. Do you want to stop and hide UI?")) {
        isRunning = false;
        stopBtn.hidden = true;
        startBtn.hidden = false;
        startBtn.disabled = true;
        startBtn.className = "_disable_btn";
        select.disabled = false;
        setTimeout(() => {
          startBtn.className = "";
          startBtn.disabled = false;
        }, 2000);
        setInterfaceVisible(false);
        return;
      } else {
        // Không làm gì nếu không muốn dừng
        return;
      }
    }
    setInterfaceVisible(!isInterfaceVisible());
  });
};

const addEventStartBtn = () => {
  const startBtn = document.getElementById("_start_btn");
  const stopBtn = document.getElementById("_stop_btn");
  const select = document.getElementById("_select_option");
  startBtn.addEventListener("click", async () => {
    isRunning = true;
    startBtn.hidden = true;
    stopBtn.hidden = false;
    stopBtn.disabled = true;
    stopBtn.className = "_disable_btn";
    select.disabled = true;

    // Lấy option đang chọn
    const selected = select.options[select.selectedIndex];
    const optionData = {
      type: selected.getAttribute("data-type"),
      amount: Number(selected.getAttribute("data-amount")),
      from: selected.getAttribute("data-from"),
      learn: selected.getAttribute("data-learn"),
      value: selected.value,
      label: selected.textContent,
    };
    await farmSelectedOption(optionData);

    setTimeout(() => {
      stopBtn.className = "";
      stopBtn.disabled = false;
    }, 2000);
  });
};

const addEventStopBtn = () => {
  const startBtn = document.getElementById("_start_btn");
  const stopBtn = document.getElementById("_stop_btn");
  const select = document.getElementById("_select_option");
  stopBtn.addEventListener("click", () => {
    isRunning = false;
    stopBtn.hidden = true;
    startBtn.hidden = false;
    startBtn.disabled = true;
    startBtn.className = "_disable_btn";
    select.disabled = false;
    setTimeout(() => {
      startBtn.className = "";
      startBtn.disabled = false;
    }, 2000);
  });
};

const addEventVersionLink = () => {
  const versionLink = document.getElementById("_version");
  versionLink.addEventListener("click", () => {
    prompt("Your JWT token: ", jwt);
  });
};

const addEventListeners = () => {
  addEventFloatingBtn();
  addEventStartBtn();
  addEventStopBtn();
  addEventVersionLink();
};

// Thêm hàm tạo option cho select
const populateOptions = () => {
  const select = document.getElementById("_select_option");
  select.innerHTML = "";
  // Lấy fromLanguage và learningLanguage từ userInfo nếu có
  const fromLang = userInfo?.fromLanguage || "ru";
  const learnLang = userInfo?.learningLanguage || "en";
  // Danh sách option mẫu
  const options = [
    { type: "gem", label: `Gem 30`, value: `gem-30`, amount: 30 },
    {
      type: "xp",
      label: `XP 499 (any -> en)`,
      value: `xp-499`,
      amount: 499,
      from: fromLang,
      learn: "en",
    },
    {
      type: "streak",
      label: `Streak repair (restore frozen streak)`,
      value: `repair`,
    },
    {
      type: "streak",
      label: `Streak farm (beta test)`,
      value: `farm`,
    },
  ];
  options.forEach((opt) => {
    const option = document.createElement("option");
    option.value = opt.value;
    option.textContent = opt.label;
    option.setAttribute("data-type", opt.type);
    option.setAttribute("data-amount", opt.amount);
    option.setAttribute("data-from", opt.from);
    option.setAttribute("data-learn", opt.learn);
    select.appendChild(option);
  });
};

const updateNotify = (message) => {
  const notify = document.getElementById("_notify");
  const now = new Date().toLocaleTimeString();
  notify.innerText = `[${now}] ` + message;
};

const disableInterface = (notify = {}) => {
  const startBtn = document.getElementById("_start_btn");
  const stopBtn = document.getElementById("_stop_btn");
  const select = document.getElementById("_select_option");
  startBtn.disabled = true;
  startBtn.className = "_disable_btn";
  stopBtn.disabled = true;
  select.disabled = true;

  if (notify) {
    const notifyElement = document.getElementById("_notify");
    notifyElement.innerText = notify;
  }
};

const resetStartStopBtn = () => {
  isRunning = false;
  const startBtn = document.getElementById("_start_btn");
  const stopBtn = document.getElementById("_stop_btn");
  const select = document.getElementById("_select_option");
  stopBtn.hidden = true;
  startBtn.hidden = false;
  startBtn.disabled = true;
  startBtn.className = "_disable_btn";
  select.disabled = false;
  setTimeout(() => {
    startBtn.className = "";
    startBtn.disabled = false;
  }, 2000);
};

const blockStopBtn = () => {
  const stopBtn = document.getElementById("_stop_btn");
  stopBtn.disabled = true;
  stopBtn.classList.add("_disable_btn");
};

const unblockStopBtn = () => {
  const stopBtn = document.getElementById("_stop_btn");
  stopBtn.disabled = false;
  stopBtn.classList.remove("_disable_btn");
};

//--------------------Logic--------------------//

const getJwtToken = () => {
  var cookies = document.cookie.split(";");
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i].trim();
    if (cookie.startsWith("jwt_token=")) {
      return cookie.substring("jwt_token=".length);
    }
  }
  return null;
};

const decodeJwtToken = (token) => {
  var base64Url = token.split(".")[1];
  var base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
  var jsonPayload = decodeURIComponent(
    atob(base64)
      .split("")
      .map(function (c) {
        return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
      })
      .join("")
  );

  return JSON.parse(jsonPayload);
};

const formatHeaders = (jwt) => ({
  "Content-Type": "application/json",
  Authorization: "Bearer " + jwt,
  "User-Agent": navigator.userAgent,
});

const getUserInfo = async (sub) => {
  const userInfoUrl = `https://www.duolingo.com/2017-06-30/users/${sub}?fields=id,username,fromLanguage,learningLanguage,streak,totalXp,level,numFollowers,numFollowing,gems,creationDate,streakData`;
  let response = await fetch(userInfoUrl, {
    method: "GET",
    headers: defaultHeaders,
  });
  return await response.json();
};

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const updateUserInfo = () => {
  const username = document.getElementById("_username");
  const from = document.getElementById("_from");
  const learn = document.getElementById("_learn");
  const streak = document.getElementById("_streak");
  const gem = document.getElementById("_gem");
  const xp = document.getElementById("_xp");
  username.innerText = userInfo.username;
  from.innerText = userInfo.fromLanguage;
  learn.innerText = userInfo.learningLanguage;
  streak.innerText = userInfo.streak;
  gem.innerText = userInfo.gems;
  xp.innerText = userInfo.totalXp;
};

const toTimestamp = (dateStr) => {
  return Math.floor(new Date(dateStr).getTime() / 1000);
};

const daysBetween = (startTimestamp, endTimestamp) => {
  return Math.floor((endTimestamp - startTimestamp) / (60 * 60 * 24));
};

const sendRequest = async ({ url, payload, headers, method = "PUT" }) => {
  try {
    const res = await fetch(url, {
      method,
      headers,
      body: payload ? JSON.stringify(payload) : undefined,
    });
    return res;
  } catch (error) {
    return error;
  }
};

const sendRequestWithDefaultHeaders = async ({
  url,
  payload,
  headers = {},
  method = "GET",
}) => {
  const mergedHeaders = { ...defaultHeaders, ...headers };
  return sendRequest({ url, payload, headers: mergedHeaders, method });
};

const farmGemOnce = async () => {
  const idReward =
    "SKILL_COMPLETION_BALANCED-dd2495f4_d44e_3fc3_8ac8_94e2191506f0-2-GEMS";
  const patchUrl = `https://www.duolingo.com/2017-06-30/users/${sub}/rewards/${idReward}`;
  const patchData = {
    consumed: true,
    learningLanguage: userInfo.learningLanguage,
    fromLanguage: userInfo.fromLanguage,
  };
  return await sendRequestWithDefaultHeaders({
    url: patchUrl,
    payload: patchData,
    method: "PATCH",
  });
};

const farmGemLoop = async () => {
  const gemFarmed = 30;
  while (isRunning) {
    try {
      await farmGemOnce();
      userInfo = { ...userInfo, gems: userInfo.gems + gemFarmed };
      updateNotify(`You got ${gemFarmed} gem!!!`);
      updateUserInfo();
      await delay(DELAY);
    } catch (error) {
      updateNotify(
        `Error ${error.status}! Please record screen and report in telegram group!`
      );
      await delay(DELAY + 1000);
    }
  }
};

const farmXpOnce = async (amount) => {
  const startTime = Math.floor(Date.now() / 1000);
  const fromLanguage = userInfo.fromLanguage;
  const completeUrl = `https://stories.duolingo.com/api2/stories/en-${fromLanguage}-the-passport/complete`;
  const payload = {
    awardXp: true,
    isFeaturedStoryInPracticeHub: false,
    completedBonusChallenge: true,
    mode: "READ",
    isV2Redo: false,
    isV2Story: false,
    isLegendaryMode: true,
    masterVersion: false,
    maxScore: 0,
    numHintsUsed: 0,
    score: 0,
    startTime: startTime,
    fromLanguage: fromLanguage,
    learningLanguage: "en",
    hasXpBoost: false,
    happyHourBonusXp: 449,
  };
  return await sendRequestWithDefaultHeaders({
    url: completeUrl,
    payload: payload,
    headers: defaultHeaders,
    method: "POST",
  });
};

const farmXpLoop = async (amount) => {
  while (isRunning) {
    try {
      const response = await farmXpOnce(amount);
      if (response.status == 500) {
        updateNotify(
          "Make sure you are on English course (learning lang must be EN)!"
        );
        await delay(DELAY + 1000);
        continue;
      }
      const responseData = await response.json();
      const xpFarmed = responseData?.awardedXp || 0;
      userInfo = { ...userInfo, totalXp: userInfo.totalXp + xpFarmed };
      updateNotify(`You got ${xpFarmed} XP!!!`);
      updateUserInfo();
      await delay(DELAY);
    } catch (error) {
      updateNotify(
        `Error ${error.status}! Please record screen and report in telegram group!`
      );
      await delay(DELAY + 1000);
    }
  }
};

const farmSessionOnce = async (startTime, endTime) => {
  //tạo và lấy session
  const sessionPayload = {
    challengeTypes: [
      "assist",
      "characterIntro",
      "characterMatch",
      "characterPuzzle",
      "characterSelect",
      "characterTrace",
      "characterWrite",
      "completeReverseTranslation",
      "definition",
      "dialogue",
      "extendedMatch",
      "extendedListenMatch",
      "form",
      "freeResponse",
      "gapFill",
      "judge",
      "listen",
      "listenComplete",
      "listenMatch",
      "match",
      "name",
      "listenComprehension",
      "listenIsolation",
      "listenSpeak",
      "listenTap",
      "orderTapComplete",
      "partialListen",
      "partialReverseTranslate",
      "patternTapComplete",
      "radioBinary",
      "radioImageSelect",
      "radioListenMatch",
      "radioListenRecognize",
      "radioSelect",
      "readComprehension",
      "reverseAssist",
      "sameDifferent",
      "select",
      "selectPronunciation",
      "selectTranscription",
      "svgPuzzle",
      "syllableTap",
      "syllableListenTap",
      "speak",
      "tapCloze",
      "tapClozeTable",
      "tapComplete",
      "tapCompleteTable",
      "tapDescribe",
      "translate",
      "transliterate",
      "transliterationAssist",
      "typeCloze",
      "typeClozeTable",
      "typeComplete",
      "typeCompleteTable",
      "writeComprehension",
    ],
    fromLanguage: userInfo.fromLanguage,
    isFinalLevel: false,
    isV2: true,
    juicy: true,
    learningLanguage: userInfo.learningLanguage,
    smartTipsVersion: 2,
    type: "GLOBAL_PRACTICE",
  };
  const sessionRes = await sendRequestWithDefaultHeaders({
    url: "https://www.duolingo.com/2017-06-30/sessions",
    payload: sessionPayload,
    method: "POST",
  });
  const sessionData = await sessionRes.json();

  // lấy session và gán vào update
  const updateSessionPayload = {
    ...sessionData,
    heartsLeft: 0,
    startTime: startTime,
    enableBonusPoints: false,
    endTime: endTime,
    failed: false,
    maxInLessonStreak: 9,
    shouldLearnThings: true,
  };
  const updateRes = await sendRequestWithDefaultHeaders({
    url: `https://www.duolingo.com/2017-06-30/sessions/${sessionData.id}`,
    payload: updateSessionPayload,
    method: "PUT",
  });
  return await updateRes.json();
};

const repairStreak = async () => {
  blockStopBtn();
  try {
    if(!userInfo.streakData.currentStreak) {
      updateNotify("You have no streak! Abort!");
      resetStartStopBtn();
    }

    const startStreakDate = userInfo.streakData.currentStreak.startDate;
    const endStreakDate = userInfo.streakData.currentStreak.endDate;

    const startStreakTimestamp = toTimestamp(startStreakDate);
    const endStreakTimestamp = toTimestamp(endStreakDate);
    const expectedStreak =
      daysBetween(startStreakTimestamp, endStreakTimestamp) + 1; //+1 vì nó bị lệch gì đó

    //check xem có streak freeze không
    if (expectedStreak > userInfo.streak) {
      updateNotify("Your streak is frozen somewhere! Repairing...");
      await delay(2000);

      let currentTimestamp = Math.floor(Date.now() / 1000);
      for (let i = 0; i < expectedStreak; i++) {
        const createdSession = await farmSessionOnce(
          currentTimestamp,
          currentTimestamp + 60
        );
        currentTimestamp -= 86400;
        updateNotify(
          `Trying to repair streak ( ${i + 1}/${expectedStreak})...`
        );
        await delay(DELAY);
      }

      const userAfterRepair = await getUserInfo(sub);
      if (userAfterRepair.streakData.currentStreak.length > expectedStreak) {
        updateNotify(`Your streak has been repaired! No more frozen streak!`);
        userInfo = userAfterRepair;
        updateUserInfo();
      } else {
        updateNotify(
          `Streak repair failed or no frozen streak! Please check your account!`
        );
      }
    } else {
      updateNotify("You have no frozen streak! No need to repair!");
      resetStartStopBtn();
      return;
    }
  } finally {
    unblockStopBtn();
  }
};

const farmStreakLoop = async () => {
  const hasStreak = !!userInfo.streakData.currentStreak;
  const startStreakDate = hasStreak
    ? userInfo.streakData.currentStreak.startDate
    : new Date();

  const startFarmStreakTimestamp = toTimestamp(startStreakDate);
  let currentTimestamp = hasStreak
    ? startFarmStreakTimestamp - 86400 // Nếu đã có streak, farm từ ngày trước đó
    : startFarmStreakTimestamp;        // Nếu chưa có streak, farm luôn ngày hôm nay

  while (isRunning) {
    try {
      const sessionRes = await farmSessionOnce(currentTimestamp, currentTimestamp + 60);
      if (sessionRes) {
        currentTimestamp -= 86400;
        userInfo = { ...userInfo, streak: userInfo.streak + 1 };
        updateNotify(`You got +1 streak!`);
        updateUserInfo();
        await delay(DELAY);
      } else {
        updateNotify("Failed to farm streak session, I'm trying again...");
        await delay(2000);
        continue;
      }
    } catch (error) {
      updateNotify(`Error in farmStreak: ${error?.message || error}`);
      await delay(2000);
      continue;
    }
  }
}

const farmSelectedOption = async (option) => {
  const { type, value, amount, from, learn } = option;

  switch (type) {
    case "gem":
      farmGemLoop();
      break;
    case "xp":
      farmXpLoop(amount);
      break;
    case "streak":
      if (value == "repair") {
        repairStreak();
      } else if (value == "farm") {
        farmStreakLoop();
      }

      break;
  }
};

const initVariables = async () => {
  jwt = getJwtToken();
  if (!jwt) {
    disableInterface("Please login to Duolingo and reload!");
    return;
  }
  defaultHeaders = formatHeaders(jwt);
  const decodedJwt = decodeJwtToken(jwt);
  sub = decodedJwt.sub;
  userInfo = await getUserInfo(sub);
  populateOptions(); // cập nhật lại option khi đã có userInfo
};

//--------------------Main--------------------//

(async () => {
  initInterface();
  setInterfaceVisible(false);
  addEventListeners();
  await initVariables();
  updateUserInfo();
})();