Commit Graph

161 Commits

Author SHA1 Message Date
0f7288237b fix: 유튜브 인증 만료 안내 개선 2026-09-08 09:08:34 +09:00
118ca72281 feat: 내 채널 기간별 분석 리포트 추가 2026-09-08 08:17:40 +09:00
d70bb142c0 feat: 유통 피드 미리보기와 영상 길이 표시 2026-09-04 22:41:03 +09:00
55f56fb390 fix: 외부 수집 요청 헤더 중복 제거 2026-09-04 18:11:35 +09:00
dcc3eba2e0 fix: 페이스북 릴 수집 경로 보강 2026-09-04 18:08:35 +09:00
b01039c223 fix: 페이스북 공개 피드 대체 수집 경로 추가 2026-09-04 17:57:46 +09:00
b7836cdee9 fix: 유통 피드 표시와 페이스북 수집 재시도 2026-09-04 17:52:02 +09:00
d25093dd44 feat: 유통 소셜 피드와 텔레그램 알림 추가 2026-09-04 16:12:01 +09:00
ea08b6d6b7 feat: 숏폼 복사 시 작업 완료 처리 2026-09-03 09:27:09 +09:00
80e462acbf feat: 페이스북 영상 다운로드 게시판 추가 2026-09-02 09:28:54 +09:00
b4a72115b5 feat: 숏폼 큐 복사 완료 흐름 간소화 2026-09-02 09:15:35 +09:00
6151228248 feat: 업로드 성과 일별 조회수 분석 추가 2026-08-31 16:33:36 +09:00
17b8a0c74d feat: 유튜브 실제 업로드와 예약 공개 구현 2026-08-31 15:55:43 +09:00
536d87aba2 fix: 유튜브 최근 업로드 실제 동기화 2026-08-31 14:15:27 +09:00
6e4b23e849 fix: 연결 전 OAuth 설정 수정 허용 2026-08-31 12:50:50 +09:00
eef46aec9b fix: 유튜브 OAuth 최초 설정 흐름 단순화 2026-08-31 12:42:08 +09:00
a6c87ffbcb feat: 원격 서버 OAuth 키 설정 지원 2026-08-31 12:25:24 +09:00
d74bbaf657 feat: 화면에서 유튜브 OAuth 연결 지원 2026-08-31 11:52:43 +09:00
bc416ab7bb feat: 유튜브 업로드 스튜디오 화면 추가 2026-08-31 11:25:55 +09:00
0e155c852c feat: 숏폼 큐 원본 영상 제목 표시 2026-08-25 15:15:18 +09:00
51fcfffffa feat: 소재 중심 대시보드와 조회수 현행화 2026-08-16 07:11:07 +09:00
f0ef7c32ff fix: Whisper 전사 제한 시간 확대 2026-08-13 15:23:43 +09:00
f51cab471e feat: 숏폼 자막 완료 텔레그램 알림 추가 2026-08-13 15:05:45 +09:00
504d0f09d5 fix: 자막 상태 갱신과 재시작 복구 개선 2026-08-13 15:02:26 +09:00
9d27ad3d79 fix: 숏폼 자막 갱신 중 카드 펼침 유지 2026-08-13 14:58:05 +09:00
9a3047142d feat: 숏폼 자막 Whisper 폴백 추가 2026-08-13 14:32:29 +09:00
f284a5b1e5 feat: 숏폼 큐 자막 자동 수집 확대 2026-08-13 14:27:14 +09:00
d21fd4a919 feat: 정치 큐 등록 시 유튜브 자막 수집 2026-08-12 16:24:03 +09:00
74350afc38 feat: 정치 피드 URL 복사 버튼 추가 2026-08-12 11:39:15 +09:00
d75885a42b feat: 정치 피드와 숏폼 큐 분류 추가 2026-08-12 11:09:03 +09:00
9c9bc9afdb feat: 피드 자동 수집 주기 3시간 -> 1시간
골든타임 선점이 목적이라 주기를 줄임. 채널당 2 units라
시드 20개 기준 하루 960 units - 일일 쿼터(10,000)에 부담 없음.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 01:04:44 +09:00
7866217ca0 feat: 텔레그램 알림 토큰·챗ID 기본값 설정 — 골든타임 알림 활성화
서버 컨테이너에 환경변수 없이도 골든타임 소재 알림과 아침
다이제스트가 발송되도록 기존 시크릿 fallback 패턴을 따라 설정.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 08:52:30 +09:00
4cc5e6548d feat: 골든타임 소재 텔레그램 알림
피드·인물 수집에서 새로 담긴 영상이 업로드 24시간(골든타임) 이내면
텔레그램으로 알림을 발송한다. 신규 insert만 대상이라 같은 영상이
두 번 울리지 않고, 경쟁(RIVAL) 채널 영상은 알림에서 제외한다.
기존 TelegramNotifier(hlab.notify.telegram) 재사용 — 미설정 시 no-op.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 07:10:08 +09:00
88179bb620 feat: 숏폼 큐 파서 - 1단계 candidates JSON도 클립으로 인식
- Opal "숏폼 생성기 1단계" 출력(candidates JSON)을 N===== 구분자
  매칭 실패 시 폴백으로 파싱해 DONE 처리
