first commit

This commit is contained in:
2025-12-10 13:33:46 +09:00
commit d56f9846d5
9 changed files with 338 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
/Downloads/
+10
View File
@@ -0,0 +1,10 @@
# 디폴트 무시된 파일
/shelf/
/workspace.xml
# 쿼리 파일을 포함한 무시된 디폴트 폴더
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# 에디터 기반 HTTP 클라이언트 요청
/httpRequests/
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="temurin-1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>DiscordDownloaderBot</artifactId>
<version>1.0</version>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>DiscordDownloadBot</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
+53
View File
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>DiscordDownloaderBot</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.0.0-beta.19</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.13</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>DiscordDownloadBot</mainClass> </transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+173
View File
@@ -0,0 +1,173 @@
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
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.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
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";
// 저장할 폴더 (프로젝트 폴더 아래 'Downloads')
private static final String DOWNLOAD_DIR = System.getProperty("user.dir") + File.separator + "Downloads";
// 동시에 3개까지만 다운로드 처리 (서버 부하 방지)
private final ExecutorService executor = Executors.newFixedThreadPool(3);
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. 봇 실행
try {
JDABuilder.createDefault(BOT_TOKEN)
.enableIntents(GatewayIntent.MESSAGE_CONTENT) // 메시지 내용 읽기 권한
.addEventListeners(new DiscordDownloadBot())
.build();
System.out.println("🤖 봇 실행 완료! 유튜브/인스타 링크를 감지합니다.");
System.out.println("📂 저장 경로: " + DOWNLOAD_DIR);
} catch (Exception e) {
System.err.println("❌ 봇 실행 실패: 토큰이 올바른지 확인하세요.");
e.printStackTrace();
}
}
@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
// 1. 봇 자신의 메시지는 무시
if (event.getAuthor().isBot()) return;
// 2. 메시지 내용 가져오기
String message = event.getMessage().getContentRaw();
System.out.println("👂 감지된 메시지: " + message);
// 3. URL 추출 (작성하신 extractUrl 함수 사용)
String foundUrl = extractUrl(message);
// 4. 유효한 링크가 발견되면 다운로드 시작
if (foundUrl != null) {
System.out.println("✅ 다운로드 대상 링크 발견: " + foundUrl);
event.getChannel().sendMessage("📥 영상을 감지했습니다! 다운로드를 시도합니다... (" + foundUrl + ")").queue();
// 5. [핵심] 별도 스레드에서 다운로드 실행 (봇 멈춤 방지)
executor.submit(() -> {
processDownload(foundUrl, event);
});
}
}
// 유튜브, 인스타, 틱톡 등의 URL만 쏙 뽑아내는 함수
private String extractUrl(String text) {
// http 또는 https로 시작하는 주소 패턴
String regex = "(https?://\\S+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.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;
}
}
return null;
}
private void processDownload(String url, MessageReceivedEvent event) {
// 진행 상황을 표시할 메시지 객체
net.dv8tion.jda.api.entities.Message progressMessage = null;
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();
}
}
}
}
BIN
View File
Binary file not shown.