diff --git a/cookies.txt b/cookies.txt new file mode 100644 index 0000000..86f98ab --- /dev/null +++ b/cookies.txt @@ -0,0 +1,17 @@ +# Netscape HTTP Cookie File +# This file is generated by yt-dlp. Do not edit. + +.instagram.com TRUE / TRUE 1810802330 csrftoken cqJtFS0VgmeCPjjmVLovvPJXWA8L9cEj +.instagram.com TRUE / TRUE 1793493582 datr TTLXaMExDmYry2rCMm1_8VTv +.instagram.com TRUE / TRUE 1790469584 ig_did B4AF5095-476B-456B-A553-C6494A4C132F +.instagram.com TRUE / TRUE 1790469583 ig_nrcb 1 +.instagram.com TRUE / TRUE 1793493571 mid aNcyQwALAAG93crkt_hpz8Bjf_C- +.instagram.com TRUE / TRUE 1794698883 ps_l 1 +.instagram.com TRUE / TRUE 1794698883 ps_n 1 +.instagram.com TRUE / TRUE 0 rur "CCO\0542032189992\0541797490351:01fe0cab60bd4e111fcf77da34894f19d48758b1e2327dd4acd78bd64ee59cf6846f01fa" +.youtube.com TRUE / FALSE 0 PREF hl=en&tz=UTC +.youtube.com TRUE / TRUE 0 SOCS CAI +.youtube.com TRUE / TRUE 1781777544 VISITOR_INFO1_LIVE d6cJqkeLJ6I +.youtube.com TRUE / TRUE 1781777544 VISITOR_PRIVACY_METADATA CgJLUhIEGgAgHQ%3D%3D +.youtube.com TRUE / TRUE 0 YSC k9F7AmifQN8 +.youtube.com TRUE / TRUE 1781777544 __Secure-ROLLOUT_TOKEN CJjosKqwurikwwEQitTOib7IkQMYgovA8fbLkQM%3D diff --git a/src/main/java/DiscordDownloadBot.java b/src/main/java/DiscordDownloadBot.java index ae4e4e5..1540db5 100644 --- a/src/main/java/DiscordDownloadBot.java +++ b/src/main/java/DiscordDownloadBot.java @@ -1,12 +1,19 @@ import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; import net.dv8tion.jda.api.requests.GatewayIntent; import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Matcher; @@ -14,160 +21,275 @@ import java.util.regex.Pattern; public class DiscordDownloadBot extends ListenerAdapter { - // πŸ”΄ [ν•„μˆ˜] 여기에 λ””μŠ€μ½”λ“œ 봇 토큰을 λΆ™μ—¬λ„£μœΌμ„Έμš” + // πŸ”΄ 토큰 확인 private static final String BOT_TOKEN = "MTQ0Nzg1NjI1NTY4NjE0ODEzOA.GpvmOr.KRTrSGh1GrmlF_oHY1brKZbAXb3erfHBlPXHEI"; - // yt-dlp.exe 경둜 (ν”„λ‘œμ νŠΈ μ΅œμƒμœ„ 폴더 κΈ°μ€€) private static final String YTDLP_PATH = System.getProperty("user.dir") + File.separator + "yt-dlp.exe"; + private static final String BASE_DOWNLOAD_DIR = System.getProperty("user.dir") + File.separator + "Downloads"; - // μ €μž₯ν•  폴더 (ν”„λ‘œμ νŠΈ 폴더 μ•„λž˜ 'Downloads') - private static final String DOWNLOAD_DIR = System.getProperty("user.dir") + File.separator + "Downloads"; - - // λ™μ‹œμ— 3κ°œκΉŒμ§€λ§Œ λ‹€μš΄λ‘œλ“œ 처리 (μ„œλ²„ λΆ€ν•˜ λ°©μ§€) private final ExecutorService executor = Executors.newFixedThreadPool(3); + private static final Map pendingUrls = new HashMap<>(); + private static final Map isCreatingFolder = new HashMap<>(); public static void main(String[] args) { - // 1. ν•„μˆ˜ 파일 확인 - if (!new File(YTDLP_PATH).exists()) { - System.err.println("❌ 였λ₯˜: yt-dlp.exe νŒŒμΌμ„ ν”„λ‘œμ νŠΈ 폴더(pom.xml μ˜†)에 λ„£μ–΄μ£Όμ„Έμš”!"); - return; - } - - File folder = new File(DOWNLOAD_DIR); - if (!folder.exists()) { - boolean success = folder.mkdirs(); // 폴더가 μ—†μœΌλ©΄ 생성 - if (success) { - System.out.println("βœ… λ‹€μš΄λ‘œλ“œ 폴더λ₯Ό μƒˆλ‘œ λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€: " + DOWNLOAD_DIR); - } else { - System.out.println("⚠️ 폴더 생성 μ‹€νŒ¨! κΆŒν•œμ„ ν™•μΈν•˜μ„Έμš”."); - } - } - - // 2. 봇 μ‹€ν–‰ + new File(BASE_DOWNLOAD_DIR).mkdirs(); try { JDABuilder.createDefault(BOT_TOKEN) - .enableIntents(GatewayIntent.MESSAGE_CONTENT) // λ©”μ‹œμ§€ λ‚΄μš© 읽기 κΆŒν•œ + .enableIntents(GatewayIntent.MESSAGE_CONTENT) .addEventListeners(new DiscordDownloadBot()) .build(); - System.out.println("πŸ€– 봇 μ‹€ν–‰ μ™„λ£Œ! 유튜브/μΈμŠ€νƒ€ 링크λ₯Ό κ°μ§€ν•©λ‹ˆλ‹€."); - System.out.println("πŸ“‚ μ €μž₯ 경둜: " + DOWNLOAD_DIR); + System.out.println("πŸ€– 봇 μ‹€ν–‰ μ™„λ£Œ! (더블 νƒ­ μ „λž΅)"); } catch (Exception e) { - System.err.println("❌ 봇 μ‹€ν–‰ μ‹€νŒ¨: 토큰이 μ˜¬λ°”λ₯Έμ§€ ν™•μΈν•˜μ„Έμš”."); e.printStackTrace(); } } @Override public void onMessageReceived(@NotNull MessageReceivedEvent event) { - // 1. 봇 μžμ‹ μ˜ λ©”μ‹œμ§€λŠ” λ¬΄μ‹œ if (event.getAuthor().isBot()) return; - - // 2. λ©”μ‹œμ§€ λ‚΄μš© κ°€μ Έμ˜€κΈ° + String userId = event.getAuthor().getId(); String message = event.getMessage().getContentRaw(); - System.out.println("πŸ‘‚ κ°μ§€λœ λ©”μ‹œμ§€: " + message); - // 3. URL μΆ”μΆœ (μž‘μ„±ν•˜μ‹  extractUrl ν•¨μˆ˜ μ‚¬μš©) + if (isCreatingFolder.getOrDefault(userId, false)) { + createNewFolderAndDownload(event, message); + return; + } + String foundUrl = extractUrl(message); - - // 4. μœ νš¨ν•œ 링크가 발견되면 λ‹€μš΄λ‘œλ“œ μ‹œμž‘ if (foundUrl != null) { - System.out.println("βœ… λ‹€μš΄λ‘œλ“œ λŒ€μƒ 링크 발견: " + foundUrl); - event.getChannel().sendMessage("πŸ“₯ μ˜μƒμ„ κ°μ§€ν–ˆμŠ΅λ‹ˆλ‹€! λ‹€μš΄λ‘œλ“œλ₯Ό μ‹œλ„ν•©λ‹ˆλ‹€... (" + foundUrl + ")").queue(); - - // 5. [핡심] 별도 μŠ€λ ˆλ“œμ—μ„œ λ‹€μš΄λ‘œλ“œ μ‹€ν–‰ (봇 멈좀 λ°©μ§€) - executor.submit(() -> { - processDownload(foundUrl, event); - }); + pendingUrls.put(userId, foundUrl); + sendFolderSelectionMenu(event); } } - // 유튜브, μΈμŠ€νƒ€, 틱톑 λ“±μ˜ URL만 쏙 λ½‘μ•„λ‚΄λŠ” ν•¨μˆ˜ - private String extractUrl(String text) { - // http λ˜λŠ” https둜 μ‹œμž‘ν•˜λŠ” μ£Όμ†Œ νŒ¨ν„΄ - String regex = "(https?://\\S+)"; - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(text); + private void sendFolderSelectionMenu(MessageReceivedEvent event) { + File baseDir = new File(BASE_DOWNLOAD_DIR); + File[] directories = baseDir.listFiles(File::isDirectory); + StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("folder-select").setPlaceholder("πŸ“‚ μ €μž₯ν•  폴더λ₯Ό μ„ νƒν•˜μ„Έμš”"); + if (directories != null) { + for (File dir : directories) { + if (menuBuilder.getOptions().size() >= 24) break; + menuBuilder.addOption("πŸ“‚ " + dir.getName(), dir.getName()); + } + } + menuBuilder.addOption("βž• μƒˆ 폴더 λ§Œλ“€κΈ°", "create_new_folder"); + event.getChannel().sendMessage("μ˜μƒ 링크λ₯Ό ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€! **어디에 μ €μž₯ν• κΉŒμš”?**").addActionRow(menuBuilder.build()).queue(); + } + + @Override + public void onStringSelectInteraction(StringSelectInteractionEvent event) { + if (!event.getComponentId().equals("folder-select")) return; + String selectedValue = event.getValues().get(0); + String userId = event.getUser().getId(); + String url = pendingUrls.get(userId); + + if (url == null) { + event.reply("❌ 링크 정보가 λ§Œλ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.").setEphemeral(true).queue(); + return; + } + + if (selectedValue.equals("create_new_folder")) { + isCreatingFolder.put(userId, true); + event.reply("πŸ“ **μƒˆ 폴더 이름을 μž…λ ₯ν•΄μ£Όμ„Έμš”.**").setEphemeral(true).queue(); + } else { + event.reply("βœ… **" + selectedValue + "** 폴더에 μ €μž₯을 μ‹œμž‘ν•©λ‹ˆλ‹€!").queue(); + String targetPath = BASE_DOWNLOAD_DIR + File.separator + selectedValue; + executor.submit(() -> processDownload(url, event.getMessageChannel(), targetPath)); + pendingUrls.remove(userId); + } + } + + private void createNewFolderAndDownload(MessageReceivedEvent event, String folderName) { + String userId = event.getAuthor().getId(); + folderName = folderName.replaceAll("[^a-zA-Z0-9κ°€-힣_\\-]", ""); + if (folderName.isEmpty()) folderName = "NewFolder"; + String newFolderPath = BASE_DOWNLOAD_DIR + File.separator + folderName; + new File(newFolderPath).mkdirs(); + + String url = pendingUrls.get(userId); + if (url != null) { + event.getChannel().sendMessage("πŸ“₯ **" + folderName + "** 폴더에 λ‹€μš΄λ‘œλ“œ μ‹œμž‘!").queue(); + executor.submit(() -> processDownload(url, event.getChannel(), newFolderPath)); + } + isCreatingFolder.remove(userId); + pendingUrls.remove(userId); + } + + // ⬇️ [μ΅œμ’… μ „λž΅] 1μ°¨(μ˜μƒ) μ‹œλ„ -> μ‹€νŒ¨μ‹œ -> 2μ°¨(사진) μ‹œλ„ + private void processDownload(String url, net.dv8tion.jda.api.entities.channel.middleman.MessageChannel channel, String saveDir) { + Message progressMessage = null; + try { + progressMessage = channel.sendMessage("⏳ **λ‹€μš΄λ‘œλ“œ μ‹œλ„ 쀑...**").complete(); + + // 1. URLμ—μ„œ 번호 뽑기 + String targetIndexStr = getImgIndex(url); + String targetIndex = (targetIndexStr != null) ? targetIndexStr : "1"; + System.out.println("🎯 νƒ€κ²Ÿ 번호: " + targetIndex + "번"); + + // ========================================== + // πŸ‘Š 1타: λ™μ˜μƒμ΄λΌκ³  κ°€μ •ν•˜κ³  λ‹€μš΄λ‘œλ“œ μ‹œλ„ + // ========================================== + boolean isVideoSuccess = tryDownloadAsVideo(url, saveDir, targetIndex); + + if (isVideoSuccess) { + progressMessage.editMessage("βœ… **λ™μ˜μƒ λ‹€μš΄λ‘œλ“œ μ™„λ£Œ!**\nπŸ“‚ 폴더: `" + new File(saveDir).getName() + "`").queue(); + return; + } + + System.out.println("⚠️ λ™μ˜μƒ λ‹€μš΄λ‘œλ“œ μ‹€νŒ¨(사진인듯). 2μ°¨ μ‹œλ„ μ‹œμž‘..."); + + // ========================================== + // πŸ‘Š 2타: 사진이라고 κ°€μ •ν•˜κ³  λ‹€μš΄λ‘œλ“œ μ‹œλ„ + // ========================================== + boolean isImageSuccess = tryDownloadAsImage(url, saveDir, targetIndex); + + if (isImageSuccess) { + progressMessage.editMessage("βœ… **사진 λ‹€μš΄λ‘œλ“œ μ™„λ£Œ!**\nπŸ“‚ 폴더: `" + new File(saveDir).getName() + "`").queue(); + } else { + progressMessage.editMessage("❌ **μ΅œμ’… μ‹€νŒ¨.** (μΏ ν‚€ 만료 λ˜λŠ” κ²Œμ‹œλ¬Ό μ‚­μ œλ¨)").queue(); + } + + } catch (Exception e) { + if (progressMessage != null) progressMessage.editMessage("πŸ”₯ 였λ₯˜: " + e.getMessage()).queue(); + e.printStackTrace(); + } + } + + // πŸŽ₯ 1타: λ™μ˜μƒ λͺ¨λ“œ (Index μ§€μ •) + private boolean tryDownloadAsVideo(String url, String saveDir, String index) { + try { + List cmdList = new ArrayList<>(); + cmdList.add(YTDLP_PATH); + cmdList.add("--cookies"); + cmdList.add("cookies.txt"); + cmdList.add("--user-agent"); // λ™μ˜μƒμ€ PC UAκ°€ 더 μ•ˆμ •μ μΌ λ•Œκ°€ 있음 + cmdList.add("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); + + // 🎯 인덱슀 μ§€μ • + cmdList.add("--playlist-items"); + cmdList.add(index); + + // 포맷 κ°•μ œ (λ™μ˜μƒμ΄ μ•„λ‹ˆλ©΄ μ—¬κΈ°μ„œ μ—λŸ¬ λ‚˜κ³  죽음 -> catch둜 λ„˜μ–΄κ°) + cmdList.add("-f"); + cmdList.add("bestvideo+bestaudio/best"); + cmdList.add("--merge-output-format"); + cmdList.add("mp4"); + + cmdList.add("-o"); + cmdList.add(saveDir + File.separator + "%(title)s_%(id)s_Video" + index + ".%(ext)s"); + cmdList.add(url); + + System.out.println("πŸš€ [1타] λ™μ˜μƒ μ‹œλ„: " + index + "번"); + + ProcessBuilder pb = new ProcessBuilder(cmdList); + pb.redirectErrorStream(true); + Process process = pb.start(); + + // 둜그 읽기 (성곡 μ—¬λΆ€ νŒλ‹¨) + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + boolean success = false; + while ((line = reader.readLine()) != null) { + // System.out.println("[Video] " + line); // λ„ˆλ¬΄ μ‹œλ„λŸ¬μš°λ©΄ 주석 처리 + if (line.contains("100%") || line.contains("Destination")) { + success = true; + } + } + process.waitFor(); + + // 파일이 μ§„μ§œ μƒκ²ΌλŠ”μ§€ 확인 + if (success) return true; + + // yt-dlpκ°€ μ„±κ³΅ν–ˆλ‹€κ³  ꡬ라칠 μˆ˜λ„ μžˆμœΌλ‹ˆ 파일 확인 + return checkFileExists(saveDir, "Video" + index); + + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + // πŸ–ΌοΈ 2타: 사진 λͺ¨λ“œ (Index μ§€μ • + 썸넀일 κ°•μ œ μ €μž₯) + private boolean tryDownloadAsImage(String url, String saveDir, String index) { + try { + List cmdList = new ArrayList<>(); + cmdList.add(YTDLP_PATH); + cmdList.add("--cookies"); + cmdList.add("cookies.txt"); + cmdList.add("--user-agent"); // 사진은 λͺ¨λ°”일 UAκ°€ μ§±μž„ + cmdList.add("Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1"); + + // 🎯 인덱슀 μ§€μ • + cmdList.add("--playlist-items"); + cmdList.add(index); + + // ⭐ [핡심] "λ™μ˜μƒ 말고 썸넀일(사진)을 파일둜 써라" + cmdList.add("--write-thumbnail"); + cmdList.add("--skip-download"); // λ™μ˜μƒ λ‹€μš΄λ‘œλ“œλŠ” μƒλž΅ (μ–΄μ°¨ν”Ό μ—†μœΌλ‹ˆκΉŒ) + cmdList.add("--convert-thumbnails"); + cmdList.add("jpg"); + + cmdList.add("--ignore-errors"); // μ—λŸ¬ λ¬΄μ‹œ (No video μ—λŸ¬ μ”ΉκΈ°) + + cmdList.add("-o"); + // 파일λͺ…에 ImageTagλ₯Ό λΆ™μ—¬μ„œ 식별 + cmdList.add(saveDir + File.separator + "%(title)s_%(id)s_Image" + index + ".%(ext)s"); + cmdList.add(url); + + System.out.println("πŸš€ [2타] 사진 μ‹œλ„: " + index + "번"); + + ProcessBuilder pb = new ProcessBuilder(cmdList); + pb.redirectErrorStream(true); + Process process = pb.start(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + // System.out.println("[Image] " + line); + } + process.waitFor(); + + // 파일이 μƒκ²ΌλŠ”μ§€ 확인 (이게 제일 확싀함) + return checkFileExists(saveDir, "Image" + index); + + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + // 파일 생성 확인 ν•¨μˆ˜ + private boolean checkFileExists(String saveDir, String keyword) { + File dir = new File(saveDir); + File[] files = dir.listFiles(); + if (files == null) return false; + + for (File f : files) { + // 방금 λ‹€μš΄λ‘œλ“œ μ‹œλ„ν•œ 파일(ν‚€μ›Œλ“œ 포함)이 μ‘΄μž¬ν•˜λ©΄ 성곡 + if (f.getName().contains(keyword)) { + return true; + } + } + return false; + } + + private String extractUrl(String text) { + Matcher matcher = Pattern.compile("(https?://\\S+)").matcher(text); if (matcher.find()) { - String foundUrl = matcher.group(1); - // 유튜브, μΈμŠ€νƒ€, 틱톑 μ£Όμ†ŒμΈμ§€ ν•œλ²ˆ 더 체크 (μ›ν•˜λ©΄ 이 λΆ€λΆ„ μ‚­μ œν•΄μ„œ λͺ¨λ“  링크 ν—ˆμš© κ°€λŠ₯) - if (foundUrl.contains("youtube.com") || foundUrl.contains("youtu.be") || - foundUrl.contains("instagram.com") || foundUrl.contains("tiktok.com")) { - return foundUrl; + String found = matcher.group(1); + if (found.contains("youtube.com") || found.contains("youtu.be") || found.contains("instagram.com")) { + return found; } } return null; } - private void processDownload(String url, MessageReceivedEvent event) { - // μ§„ν–‰ 상황을 ν‘œμ‹œν•  λ©”μ‹œμ§€ 객체 - net.dv8tion.jda.api.entities.Message progressMessage = null; - + private String getImgIndex(String url) { try { - // 1. μ²˜μŒμ— "λ‹€μš΄λ‘œλ“œ μ‹œμž‘" λ©”μ‹œμ§€λ₯Ό 보내고, κ·Έ λ©”μ‹œμ§€ 객체λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€. - progressMessage = event.getChannel().sendMessage("⏳ **λ‹€μš΄λ‘œλ“œ μ€€λΉ„ 쀑...**").complete(); - - File dir = new File(DOWNLOAD_DIR); - if (!dir.exists()) dir.mkdirs(); - - String outputPath = DOWNLOAD_DIR + File.separator + "%(title)s.%(ext)s"; - - // 2. λͺ…λ Ήμ–΄ μ„€μ • (--newline μ˜΅μ…˜ μΆ”κ°€κ°€ 핡심!) - String[] command = { - YTDLP_PATH, - "--newline", // πŸ‘ˆ [μ€‘μš”] μ§„ν–‰λ₯ μ„ ν•œ 쀄씩 좜λ ₯ν•˜κ²Œ 함 - "--no-warnings", - "-f", "bestvideo+bestaudio/best", - "--merge-output-format", "mp4", - "-o", outputPath, - url - }; - - ProcessBuilder pb = new ProcessBuilder(command); - pb.redirectErrorStream(true); // μ—λŸ¬λ„ 같이 읽음 - Process process = pb.start(); - - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line; - - // μ‹œκ°„ 체크용 λ³€μˆ˜ (λ„ˆλ¬΄ μž¦μ€ μ—…λ°μ΄νŠΈ λ°©μ§€) - long lastUpdateTime = 0; - - // 3. yt-dlp의 좜λ ₯을 ν•œ 쀄씩 읽음 - while ((line = reader.readLine()) != null) { - System.out.println("[yt-dlp] " + line); // μ½˜μ†”μ—λ„ 좜λ ₯ - - // "[download] 25.4%" 같은 νŒ¨ν„΄μ„ 찾음 - if (line.contains("[download]") && line.contains("%")) { - long currentTime = System.currentTimeMillis(); - - // 3초(3000ms)κ°€ 지났을 λ•Œλ§Œ λ””μŠ€μ½”λ“œ λ©”μ‹œμ§€ μˆ˜μ • - if (currentTime - lastUpdateTime > 3000) { - // μ •κ·œμ‹μœΌλ‘œ 숫자만 쏙 뽑기 (예: 25.4%) - Matcher matcher = Pattern.compile("(\\d+\\.\\d+%)").matcher(line); - if (matcher.find()) { - String percent = matcher.group(1); - progressMessage.editMessage("πŸ“₯ **λ‹€μš΄λ‘œλ“œ 쀑...** " + percent).queue(); - lastUpdateTime = currentTime; - } - } - } - } - - int exitCode = process.waitFor(); - - // 4. μ™„λ£Œ μ‹œ λ©”μ‹œμ§€ μˆ˜μ • - if (exitCode == 0) { - progressMessage.editMessage("βœ… **λ‹€μš΄λ‘œλ“œ μ™„λ£Œ!**\n(νŒŒμΌμ€ μ„œλ²„ μ»΄ν“¨ν„°μ˜ `Downloads` 폴더에 μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€.)").queue(); - } else { - progressMessage.editMessage("❌ **λ‹€μš΄λ‘œλ“œ μ‹€νŒ¨.** μ½˜μ†” 둜그λ₯Ό ν™•μΈν•˜μ„Έμš”.").queue(); - } - - } catch (Exception e) { - e.printStackTrace(); - if (progressMessage != null) { - progressMessage.editMessage("πŸ”₯ **였λ₯˜ λ°œμƒ:** " + e.getMessage()).queue(); - } - } + Matcher m = Pattern.compile("[?&]img_index=(\\d+)").matcher(url); + if (m.find()) return m.group(1); + } catch (Exception e) { /* λ¬΄μ‹œ */ } + return null; } } \ No newline at end of file