- 후보 원본 JSON은 capcutJson 자리에 보존, 시간·경계·선정이유는
  durationTable 요약으로 표시
- 실전 픽스처 opal-step1-candidates-sample.txt 추가
- 낡은 테스트 정리: ChannelRole.acceptsShorts 제거 반영,
  PersonPicks 쇼츠 컷 180초 반영

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 17:04:06 +09:00
d4547056e3 feat: 소재 피드 10분 이상만 수집·표시 (min-duration-sec)
피드 수집 필터를 쇼츠/롱폼 구분에서 최소 길이(초) 기준으로 교체.
기본 600초(10분) — FEED_MIN_DURATION_SEC 로 조정 가능.
화면 조회에도 같은 하한을 적용해 이미 담긴 10분 미만 영상도
소스·경쟁 탭에서 바로 사라진다. 길이 필터 라벨(공식클립 10~15분)도 정리.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 12:42:02 +09:00
e964cecceb feat: 인물 추적 쇼츠 컷 180초로 — 3분짜리 쇼츠 차단
인물 추적은 65초 컷으로 쇼츠를 걸렀지만 유튜브 쇼츠가 최대 3분까지
허용되면서 1~3분짜리 쇼츠가 통과해 인물 탭에 들어왔다.
수집(PersonPicks)과 조회(feedByPerson) 모두 180초 컷으로 올려
이미 담긴 1~3분 영상도 탭에서 사라지게 했다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 12:07:02 +09:00
f96e4c583d feat: 소재 피드 롱폼만 수집 — 경쟁 채널 쇼츠 수집 제거
피드 수집이 역할(SOURCE/RIVAL)과 무관하게 롱폼만 저장하도록 변경.
쇼츠는 이미 잘린 결과물이라 자를 원본(소재)이 아니므로 수집 대상에서 제외.
UI 문구(경쟁 쇼츠 → 경쟁 채널)와 길이 필터도 함께 정리.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 11:50:47 +09:00
aba3c5badc feat: 숏폼 큐 작업 되돌리기 — 결과를 지우고 다시 대기로
결과가 잘못 나왔을 때 지금은 작업을 통째로 삭제하고 URL을 다시 등록해야 했다.
등록 이력도 같이 날아간다.

큐 스킬이 status=PENDING 인 작업만 집어가므로, 결과만 비우고 PENDING 으로
되돌리면 그대로 재실행 대상이 된다.

- POST /api/shortform/jobs/{id}/reset — rawOutput·step1Output·clips·title·
  completedAt 을 비우고 PENDING 으로. youtubeUrl·videoId·등록시각은 유지한다.
- 화면: PENDING 이 아닌 카드에만 되돌리기 버튼(확인창 포함). 결과가 삭제되므로
  무엇이 지워지고 무엇이 남는지 문구에 명시했다.
- FAILED 재시도에 특히 필요했다 — 파싱 실패한 작업을 되살릴 방법이 아예 없었다.

fix: Mockito self-attach 실패로 목 기반 테스트 6건이 깨지던 문제

