Skip to content

Commit

Permalink
feat(bot): add instagram support
Browse files Browse the repository at this point in the history
  • Loading branch information
lastarc committed Apr 27, 2024
1 parent 8f8092d commit 8eeb52d
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 13 deletions.
111 changes: 111 additions & 0 deletions bot/commands/media/instagram.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const { SlashCommandBuilder } = require('discord.js');
const { ListObjectsV2Command, PutObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
const { execSync } = require('child_process');
const fs = require('node:fs');
const S3 = require('../../s3');
const crypto = require('crypto');
const EmbedProxyClient = require('../../embed-proxy-client');

const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME;
if (!S3_BUCKET_NAME) throw new Error('No bucket name provided');

const S3_PUBLIC_BUCKET_URL = process.env.S3_PUBLIC_BUCKET_URL;
if (!S3_PUBLIC_BUCKET_URL) throw new Error('No public bucket URL provided');

module.exports = {
data: new SlashCommandBuilder()
.setName('instagram')
.setDescription('Displays an embedded Instagram video.')
.addStringOption(option => option.setName('url')
.setDescription('The Instagram video URL.')
.setRequired(true))
.addBooleanOption(option => option.setName('force')
.setDescription('Re-download?')),
async execute(interaction) {
const videoUrl = interaction.options.getString('url');
const force = interaction.options.getBoolean('force') || false;
console.log({ videoUrl, force });

const replyMsg = await interaction.reply('Processing Instagram link...');

// hash the videoUrl
const hash = crypto
.createHash('sha256')
.update(videoUrl)
.digest('hex')
.slice(0, 16);

let vurl = '', vwidth = 0, vheight = 0;

// check if the video is already downloaded
const data = await S3.send(new ListObjectsV2Command({
Bucket: S3_BUCKET_NAME, Prefix: `instagram/${hash}-`,
}));

console.log(data);

if (data.KeyCount !== 0 && force) {
console.log('Existing video with `force`, deleting');
await S3.send(new DeleteObjectCommand({
Bucket: S3_BUCKET_NAME,
Key: data.Contents[0].Key,
}));
}

if (data.KeyCount === 0 || force) {
// download the video
execSync('mkdir -p tmp/instagram');
try {
execSync(`yt-dlp -f bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best -S vcodec:h264 -o ./tmp/instagram/${hash}.mp4 ` + videoUrl, {
stdio: 'inherit',
});
} catch (e) {
console.error(e);
await replyMsg.edit('Error downloading video');
return;
}

// get width and height
let width = 0, height = 0;
try {
const res = execSync(`ffprobe -v error -select_streams v:0 -show_entries stream=width,height,codec_name -of json ./tmp/instagram/${hash}.mp4`).toString();
console.log(res);
const stream = JSON.parse(res).streams[0];
width = stream.width;
height = stream.height;
} catch (e) {
console.error(e);
await replyMsg.edit('No video found in URL');
return;
}

// upload the video
await S3.send(new PutObjectCommand({
Bucket: S3_BUCKET_NAME,
Key: `instagram/${hash}-${width}x${height}.mp4`,
Body: fs.createReadStream(`./tmp/instagram/${hash}.mp4`),
}));

// delete the video
execSync(`rm -f ./tmp/instagram/${hash}.mp4`);

vurl = `${S3_PUBLIC_BUCKET_URL}/instagram/${hash}-${width}x${height}.mp4`;
vwidth = width;
vheight = height;
} else {
// get width and height from the first file
const res = data.Contents[0].Key;
console.log(res);
// eslint-disable-next-line no-unused-vars
const [_, width, height] = res.match(/instagram\/.*-(\d+)x(\d+)\.mp4/);

vurl = `${S3_PUBLIC_BUCKET_URL}/${res}`;
vwidth = width;
vheight = height;
}

console.log(vurl, vwidth, vheight);
const embeddableUrl = await EmbedProxyClient.add(vurl, parseInt(vwidth, 10), parseInt(vheight, 10));
await replyMsg.edit(`${embeddableUrl}`);
},
};
33 changes: 20 additions & 13 deletions bot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,28 @@ client.on(Events.MessageCreate, async message => {
message = msgRef;
}

const videoUrlMatch = content.match(/https:\/\/(?:m|www|vm|vt)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video)\/|\?shareId=|&item_id=)(\d+))|\w+)/gim);

if (!videoUrlMatch) {
const tiktokVideoUrlMatch = content.match(/https:\/\/(?:m|www|vm|vt)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video)\/|\?shareId=|&item_id=)(\d+))|\w+)/gim);

const instagramVideoUrlMatch = content.match(/https:\/\/www\.instagram\.com\/reel\/[a-zA-Z0-9_-]+\/?/gim);

switch (true) {
case tiktokVideoUrlMatch !== null:
return client.emit(Events.InteractionCreate, {
isChatInputCommand: () => true, client: client, commandName: 'tiktok', options: {
getString: () => tiktokVideoUrlMatch[0],
getBoolean: () => force,
}, reply: message.reply.bind(message),
});
case instagramVideoUrlMatch !== null:
return client.emit(Events.InteractionCreate, {
isChatInputCommand: () => true, client: client, commandName: 'instagram', options: {
getString: () => instagramVideoUrlMatch[0],
getBoolean: () => force,
}, reply: message.reply.bind(message),
});
default:
return;
}

const videoUrl = videoUrlMatch[0];

// forward to tiktok command
client.emit(Events.InteractionCreate, {
isChatInputCommand: () => true, client: client, commandName: 'tiktok', options: {
getString: () => videoUrl,
getBoolean: () => force,
}, reply: message.reply.bind(message),
});
});

client.login(DISCORD_TOKEN);

0 comments on commit 8eeb52d

Please sign in to comment.