feat: init
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
TOKEN=your_discord_token_here
|
||||||
|
CHANNEL_ID=voice_channel_id_here
|
||||||
|
MESSAGE_CRON=""
|
||||||
|
MESSAGE="pong"
|
||||||
|
RAMADHAN_CRON=0 5 * * *
|
||||||
|
START_DATE="2026-02-18"
|
||||||
|
SELF_DEAF=true
|
||||||
|
SELF_MUTE=true
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
node_modules/
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,89 @@
|
|||||||
|
import "dotenv/config";
|
||||||
|
import cron from "node-cron";
|
||||||
|
import { Client } from 'discord.js-selfbot-v13';
|
||||||
|
import { joinVoiceChannel } from "@discordjs/voice";
|
||||||
|
import generateMeme from "./meme.js";
|
||||||
|
|
||||||
|
const TOKEN = process.env.TOKEN;
|
||||||
|
const channelId = process.env.CHANNEL_ID;
|
||||||
|
|
||||||
|
// Ramadan 2026 start date (adjust as needed)
|
||||||
|
const start = new Date(process.env.START_DATE);
|
||||||
|
|
||||||
|
const days = {
|
||||||
|
1: "Pertama",
|
||||||
|
2: "Kedua",
|
||||||
|
3: "Ketiga",
|
||||||
|
4: "Keempat",
|
||||||
|
5: "Kelima",
|
||||||
|
6: "Keenam",
|
||||||
|
7: "Ketujuh",
|
||||||
|
8: "Kedelapan",
|
||||||
|
9: "Kesembilan",
|
||||||
|
10: "Kesepuluh",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const client = new Client();
|
||||||
|
|
||||||
|
const joinVoice = async (client, channelId, config) => {
|
||||||
|
try {
|
||||||
|
const channel = await client.channels.fetch(channelId).catch(() => null);
|
||||||
|
const connection = joinVoiceChannel({
|
||||||
|
channelId: channel.id,
|
||||||
|
guildId: channel.guild.id,
|
||||||
|
adapterCreator: channel.guild.voiceAdapterCreator,
|
||||||
|
selfDeaf: config.selfDeaf || false,
|
||||||
|
selfMute: config.selfMute || false, // Use selfMute from config
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('error', error => {
|
||||||
|
console.error(`[${client.user.username}] Error voice connection: ${error}`);
|
||||||
|
setTimeout(() => joinVoice(client, channelId, config), 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('disconnect', () => {
|
||||||
|
console.log(`[${client.user.username}] Voice connection terputus. Mencoba terhubung kembali...`);
|
||||||
|
setTimeout(() => joinVoice(client, channelId, config), 10000);
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
console.error(`[${client.user.username}] Failed to join voice channel: ${e}`);
|
||||||
|
setTimeout(() => joinVoice(client, channelId, config), 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.on('ready', async () => {
|
||||||
|
joinVoice(client, channelId, {
|
||||||
|
selfDeaf: process.env.SELF_DEAF === "true",
|
||||||
|
selfMute: process.env.SELF_MUTE === "true",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cron.schedule(process.env.CRON, async () => {
|
||||||
|
try {
|
||||||
|
console.log("Uploading...")
|
||||||
|
const now = new Date();
|
||||||
|
const diff = (Math.floor((now.getTime() - start.getTime())/(1000*60*60*24))) + 1;
|
||||||
|
const image = await generateMeme("./assets/images/mr_crab.jpg", "SEMANGAT PUASA HARI " + (days[diff]?.toUpperCase() || "KE-" + diff), "YAA.... HARI " + (days[diff]?.toUpperCase() || "KE-" + diff));
|
||||||
|
client.channels.fetch(process.env.CHANNEL_ID).then(channel => {
|
||||||
|
channel.send({
|
||||||
|
files: [{
|
||||||
|
attachment: image,
|
||||||
|
name: new Date().toLocaleDateString() + '.jpg'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}).catch(console.error);
|
||||||
|
}catch(e){
|
||||||
|
console.log(e)
|
||||||
|
}finally{
|
||||||
|
console.log("Done");
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
scheduled: true,
|
||||||
|
timezone: "Asia/Jakarta",
|
||||||
|
});
|
||||||
|
|
||||||
|
client.login(TOKEN).then(() => {
|
||||||
|
console.log('Logged in as ' + client.user.tag);
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import Canvas, { registerFont} from "canvas";
|
||||||
|
|
||||||
|
|
||||||
|
const wrapText = (ctx, text, maxWidth) => {
|
||||||
|
registerFont(`./assets/fonts/Impact.ttf`, {
|
||||||
|
family: "Impact",
|
||||||
|
});
|
||||||
|
if(ctx.measureText(text).width < maxWidth) return [text];
|
||||||
|
if(ctx.measureText("W").width > maxWidth) return null;
|
||||||
|
const words = text.split(" ");
|
||||||
|
const lines = [];
|
||||||
|
let line = "";
|
||||||
|
while(words.length > 0) {
|
||||||
|
let split = false;
|
||||||
|
while(ctx.measureText(words[0]).width >= maxWidth){
|
||||||
|
const tmp = words[0];
|
||||||
|
words[0] = tmp.slice(0, -1);
|
||||||
|
if(split){
|
||||||
|
words[1] = `${tmp.slice(-1)}${words[1]}`
|
||||||
|
}else{
|
||||||
|
split = true;
|
||||||
|
words.splice(1, 0, tmp.slice(-1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctx.measureText(`${line}${words[0]}`).width < maxWidth) {
|
||||||
|
line += `${words.shift()} `;
|
||||||
|
} else {
|
||||||
|
lines.push(line.trim());
|
||||||
|
line = "";
|
||||||
|
}
|
||||||
|
if (words.length === 0) lines.push(line.trim());
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const generateMeme = async (imageSrc, topText, bottomText) => {
|
||||||
|
const base = await Canvas.loadImage(imageSrc);
|
||||||
|
const canvas = Canvas.createCanvas(base.width, base.height);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
ctx.drawImage(base, 0, 0);
|
||||||
|
const fontSize = Math.round(base.height / 10);
|
||||||
|
ctx.font = `bold ${fontSize}px Impact`;
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "top";
|
||||||
|
|
||||||
|
const topLines = await wrapText(ctx, topText, base.width - 10)
|
||||||
|
if (topLines) {
|
||||||
|
for (let i = 0; i < topLines.length; i++) {
|
||||||
|
const textHeight = i * fontSize + i * 10;
|
||||||
|
ctx.strokeStyle = "black";
|
||||||
|
ctx.lineWidth = 5;
|
||||||
|
ctx.strokeText(topLines[i], base.width / 2, textHeight);
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
ctx.fillText(topLines[i], base.width / 2, textHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bottomLines = await wrapText(ctx, bottomText, base.width - 10);
|
||||||
|
if (bottomLines) {
|
||||||
|
ctx.textBaseline = "bottom";
|
||||||
|
const initial =
|
||||||
|
base.height -
|
||||||
|
(bottomLines.length - 1) * fontSize -
|
||||||
|
(bottomLines.length - 1) * 10;
|
||||||
|
for (let i = 0; i < bottomLines.length; i++) {
|
||||||
|
const textHeight = initial + i * fontSize + i * 10;
|
||||||
|
ctx.strokeStyle = "black";
|
||||||
|
ctx.lineWidth = 5;
|
||||||
|
ctx.strokeText(bottomLines[i], base.width / 2, textHeight);
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
ctx.fillText(bottomLines[i], base.width / 2, textHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateMeme;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "RTRT",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"packageManager": "[email protected]",
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/voice": "^0.19.0",
|
||||||
|
"@snazzah/davey": "^0.1.9",
|
||||||
|
"canvas": "^3.2.1",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"discord.js-selfbot-v13": "^3.7.1",
|
||||||
|
"dotenv": "^17.3.1",
|
||||||
|
"node-cron": "^4.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+1116
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- canvas
|
||||||
Reference in New Issue
Block a user