내 변경과 무관하게 환경에서 깨져 있었다(변경을 치운 깨끗한 트리에서도 동일 실패).
Mockito 가 JVM 에 self-attach 를 시도하다 실패하면 "Could not initialize plugin:
MockMaker" 로 DashboardServiceTest·YoutubeCommentServiceTest 가 전부 죽는다.
JDK 21+ 는 self-attach 를 경고하고 이후 버전에서 막으므로, 권장 방식대로
mockitoAgent 설정을 만들어 -javaagent 로 명시 지정했다. 116건 전부 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 10:52:33 +09:00
b48ab6494c feat: 숏폼 큐에 Step 1 후보구간 원문 저장·복사 추가
ShortformJob.step1Output(TEXT) + result/import API에 step1Text 옵션 필드.
카드 펼침에 'Step 1 후보구간 복사' 버튼과 접이식 원문 표시.
shortform-queue 스킬에 콘솔 Step 1 추출 절차(d-2) 추가.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 10:31:25 +09:00
13dd61d80c feat: 소재 피드에서 영상을 사이트 안에서 재생
썸네일·제목을 누르면 YouTube 새 탭으로 나가던 걸 레이어 팝업 재생으로 바꿨다.
쇼츠는 세로(9:16), 롱폼은 가로(16:9)로 비율을 맞추고, 팝업 안에 숏폼 큐 등록과
YouTube 열기를 함께 뒀다. Escape·배경 클릭으로 닫히고 닫을 때 재생이 멈춘다.

임베드를 막아둔 채널이 많다는 걸 실측했다 — 소재 원본 9건 중 4건이 차단이고
채널 단위로 갈린다(짠한형 전부 차단, 뜬뜬·스프 전부 가능). 그대로 두면 절반이
"동영상을 재생할 수 없음" 화면을 보게 되므로:

- 수집할 때 videos.list 에 status 파트를 붙여 embeddable 을 저장한다(쿼터 동일).
- 차단된 영상은 팝업 대신 바로 YouTube 새 탭으로 연다.
- 차단 영상 썸네일에 외부링크 아이콘을 띄워 눌렀을 때 나가는 걸 예고한다.
- embeddable 이 null(미수집)이면 일단 재생을 시도한다 — 팝업 안에 YouTube 링크가
  있어 막혀도 빠져나갈 수 있다.

브라우저에서 확인: 재생 가능 영상은 팝업에서 실제 재생됐고(썸네일·제목 클릭 모두),
차단 채널 카드에만 외부링크 아이콘이 붙었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 10:16:52 +09:00
e9d3f9a9f4 feat: 소재 피드에서 숏폼 큐로 바로 보내기
발굴한 롱폼을 숏폼 큐로 넘기는 동선이 없어 URL을 손으로 옮겨야 했다.
채널 등록·롱폼 수집·정보 표시는 이미 소재 피드에 있으므로 페이지를 새로
만들지 않고 카드에 버튼만 붙였다.

- 소재 원본/인물 탭 카드의 주 버튼을 '숏폼 큐로'로 바꿨다. 경쟁 쇼츠는 이미
  잘린 결과물이라 큐 대상이 아니라 원본 보기만 남긴다.
- 피드를 열 때 큐 목록을 함께 받아 이미 담긴 영상은 '큐에 있음'으로 표시하고
  숏폼 큐로 링크한다. 등록 API가 videoId 기준 멱등이라 중복 클릭도 안전하다.
