Discord.js Bot 本地主机和托管环境之间的时区差异

问题描述 投票:0回答:1

我有一个 Discord.js 机器人,它使用外部 API 检索祈祷时间并为这些时间设置提醒。该机器人在我的本地主机上运行良好,其中时区设置为本地主机上的埃及开罗。但是,当我将机器人部署到 Repl.it 或 Render 等托管环境时,我注意到机器人输出中显示的时间存在差异。 其中命令中设置的时区比当前时区多 3 小时。

这是我的代码:

const discord = require("discord.js");
const {
  SlashCommandBuilder,
  StringSelectMenuBuilder,
  ActionRowBuilder,
  ButtonBuilder,
} = require("discord.js");
const fetch = require("node-fetch");
const tc = require("../../functions/TimeConvert");
const cfl = require("../../functions/CapitalizedChar");
const schema = require("../../schema/TimeOut-Schema");
const moment = require("moment");
const momentTz = require("moment-timezone");
let dinMS;

module.exports = {
  clientpermissions: [
    discord.PermissionsBitField.Flags.EmbedLinks,
    discord.PermissionsBitField.Flags.ReadMessageHistory,
  ],
  data: new SlashCommandBuilder()
    .setName("prays")
    .setDescription("Replies with prays times!")
    .addStringOption((option) =>
      option
        .setName("country")
        .setDescription("Enter country name.")
        .setRequired(true)
    )
    .addStringOption((option) =>
      option
        .setName("city")
        .setDescription("Enter city name.")
        .setRequired(true)
    ),
  async execute(client, interaction) {
    try {
      async function LoadButtons(MAX_BTNS, arr) {
        const MAX_BUTTONS_PER_ROW = 5;
        const MAX_BUTTONS_PER_MESSAGE = MAX_BTNS;

        const buttonRows = [];
        let currentRow = new ActionRowBuilder();

        for (let i = 0; i < arr.length; i++) {
          const [label, value] = arr[i];
          const button = new ButtonBuilder()
            .setLabel(label)
            .setCustomId(`${label} ${value}`)
            .setStyle(1);

          if (
            currentRow.components.length >= MAX_BUTTONS_PER_ROW ||
            buttonRows.length * MAX_BUTTONS_PER_ROW +
              currentRow.components.length >=
              MAX_BUTTONS_PER_MESSAGE
          ) {
            buttonRows.push(currentRow);
            currentRow = new ActionRowBuilder();
          }

          currentRow.addComponents(button);
        }

        if (currentRow.components.length > 0) {
          buttonRows.push(currentRow);
        }

        return buttonRows;
      }

      async function setReminder(interaction, timezone, prayerTime) {
        try {
          let data = await schema.findOne({ userId: interaction.user.id });

          if (!data) {
            data = await schema.create({ userId: interaction.user.id });
          }

          if (data.Reminder.current) {
            await interaction.channel
              .send(
                `${interaction.user}, looks like you already have an \`active reminder\`, do you want to add this one instead? \`(y/n)\``
              )
              .catch(() => null);

            const filter = (_message) =>
              interaction.user.id === _message.author.id &&
              ["y", "n", "yes", "no"].includes(_message.content.toLowerCase());

            const proceed = await interaction.channel
              .awaitMessages({ filter, max: 1, time: 40000, errors: ["time"] })
              .then((collected) =>
                ["y", "yes"].includes(collected.first().content.toLowerCase())
                  ? true
                  : false
              )
              .catch(() => false);

            if (!proceed) {
              return interaction.channel
                .send({
                  content: `\\❌ | **${interaction.user.tag}**, Cancelled the \`reminder\`!`,
                  ephemeral: true,
                })
                .catch(() => null);
            }
          }

          const [hours, minutes] = prayerTime.split(":");

          let currentDatetime;
          await CurrentTime(timezone).then(async (time) => {
            currentDatetime = time.date.toLocaleString("en-US", {
              timeZone: time.timezone,
              hour12: false,
            });
          });

          const [date, time] = currentDatetime.split(", ");
          const [month, day, year] = date.split("/");
          const [hour, minute, second] = time.split(":");

          const prayerDatetime = new Date(
            +year,
            month - 1,
            day,
            hours,
            minutes
          ).getTime();

          const currentTime = new Date(
            +year,
            month - 1,
            day,
            hour,
            minute,
            second
          ).getTime();

          const TimeDiff = (prayerDatetime - currentTime);

          if (currentTime >= prayerDatetime || TimeDiff <= 0) {
            return interaction
              .reply({
                content: `\\❌ ${interaction.user}, This pray time has already passed!`,
                ephemeral: true,
              })
              .catch(() => null);
          }

          const Reason = interaction.customId.split(" ")[0];

          data.Reminder.current = true;
          data.Reminder.time = prayerDatetime;
          data.Reminder.reason = `${Reason} will start soon`;
          data.Reminder.timezone = timezone;

          await data.save();

          /*
          const duration = moment.duration(TimeDiff, "milliseconds");
          const formattedDuration = duration.format(
            "H [hours,] m [minutes, and] s [seconds,]"
          );
          */

          const dnEmbed = new discord.EmbedBuilder()
            .setAuthor({
              name: "| Reminder Set!",
              iconURL: interaction.user.displayAvatarURL(),
            })
            .setDescription(
              `Successfully set \`${interaction.user.tag}'s\` reminder!`
            )
            .addFields(
              {
                name: "❯ Remind You In:",
                value: `<t:${prayerDatetime / 1000}:R>`,
              },
              {
                name: "❯ Remind Reason:",
                value: `${Reason} will start soon`,
              }
            )
            .setColor("Green")
            .setTimestamp()
            .setFooter({
              text: "Successfully set the reminder!",
              iconURL: client.user.displayAvatarURL(),
            });

          interaction.channel
            .send({ embeds: [dnEmbed], ephemeral: true })
            .catch(() => null);
        } catch (err) {
          console.error(err);
        }
      }

      async function CurrentTime(timezone) {
        const now = momentTz().tz(timezone);
        const isDST = now.isDST();

        let options = {
          timeZone: timezone,
          year: "numeric",
          month: "numeric",
          day: "numeric",
          hour: "numeric",
          minute: "numeric",
          second: "numeric",
          hour12: false,
        };

        let formatter = new Intl.DateTimeFormat([], options);
        const d = new Date(formatter.format(now.toDate()).split(",").join(" "));

        if (isDST) {
          d.setHours(d.getHours() + 1);
        }

        return {
          date: d,
          timezone: timezone,
          MiliSeconds: Math.floor(d.getTime() / 1000),
        };
      }

      const country = interaction.options.getString("country");
      const city = interaction.options.getString("city");

      const url = `https://api.aladhan.com/v1/timingsByCity?city=${city}&country=${country}`;
      const options = {
        method: "GET",
      };

      fetch(url, options)
        .then((res) => res.json())
        .then(async (json) => {
          if (json.code != 200) {
            return interaction.editReply({
              content:
                "<:error:888264104081522698> Please enter valid country and city in the options!",
            });
          }

          let rows = [];
          const json_data = json.data.timings;
          let result = Object.entries(json_data)
            .filter(([key]) => key !== "Imsak" && key !== "Sunset")
            .map(([key, value]) => [key, value]);

          rows = await LoadButtons(25, result);

          const timezone = json.data.meta.timezone;

          try {
            if (timezone) {
              dinMS = await CurrentTime(timezone).then(
                (time) => time.MiliSeconds
              );
            } else {
              return await interaction.editReply({
                content:
                  "<:error:888264104081522698> I can't identify this timezone, please write the right `City, Country`!",
              });
            }
          } catch (e) {
            console.error(e);
            return await interaction.editReply({
              content:
                "<:error:888264104081522698> I can't identify this timezone, please write the right `City, Country`!",
            });
          }

          let pTimeInMS;
          let str;
          let nxtStr = null;
          let num = -1;
          let marked = false;

          for (const pTime of result) {
            num++;
            str = `${json.data.date.readable.split(" ").join("/")} ${pTime[1]}`;
            const [dateComponents, timeComponents] = str.split(" ");
            const [day, month, year] = dateComponents.split("/");
            const [hours, minutes] = timeComponents.split(":");

            const formatter = new Intl.DateTimeFormat([], { month: "numeric" });
            const monthNumber = formatter.format(
              new Date(`${year}-${month}-${day}`)
            );
            pTimeInMS = Math.floor(
              new Date(
                +year,
                monthNumber - 1,
                +day,
                +hours,
                +minutes,
                0
              ).getTime() / 1000
            );

            if (dinMS < pTimeInMS) {
              if (!marked) {
                const TimeDiff = Math.floor(pTimeInMS - dinMS) * 1000;
                nxtStr = `${pTime[0]} - *${moment
                  .duration(TimeDiff, "milliseconds")
                  .humanize()}*!`;
                result[num][0] = pTime[0] + "(`Next`)";
                marked = true;
              }
            }
          }

          const ActionRow = new ActionRowBuilder().addComponents(
            new StringSelectMenuBuilder()
              .setCustomId("kwthbek4m221pyddhwp4")
              .setPlaceholder("Nothing selected!")
              .addOptions([
                {
                  label: "Remind Before",
                  description: `Choose this option to set a time to remind to before the pray (like 5 minutes before)`,
                  value: "remind_before1",
                },
                {
                  label: "Set timezone",
                  description: `Choose this option to set the default timezone for the cmd.`,
                  value: "timezone1",
                },
                {
                  label: "Auto Reminder",
                  description: `Choose this option to get notified before every pray.`,
                  value: "auto_reminder1",
                },
              ])
          );

          rows.push(ActionRow);

          const embed = new discord.EmbedBuilder()
            .setAuthor({
              name: interaction.user.username,
              iconURL: interaction.user.displayAvatarURL({ dynamic: true }),
            })
            .setDescription(
              [
                `<:Tag:836168214525509653> Praying times for country \`${cfl.capitalizeFirstLetter(
                  country
                )}\` in city \`${cfl.capitalizeFirstLetter(city)}\`!`,
                `You can set a \`reminder\` for the next pray from the **buttons** below, use the **action Row** to set the \`default timezone\` and if you want the \`auto reminder\`.`,
              ].join("\n")
            )
            .addFields(
              {
                name: "<:star:888264104026992670> Date",
                value: `<t:${dinMS}>`,
                inline: false,
              },
              {
                name: "<:Timer:853494926850654249> Next Pray in:",
                value: nxtStr || "Tomorrow!",
                inline: false,
              },
              { name: " ‍ ", value: ` ‍ `, inline: false }
            )
            .addFields(
              result.flatMap((i) => [
                {
                  name: i[0],
                  value: `\`\`\`${tc.tConvert(i[1])}\`\`\``,
                  inline: true,
                },
              ])
            )
            .setFooter({
              text: [
                `Based on: ${
                  json.data.meta.method.name
                    ? json.data.meta.method.name
                    : "Unknown"
                }\n`,
                "Times may vary!",
              ].join(" "),
              iconURL: client.user.displayAvatarURL({ dynamic: true }),
            })
            .setTimestamp();
          await interaction
            .editReply({ embeds: [embed], components: rows })
            .then((msg) => {
              const filter = (int) => int.user.id == interaction.user.id;

              const collector = msg.createMessageComponentCollector({
                filter: filter,
                time: 180000,
                fetch: true,
              });

              collector.on("collect", async (interaction) => {
                if (interaction.isButton()) {
                  interaction.deferUpdate().then(async () => {
                    const PrayingTime = result.find(
                      (time) => time[1] === interaction.customId.split(" ")[1]
                    )[1];
                    await setReminder(interaction, timezone, PrayingTime);
                  });
                } else {
                  return;
                }
              });

              collector.on("end", async () => {
                return await msg.edit({ embeds: [embed], components: [] });
              });
            });
        })
        .catch((err) => {
          console.error(err);
          interaction.channel.send({
            content: `<:error:888264104081522698> ${interaction.user} Something went wrong, please try again later!`,
          });
        });
    } catch (error) {
      console.error("Error in execute function:", error);
      interaction.reply({
        content: "An error occurred. Please try again later.",
        ephemeral: true,
      });
    }
  },
};

