Compare commits
15 Commits
2eb8d25a2d
...
0.0.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b206c734b0 | ||
|
|
9102a9b15a | ||
|
|
3ff9759e1e | ||
|
|
dd2571c09d | ||
|
|
30d0485da3 | ||
|
|
3232abab68 | ||
|
|
6add9f7cad | ||
|
|
e8ca15df06 | ||
|
|
1061864cae | ||
|
|
26449dd5c9 | ||
|
|
72e822950b | ||
|
|
d3396a4c9b | ||
|
|
1269b35dbd | ||
|
|
0163625507 | ||
|
|
b28c1e6795 |
@@ -6,39 +6,39 @@ on:
|
|||||||
- '*' # Trigger on any git tag
|
- '*' # Trigger on any git tag
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-image:
|
docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: gitea-runner-1
|
||||||
container:
|
|
||||||
image: docker:rc-dind
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install dedps
|
|
||||||
run: |
|
|
||||||
apk add --no-cache nodejs npm git
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Build container image
|
- name: Install Podman
|
||||||
env:
|
|
||||||
IMAGE_NAME: papryk/dj-spangebob
|
|
||||||
TAG: ${{ github.ref_name }}
|
|
||||||
run: |
|
run: |
|
||||||
docker build -t "$IMAGE_NAME:$TAG" .
|
apt-get update
|
||||||
|
apt-get install -y podman fuse-overlayfs slirp4netns
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
- name: Verify Podman
|
||||||
|
run: |
|
||||||
|
podman --version
|
||||||
|
podman info
|
||||||
|
|
||||||
|
- name: Build container image
|
||||||
|
run: |
|
||||||
|
podman build --storage-driver=vfs -t my-image:latest .
|
||||||
|
|
||||||
- name: Log in to Docker registry
|
- name: Log in to Docker registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
env:
|
env:
|
||||||
REGISTRY: https://gitea.papryk.com
|
registry: https://gitea.papryk.com
|
||||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
run: |
|
|
||||||
echo "$REGISTRY_PASSWORD" | docker "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
env:
|
uses: docker/build-push-action@v5
|
||||||
REGISTRY: https://gitea.papryk.com
|
with:
|
||||||
IMAGE_NAME: Papryk/dj-spangebob
|
context: .
|
||||||
TAG: ${{ github.ref_name }}
|
push: true
|
||||||
run: |
|
tags: "https://gitea.papryk.com/Papryk/dj-spangebob:${{ gitea.ref_name }}"
|
||||||
docker push "$REGISTRY/$IMAGE_NAME:$TAG"
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
FROM node:22-alpine
|
FROM node:20-alpine
|
||||||
|
|
||||||
RUN apk add --no-cache wget ffmpeg #yt-dlp
|
RUN apk add --no-cache ffmpeg yt-dlp
|
||||||
RUN wget -P /tmp https://github.com/yt-dlp/yt-dlp/releases/download/2026.03.17/yt-dlp_musllinux
|
|
||||||
RUN mv /tmp/yt-dlp_musllinux /usr/bin/yt-dlp
|
|
||||||
RUN chmod 777 /usr/bin/yt-dlp
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{"timestamp":1775508648856,"url":"https://www.youtube.com/watch?v=xTemcPZw8Eo","user":"kapitan.papryk"}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"timestamp":1772133865649,"url":"https://www.youtube.com/watch?v=TWiOB4lUZrQ","user":"kapitan.papryk"}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"interaction": {
|
|
||||||
"type": 2,
|
|
||||||
"id": "1466473802148872486",
|
|
||||||
"applicationId": "887789346277691402",
|
|
||||||
"channelId": "470712099753164811",
|
|
||||||
"guildId": "343827205878710273",
|
|
||||||
"user": "264739894641950720",
|
|
||||||
"member": "264739894641950720",
|
|
||||||
"version": 1,
|
|
||||||
"appPermissions": "2230881414020816",
|
|
||||||
"memberPermissions": "2230883322429171",
|
|
||||||
"locale": "pl",
|
|
||||||
"guildLocale": "en-US",
|
|
||||||
"entitlements": [],
|
|
||||||
"authorizingIntegrationOwners": {
|
|
||||||
"0": "343827205878710273"
|
|
||||||
},
|
|
||||||
"context": 0,
|
|
||||||
"attachmentSizeLimit": 10485760,
|
|
||||||
"commandId": "1452614990078021710",
|
|
||||||
"commandName": "play",
|
|
||||||
"commandType": 1,
|
|
||||||
"commandGuildId": null,
|
|
||||||
"deferred": true,
|
|
||||||
"replied": true,
|
|
||||||
"ephemeral": false,
|
|
||||||
"webhook": {
|
|
||||||
"id": "887789346277691402"
|
|
||||||
},
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"url": "https://soundcloud.com/musicbymoonlght/4th-of-july-rock-x-house-mix-by-moonlght-vol-2?si=93777764ee5c404db6f3fb3c6dc347d8&utm_source=clipboard&utm_medium=text&utm_campaign=social_sharing"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -32,10 +32,10 @@ async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await interaction.editReply(msg_downloading(url));
|
await interaction.editReply(msg_downloading(url));
|
||||||
forceRequestSong(url, { user: interaction.user.username }).then(() => {
|
await forceRequestSong(interaction, url)
|
||||||
// const msg = getPlayMsg(url)
|
|
||||||
interaction.editReply(msg_play());
|
const msg = getPlayMsg(url)
|
||||||
})
|
await interaction.editReply(msg_play());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await interaction.editReply('Coś poszło nie tak :/');
|
await interaction.editReply('Coś poszło nie tak :/');
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import queue from "./queue"
|
|||||||
import resume from "./resume"
|
import resume from "./resume"
|
||||||
import skip from "./skip"
|
import skip from "./skip"
|
||||||
import stop from "./stop"
|
import stop from "./stop"
|
||||||
import loop from "./loop"
|
|
||||||
|
|
||||||
export const commands: { [key: string]: Command } = {
|
export const commands: { [key: string]: Command } = {
|
||||||
ping,
|
ping,
|
||||||
@@ -19,6 +18,5 @@ export const commands: { [key: string]: Command } = {
|
|||||||
stop,
|
stop,
|
||||||
pause,
|
pause,
|
||||||
skip,
|
skip,
|
||||||
loop,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
|
|
||||||
import { toggleLoop } from '../playback';
|
|
||||||
import { formatFilePath } from '../util/downloader'
|
|
||||||
|
|
||||||
|
|
||||||
const name = "loop"
|
|
||||||
|
|
||||||
function register() {
|
|
||||||
return new SlashCommandBuilder()
|
|
||||||
.setName(name)
|
|
||||||
.setDescription('loop current song')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
|
|
||||||
try {
|
|
||||||
await interaction.deferReply()
|
|
||||||
const audio = toggleLoop()
|
|
||||||
if (audio) {
|
|
||||||
await interaction.editReply(`looped ${formatFilePath(audio.url)}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await interaction.editReply(`Loop turned off`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
await interaction.reply('Coś poszło nie tak :/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name,
|
|
||||||
register,
|
|
||||||
execute
|
|
||||||
}
|
|
||||||
@@ -36,9 +36,10 @@ async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await interaction.editReply(msg_downloading(url));
|
await interaction.editReply(msg_downloading(url));
|
||||||
requestSong(url, { user: interaction.user.username }).then(() => {
|
await requestSong(interaction, url)
|
||||||
interaction.editReply(msg_play());
|
|
||||||
})
|
const msg = getPlayMsg(url)
|
||||||
|
await interaction.editReply(msg_play());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await interaction.editReply('Coś poszło nie tak :/');
|
await interaction.editReply('Coś poszło nie tak :/');
|
||||||
|
|||||||
10
src/global.d.ts
vendored
10
src/global.d.ts
vendored
@@ -10,13 +10,7 @@ type AudioFile = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Queue = {
|
type Queue = {
|
||||||
songList: AudioFile[]
|
songList: AudioFile[],
|
||||||
current: AudioFile | null
|
current: AudioFile | null,
|
||||||
loop: AudioFile | null
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryObject = {
|
|
||||||
user?: number | string,
|
|
||||||
url?: string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { updatePlayer } from './playback';
|
|||||||
import SpotifyWebApi from 'spotify-web-api-node';
|
import SpotifyWebApi from 'spotify-web-api-node';
|
||||||
|
|
||||||
export const DATA_DIR = "./data";
|
export const DATA_DIR = "./data";
|
||||||
export const HISTORY_DIR_PATH = process.env.HISTORY_DIR_PATH ?? "./history";
|
|
||||||
|
|
||||||
// AUDIO
|
// AUDIO
|
||||||
export const player = createAudioPlayer();
|
export const player = createAudioPlayer();
|
||||||
|
|||||||
@@ -3,39 +3,29 @@ import { player } from "./main";
|
|||||||
import { CacheType, ChatInputCommandInteraction } from "discord.js";
|
import { CacheType, ChatInputCommandInteraction } from "discord.js";
|
||||||
import { getAudioFile } from "./util/downloader";
|
import { getAudioFile } from "./util/downloader";
|
||||||
import { playSong } from "./util/helpers";
|
import { playSong } from "./util/helpers";
|
||||||
import { add_to_history } from "./util/history";
|
|
||||||
|
|
||||||
const queue: Queue = {
|
const queue: Queue = {
|
||||||
songList: [],
|
songList: [],
|
||||||
current: null,
|
current: null
|
||||||
loop: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleLoop(): AudioFile | null {
|
|
||||||
if (queue.loop) queue.loop = null
|
|
||||||
else queue.loop = queue.current
|
|
||||||
return queue.loop
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestSong(
|
export async function requestSong(
|
||||||
url: string,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
data: Partial<HistoryObject>
|
url: string) {
|
||||||
) {
|
|
||||||
const path = await getAudioFile(url)
|
const path = await getAudioFile(url)
|
||||||
queue.songList.push({ path, url })
|
queue.songList.push({ path, url })
|
||||||
add_to_history({ url, user: data.user })
|
updatePlayer()
|
||||||
await updatePlayer()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function forceRequestSong(
|
export async function forceRequestSong(
|
||||||
url: string,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
data: Partial<HistoryObject>
|
url: string) {
|
||||||
) {
|
|
||||||
|
|
||||||
const path = await getAudioFile(url)
|
const path = await getAudioFile(url)
|
||||||
const audio = { path, url }
|
const audio = { path, url }
|
||||||
queue.songList.push(audio)
|
queue.songList.push(audio)
|
||||||
add_to_history({ url, user: data.user })
|
|
||||||
queue.current = audio
|
queue.current = audio
|
||||||
playSong(player, path);
|
playSong(player, path);
|
||||||
}
|
}
|
||||||
@@ -43,7 +33,7 @@ export async function forceRequestSong(
|
|||||||
|
|
||||||
export async function updatePlayer() {
|
export async function updatePlayer() {
|
||||||
if (player.state.status === AudioPlayerStatus.Idle) {
|
if (player.state.status === AudioPlayerStatus.Idle) {
|
||||||
const nextSong = queue.loop ?? queue.songList.shift()
|
const nextSong = queue.songList.shift()
|
||||||
if (!nextSong) {
|
if (!nextSong) {
|
||||||
queue.current = null
|
queue.current = null
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -18,32 +18,27 @@ export async function getAudioFile(url: string): Promise<string> {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
function isYTLink(url: string) {
|
|
||||||
return url.startsWith("https://youtube") ||
|
|
||||||
url.startsWith("https://youtu.be");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function extractId(url: string): Promise<string> {
|
export async function extractId(url: string): Promise<string> {
|
||||||
// if (!isYTLink(url)) {
|
if (url.startsWith("https://soundcloud")) { // soundcloud
|
||||||
const ytDlpBin = process.env.YT_DLP_BIN_PATH! ?? "yt-dlp";
|
const ytDlpBin = process.env.YT_DLP_BIN_PATH! ?? "yt-dlp";
|
||||||
return await new Promise<string>((resolve, reject) => {
|
return await new Promise<string>((resolve, reject) => {
|
||||||
const p = spawn(ytDlpBin, [
|
const p = spawn(ytDlpBin, [
|
||||||
"--print", "%(id)s",
|
"--print", "%(id)s",
|
||||||
url,
|
url,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let out = ""
|
let out = ""
|
||||||
p.stderr.on("data", d => process.stderr.write(d));
|
p.stderr.on("data", d => process.stderr.write(d));
|
||||||
p.stdout.on("data", d => out += d.toString());
|
p.stdout.on("data", d => out += d.toString());
|
||||||
p.on("close", code => code === 0 ? resolve(out.trim()) : reject(new Error("yt-dlp extract id failed")));
|
p.on("close", code => code === 0 ? resolve(out.trim()) : reject(new Error("yt-dlp extract id failed")));
|
||||||
});
|
});
|
||||||
|
|
||||||
// } else {
|
} else {
|
||||||
// const idMatch = /[?&]v=([a-zA-Z0-9_-]{11})/.exec(url);
|
const idMatch = /[?&]v=([a-zA-Z0-9_-]{11})/.exec(url);
|
||||||
// if (!idMatch) throw new Error("Cannot extract video ID");
|
if (!idMatch) throw new Error("Cannot extract video ID");
|
||||||
// const id = idMatch[1];
|
const id = idMatch[1];
|
||||||
// return id
|
return id
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadAudio(url: string): Promise<void> {
|
export async function downloadAudio(url: string): Promise<void> {
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ import { player } from '../main.js';
|
|||||||
export async function connectToChannelByInteraction(interaction: ChatInputCommandInteraction<CacheType>): Promise<void> {
|
export async function connectToChannelByInteraction(interaction: ChatInputCommandInteraction<CacheType>): Promise<void> {
|
||||||
const member = await interaction.guild?.members.fetch(interaction.user.id);
|
const member = await interaction.guild?.members.fetch(interaction.user.id);
|
||||||
if (!member) throw new Error("ale chuj")
|
if (!member) throw new Error("ale chuj")
|
||||||
const voiceChannel = member.voice.channel;
|
const voiceChannel = member.voice.channel;
|
||||||
if (!voiceChannel) throw new Error("ale chuj 2")
|
if (!voiceChannel) throw new Error("ale chuj 2")
|
||||||
const connection = await connectToChannel(voiceChannel);
|
const connection = await connectToChannel(voiceChannel);
|
||||||
connection.subscribe(player);
|
connection.subscribe(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function connectToChannel(channel: VoiceBasedChannel) {
|
export async function connectToChannel(channel: VoiceBasedChannel) {
|
||||||
/**
|
/**
|
||||||
* Here, we try to establish a connection to a voice channel. If we're already connected
|
* Here, we try to establish a connection to a voice channel. If we're already connected
|
||||||
* to this voice channel, \@discordjs/voice will just return the existing connection for us!
|
* to this voice channel, \@discordjs/voice will just return the existing connection for us!
|
||||||
*/
|
*/
|
||||||
const connection = joinVoiceChannel({
|
const connection = joinVoiceChannel({
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
guildId: channel.guild.id,
|
guildId: channel.guild.id,
|
||||||
@@ -32,28 +32,28 @@ export async function connectToChannel(channel: VoiceBasedChannel) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we're dealing with a connection that isn't yet Ready, we can set a reasonable
|
* If we're dealing with a connection that isn't yet Ready, we can set a reasonable
|
||||||
* time limit before giving up. In this example, we give the voice connection 30 seconds
|
* time limit before giving up. In this example, we give the voice connection 30 seconds
|
||||||
* to enter the ready state before giving up.
|
* to enter the ready state before giving up.
|
||||||
*/
|
*/
|
||||||
try {
|
try {
|
||||||
/**
|
/**
|
||||||
* Allow ourselves 30 seconds to join the voice channel. If we do not join within then,
|
* Allow ourselves 30 seconds to join the voice channel. If we do not join within then,
|
||||||
* an error is thrown.
|
* an error is thrown.
|
||||||
*/
|
*/
|
||||||
await entersState(connection, VoiceConnectionStatus.Ready, 30_000);
|
await entersState(connection, VoiceConnectionStatus.Ready, 30_000);
|
||||||
/**
|
/**
|
||||||
* At this point, the voice connection is ready within 30 seconds! This means we can
|
* At this point, the voice connection is ready within 30 seconds! This means we can
|
||||||
* start playing audio in the voice channel. We return the connection so it can be
|
* start playing audio in the voice channel. We return the connection so it can be
|
||||||
* used by the caller.
|
* used by the caller.
|
||||||
*/
|
*/
|
||||||
return connection;
|
return connection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
/**
|
/**
|
||||||
* At this point, the voice connection has not entered the Ready state. We should make
|
* At this point, the voice connection has not entered the Ready state. We should make
|
||||||
* sure to destroy it, and propagate the error by throwing it, so that the calling function
|
* sure to destroy it, and propagate the error by throwing it, so that the calling function
|
||||||
* is aware that we failed to connect to the channel.
|
* is aware that we failed to connect to the channel.
|
||||||
*/
|
*/
|
||||||
connection.destroy();
|
connection.destroy();
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
@@ -61,31 +61,27 @@ export async function connectToChannel(channel: VoiceBasedChannel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function playSong(player: AudioPlayer, songUrl: string) {
|
export async function playSong(player: AudioPlayer, songUrl: string) {
|
||||||
try {
|
/**
|
||||||
/**
|
* Here we are creating an audio resource using a sample song freely available online
|
||||||
* Here we are creating an audio resource using a sample song freely available online
|
* (see https://www.soundhelix.com/audio-examples)
|
||||||
* (see https://www.soundhelix.com/audio-examples)
|
*
|
||||||
*
|
* We specify an arbitrary inputType. This means that we aren't too sure what the format of
|
||||||
* We specify an arbitrary inputType. This means that we aren't too sure what the format of
|
* the input is, and that we'd like to have this converted into a format we can use. If we
|
||||||
* the input is, and that we'd like to have this converted into a format we can use. If we
|
* were using an Ogg or WebM source, then we could change this value. However, for now we
|
||||||
* were using an Ogg or WebM source, then we could change this value. However, for now we
|
* will leave this as arbitrary.
|
||||||
* will leave this as arbitrary.
|
*/
|
||||||
*/
|
const resource = createAudioResource(songUrl, { inputType: StreamType.Arbitrary });
|
||||||
const resource = createAudioResource(songUrl, { inputType: StreamType.Arbitrary });
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We will now play this to the audio player. By default, the audio player will not play until
|
* We will now play this to the audio player. By default, the audio player will not play until
|
||||||
* at least one voice connection is subscribed to it, so it is fine to attach our resource to the
|
* at least one voice connection is subscribed to it, so it is fine to attach our resource to the
|
||||||
* audio player this early.
|
* audio player this early.
|
||||||
*/
|
*/
|
||||||
player.play(resource);
|
player.play(resource);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Here we are using a helper function. It will resolve if the player enters the Playing
|
* Here we are using a helper function. It will resolve if the player enters the Playing
|
||||||
* state within 5 seconds, otherwise it will reject with an error.
|
* state within 5 seconds, otherwise it will reject with an error.
|
||||||
*/
|
*/
|
||||||
return entersState(player, AudioPlayerStatus.Playing, 5_000);
|
return entersState(player, AudioPlayerStatus.Playing, 5_000);
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import path from "node:path";
|
|
||||||
import fs from "node:fs";
|
|
||||||
import { HISTORY_DIR_PATH } from "../main";
|
|
||||||
|
|
||||||
export async function add_to_history(data: HistoryObject) {
|
|
||||||
try {
|
|
||||||
const today = new Date();
|
|
||||||
|
|
||||||
const day = String(today.getDate()).padStart(2, '0');
|
|
||||||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
|
||||||
const year = today.getFullYear();
|
|
||||||
|
|
||||||
const file_name = `${day}-${month}-${year}.json`;
|
|
||||||
const file_path = path.join(HISTORY_DIR_PATH, file_name)
|
|
||||||
|
|
||||||
if (!fs.existsSync(HISTORY_DIR_PATH)) {
|
|
||||||
fs.mkdirSync(HISTORY_DIR_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
let history = {
|
|
||||||
timestamp: today.getTime(),
|
|
||||||
url: data.url,
|
|
||||||
user: data.user,
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(file_path, JSON.stringify(history));
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user