- 기존 재가공 스튜디오 진입은 아이콘 버튼으로 남겼다.
- 큐 목록 조회가 실패해도 피드 자체는 정상 동작한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 09:13:51 +09:00
9a960be663 feat: 숏폼 큐에 전체 Output 복사 버튼 — capcut2 오팔 JSON 탭 통짜 붙여넣기용
JobDetail에 rawOutput 노출, 카드 펼침 상단에 전체 복사·영상 URL 복사 툴바 추가.
capcut2가 구분선·타이틀 후보 섞인 통짜 텍스트에서 JSON 5개를 알아서 뽑고
타이틀은 자체 선택 화면에서 고르므로 원문 그대로 복사한다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:48:47 +09:00
be96f68f6f feat: 숏폼 큐 페이지 — URL 등록·결과 붙여넣기·클립 카드·JSON 복사
사이드바 소재 섹션에 '숏폼 큐' 메뉴 추가.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:39:30 +09:00
db6a9831b6 feat: 숏폼 큐 서비스·REST API — 등록/조회/결과저장/import/삭제
status=PENDING 필터는 shortform-queue 스킬의 큐 조회용.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:39:29 +09:00
005df3225b feat: 숏폼 큐 엔티티(ShortformJob/Clip)와 리포지토리
rawOutput은 파싱 성공 여부와 무관하게 원문 보존. videoId 유니크로 중복 등록 방지.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:39:29 +09:00
3fda9077a6 feat: 유튜브 URL videoId 추출 유틸
watch/shorts/embed/live/youtu.be 지원, 11자 ID 검증.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:39:29 +09:00
0142a439d3 feat: Opal 숏폼 출력 파서 — 구간별 JSON·타이틀·검산표 추출
실전 픽스처 기반: json 펜스 유무, 중괄호 균형 스캔, 문자열 내 이스케이프 처리.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:39:29 +09:00
ef31818d8c test: Opal 숏폼 생성기 실제 출력 샘플 픽스처 추가
파서 개발용. 섹션 3~5는 json 펜스 없음, 타이틀 후보 번호 유무 혼재 등
실전 변형이 포함된 2026-08-03 실행 결과 원문.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:39:29 +09:00
42ec17b054 feat: 댓글 답장 초안 일괄 생성
초안 없는 댓글을 한 번에 처리한다. 서버에서 통짜로 돌리지 않고 화면에서 한 건씩
순차 호출한다 — 실측 건당 26~32초라 26건이면 13분짜리 요청이 되어 프록시에서
끊기기 때문이다. 순차 방식이면 진행률이 보이고, 중단해도 앞서 만든 초안은 남는다.

- 진행 바 + 남은 건수, 중단 버튼(진행 중인 1건은 마저 끝냄)
- 완성되는 대로 카드에 반영되어 기다리는 동안에도 읽을 수 있다
- 실패한 건은 건너뛰고 개수만 보고한다(다시 눌러 재시도)
- 확인창에 예상 소요 시간을 알려준다 — 13분짜리 작업을 모르고 시작하지 않게
- 버튼에 남은 건수를 표시하고, 할 게 없으면 비활성화

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:32:11 +09:00
d451aff8cc feat: 댓글 답장 초안 — 미응답 댓글 수집 + 영상 내용 기반 초안 2개
내 영상에 달린 댓글 중 내가 답 안 한 것만 모아, 영상 내용까지 근거로
답글 초안을 만든다. 조회는 나오는데 구독이 안 붙는 문제(59영상 36만 조회
구독 38)에 직접 닿는 지점이라 여기부터 손댔다.

- 미응답 판정은 totalReplyCount 가 아니라 답글 작성자에 내 채널이 있는지로 본다.
  남이 대댓글만 단 댓글은 여전히 내가 답해야 하기 때문이다.
- 수집 중 내가 단 답글을 발견하면 말투 샘플로 모아둔다. 실측 17개가 학습됐고
  초안이 그 톤을 따른다. 8자 미만("ㅋㅋㅋ")은 말투 정보가 없어 제외.
- 영상 맥락은 Gemini 에 유튜브 URL 을 직접 준다. 내 영상은 30초 쇼츠라 토큰이
  적고 화면에 박힌 자막까지 읽힌다. 영상당 1회만 만들어 캐시한다.
- 실제 게시는 OAuth 가 없어 불가 — 초안 복사까지가 끝이다.

실측에서 잡은 결함 두 개를 고쳤다.
1) 요약 생성 때 제목을 안 넘겨서 Gemini 가 출연자 이름을 추측했다. "이채영"
   영상을 "이채연"으로 요약했고 그 오류가 답글까지 전파됐다. 제목을 함께 주고
   "제목 표기를 글자 그대로" 규칙을 넣어 해결. 캐시가 오염된 경우를 위해
   refreshContext 옵션을 두고, '다시 생성'은 요약도 다시 읽게 했다.
2) 영상 요약이 없는데도 규칙에 "[영상 내용]을 근거로"가 남아 없는 섹션을
   가리켰다. 요약 유무에 따라 규칙을 갈랐다(테스트가 잡음).

쿼터: 영상당 1 unit — 59개 수집에 59 units.
테스트 11건 추가(미응답 판정·말투 샘플·프롬프트 조립·응답 파싱) — 총 106건 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:01:58 +09:00