附加信息:

我正在使用 Moment.js 版本 2.30.1 和 Moment Timezone 版本 0.5.40。 这是我的 package.json 文件供参考:

{
  "name": "wolfy-bot",
  "version": "2.3.9",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "build": "node build.js",
    "test": "node test.js"
  },
  "author": "Yousef osama(WOLF)",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@discordjs/opus": "^0.9.0",
    "@discordjs/voice": "^0.15.0",
    "axios": "^0.27.2",
    "canvacord": "^6.0.1",
    "canvas": "^2.11.2",
    "discord-player": "^5.4.0",
    "discord.js": "^14.8.0",
    "dotenv": "^16.0.1",
    "express": "^4.17.1",
    "ffmpeg-static": "^4.2.7",
    "fs-extra": "^10.0.0",
    "got": "^11.8.5",
    "he": "^1.2.0",
    "html2markdown": "^1.1.0",
    "humanize-duration": "^3.28.0",
    "jsdom": "^16.4.0",
    "math-expression-evaluator": "^1.4.0",
    "minecraft-player": "^1.0.1",
    "moment": "^2.30.1",
    "moment-timezone": "^0.5.40",
    "mongoose": "^5.13.20",
    "ms": "^2.1.3",
    "node": "^16.19.0",
    "node-fetch": "^2.6.9",
    "openai": "^3.1.0",
    "opusscript": "^0.0.8",
    "parse-ms": "^2.1.0",
    "play-dl": "^1.9.6",
    "snekfetch": "^4.0.4",
    "sourcebin": "^5.0.0",
    "sourcebin_js": "^0.0.3-ignore",
    "txtgen": "3.0.1",
    "uuid": "^8.3.2",
    "weather-js": "^2.0.0",
    "youtube-sr": "^4.3.4",
    "ytdl-core": "^4.8.3"
  }
}

任何见解或建议将不胜感激。谢谢!

我已经验证我正在使用 Moment.js 和 Moment Timezone 库在代码中正确处理时区。 日期、nextPray 持续时间在服务器上不正确,在本地主机上正确,但下一个祈祷旁边的下一个祈祷字符串在本地主机和服务器主机上都可以正常工作。

javascript discord.js timezone momentjs moment-timezone
1个回答
0
投票
代码包含一些

Date

 构造函数调用,通过 
new Date()
 进行调用,然后是 
getTime()

这意味着结果将被解释为从 1970 年 1 月 1 日到该日期的毫秒数,但是

该日期将基于环境区域设置

假设你有这个:

console.log(new Date(2013, 1, 1, 1, 1).getTime())
在具有 

Europe/London

 语言环境的环境中,此返回 
1359680460000
。在 
America/Montreal
 语言环境中,返回 
1359698460000
。相差5个小时。

如果您提取的日期采用 UTC 格式,则应使用

Date.UTC()

 构造函数。

如果它们位于另一个时区,您可以使用

moment.tz()

 来正确摄取它们。

© www.soinside.com 2019 - 2024. All rights reserved.