업스트림 행 시 봇 동기화/요청 핸들러가 무한 대기하던 문제 방지.
fetchWithTimeout(AbortController, 10s)으로 7개 fetch 호출 일괄 래핑.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
깨진 JSON 값 하나가 getAllBots 전체를 throw시켜 startAll(전체 봇 기동)을
막던 위험 제거. weekly_schedule_config/title_filters/default_member_ids/
auto_schedule_config/text_filters를 safeParse(fallback)로 처리.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
무인증 공개 엔드포인트가 봇 설정(채널/계정/필터)과 에러 내부정보
(errorMessage 등)를 그대로 노출하던 것을 상태 요약 필드만 반환하도록
화이트리스트. getBots() await 누락(잠재 버그)도 함께 수정.
관리자 화면은 인증된 /api/admin/bots 사용(영향 없음).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
POST body 스키마/구조분해에 exclude_managed_channels(default true,
컬럼 기본값과 일치)를 추가. 누락으로 INSERT 시 ReferenceError 크래시.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
TikTok은 OG 이미지를 막지만 oEmbed는 thumbnail_url 제공. 단 서명·만료
URL이라 저장하지 않고, /api/schedules/x-card-thumb/:postId 엔드포인트가
요청 시 oEmbed로 현재 썸네일을 받아 302 리다이렉트(Redis 6h 캐시).
resolveCard는 TikTok 카드 이미지를 이 프록시 경로로 설정.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nitter가 주는 pbs.twimg.com/card_img 이미지는 시간이 지나면 404로
만료됨. resolveCard에서 카드 이미지가 없거나 해당 만료성 URL이면
원본 URL의 OG 이미지(i.ytimg 등 안정적)로 대체.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 스크래퍼가 Nitter HTML 엔티티(/ & 등)를 디코딩하지 않아
본문/카드 제목에 [JP/EN]처럼 노출되던 문제 수정 (extractCard,
extractTextFromHtml에 decodeEntities 적용)
- resolveCard가 Nitter 카드에 제목만 있고 이미지가 없을 때 OG로 이미지를
보강하도록 변경 (YouTube 카드 이미지 누락 복구)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- botMonths 유틸(parse/serialize) 추가, 빈배열·전체선택은 NULL 정규화
- 축제 봇 라우트 조회/생성/수정에 active_months 반영 (수정 시 봇 재시작으로 즉시 적용)
- 봇별 설정이 이미 분리돼 있어 x/youtube에는 미적용 — DB 컬럼/스케줄러 매퍼에서 제외
- 스케줄러 isActiveMonth 게이트는 범용 유지(미설정 봇은 항상 실행)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
봇 매퍼에 activeMonths 파싱 추가. cron 콜백·즉시 실행 시 현재 KST
월이 활성 월에 포함될 때만 동기화 실행(매 실행 시점 재평가). NULL/
빈배열/12개 전체는 항상 실행. date.js에 monthKST() 헬퍼 추가.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bot_festival/bot_x/bot_youtube에 active_months JSON 컬럼 추가.
NULL=전체 월(항상 실행), 정수 배열이면 해당 월에만 동기화.
대학 축제 봇처럼 시즌에만 도는 봇의 불필요한 API 호출 절약.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
월만 확정된 일정을 위해 schedules에 date_precision ENUM('day','month')
추가. 기본값 'day'로 기존 일정/쿼리에 영향 없음. month인 경우
date는 해당 월 1일로 저장하고 확정 시 수정에서 정확한 날짜 입력.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
트윗의 외부 링크에 대해 미리보기 카드 표시.
- Nitter가 렌더링한 카드 우선 사용 (extractCard)
- Nitter 카드가 비어있으면 본문 URL로 OG 직접 추출 (og.js)
- YouTube/Instagram 등 복구, HTML 엔티티 디코딩 포함
- TikTok 등 봇 차단 사이트는 Nitter 카드로 커버
- schedule_x.card_data 컬럼 + getScheduleDetail 응답에 card 포함
- 가로 레이아웃 카드 (왼쪽 이미지 + 오른쪽 텍스트)
- CardImage: 이미지 로드 실패 시 fallback 아이콘 (인스타 CDN 만료 대비)
- 자체 영상/이미지가 있으면 OG 카드 숨김 (중복 방지)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nitter는 영상 파일을 제공하지 않으므로 썸네일만 추출해 표시하고,
재생 시 원본 트윗으로 이동.
- scraper: extractVideoThumbnails 추가 (amplify_video_thumb 등)
- schedule_x.video_thumbnails 컬럼 + saveTweet 저장
- getScheduleDetail 응답에 videoThumbnails 포함
- PC/모바일 X 상세: 썸네일 + 재생버튼 + 'X에서 재생' 배지
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
같은 리트윗이 타임라인에서 래퍼 id / 원본 id 두 형태로 번갈아 나타나
post_id 중복 체크를 통과해 두 번 저장되던 문제 수정.
리트윗 저장 시 동일 내용 + (원작자 또는 봇계정) username이 이미 있으면
중복으로 간주해 건너뜀. hydration으로 양쪽 모두 전체 내용을 갖추므로
내용 기반 매칭이 안정적으로 동작.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
X long tweet(280자 초과)을 Nitter 타임라인이 간헐적으로 …로 잘라서
주는 경우, 개별 상태 페이지에서 재요청해 전체 내용으로 교체.
- parseTweets: …로 끝나는 트윗에 truncated 플래그 부여
- hydrateTruncatedTweets: 잘린 트윗 status 페이지 재요청 후 교체 (best-effort)
- fetchTweets/fetchAllTweets에 적용
- fetchSingleTweet을 timeout-safe하게 변경
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- logs API: longtext로 저장된 details를 JSON 객체로 파싱해 반환
- 응답 스키마에 additionalProperties: true 추가 (fast-json-stringify가
스키마 미정의 키를 제거하던 문제 해결)
- scheduler 에러 로그: err.cause / err.code / err.causeCode 함께 기록
(fetch failed 등 모호한 메시지의 진짜 원인 식별 가능)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
검색 페이지(memogipost)를 크롤링하여 프로미스나인 출연 대학 축제를
Gemini url_context로 추출, 행사 일정을 자동 생성하는 봇.
백엔드:
- services/event.js: 이벤트 생성 로직 공유화 (upsertVenue, createEventSchedule, 카카오 검색)
- services/festival/: scraper(검색 페이지 크롤) + gemini(추출) + index(봇 플러그인)
- routes/admin/festival-bots.js: 축제 봇 CRUD API
- scheduler.js: festival 타입 지원, 시간 단위 cron(0 */H * * *) 변환
- 처리한 글 URL은 festival_crawl_log에 기록, 새 글 없으면 Gemini 미호출
- 학교명 부분일치 중복 감지, 활동 멤버 전체 자동 등록
- Gemini 503/500/429 재시도 로직
기타 수정:
- 행사 상세 페이지 관련 링크 줄바꿈 (truncate → break-all)
- 대학 축제 아이콘 변경 (GraduationCap → PartyPopper)
- docs/api.md, CLAUDE.md 환경변수 문서화
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- bot_festival, festival_crawl_log 테이블 SQL
- FestivalBotDialog: 봇 이름/크롤링 URL/동기화 간격(1~24시간) 입력
- 봇 관리 페이지에 '축제' 섹션 추가 (emerald, PartyPopper)
- BotCard: festival 타입 수정/삭제 버튼 표시
- API 클라이언트 함수 추가 (백엔드 라우트는 3단계)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- bot_youtube에 extract_members_from_title 컬럼 추가 (기본값 0)
- services/youtube/index.js: 설명과 제목에서 각각 멤버 이름 검색, 합집합으로 중복 제거
- YouTubeBotDialog 고급 설정에 토글 추가 (설명 추출 토글 아래)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- bot_x에 exclude_managed_channels 컬럼 추가 (기본값 1, 기존 동작 유지)
- X 봇이 트윗에서 YouTube 링크를 추출할 때 이미 등록된 YouTube 봇 채널의 영상을 중복 추가할지 옵션으로 제어
- XBotDialog에 토글 추가 (extract_youtube 활성 시만 노출, 왼쪽 border로 하위 옵션 시각화)
- services/x/index.js processYoutubeLinks 시그니처에 옵션 파라미터 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Admin: EventEditForm 추가 (기존 포스터 유지 + 신규 추가 조합), ScheduleItem 편집 경로에 '행사' 분기
- PC 공개 상세: EventSection 추가 - 포스터 Swiper 슬라이드 + 호버 화살표, 클릭 시 Lightbox, 카카오맵 + 마커 + 장소명 오버레이, 관련 링크는 중간점+primary 색상, max-w-5xl 및 text-2xl로 크기 확대
- Mobile 공개 상세: MobileEventSection 추가 (포스터/장소/지도/링크)
- KakaoMap 공용 컴포넌트 신규 (SDK 1회 로드 공유), VITE_KAKAO_JS_KEY 사용
- .gitignore: frontend/.env 제외
- routes/admin/events.js: PUT 핸들러의 addOrUpdateSchedule → syncScheduleById 정정
- 관련 문서(api/architecture/development) 업데이트
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- bot_youtube에 weekly_schedule_config JSON 컬럼 추가, cron_interval nullable로 변경
- weekly 모드: 지정 요일/시각에만 cron 트리거 → setInterval로 intervalSeconds 간격 폴링
- 종료 조건: 새 영상 1개 발견(stopOnFound) 또는 durationMinutes 경과
- 평상시 API 호출 없어 주 1회 업로드 채널(워크맨 등)의 할당량 낭비 최소화
- 프론트 폼에 상시/주간 모드 토글 추가, 요일 드롭다운 월~일 순서로 정렬
- 관련 문서(api/development/architecture) 갱신
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- consecutiveErrors 카운터로 실패 횟수 추적 (성공 시 0으로 리셋)
- 동일 에러 루프에서 sync/error 로그는 첫 1회만 기록하여 스팸 방지
- 10회 연속 실패 시 stopBot 호출 및 bot/stop 로그 1건 남김
- docs/logs.md, docs/development.md 관련 설명 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- GET /admin/variety/broadcasters: DB에서 빈도수 상위 10개 조회 (Redis 1시간 캐시)
- 일정 생성/수정 시 캐시 무효화
- 프론트엔드: 하드코딩 프리셋 제거, API에서 동적으로 로드
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- schedule/{id}/thumbnail/ 경로에 original/medium_800/thumb_400 webp 업로드
- images 테이블로 이미지 관리, schedule_variety.thumbnail_id로 참조
- 프론트엔드: URL 입력 → 파일 업로드(드래그&드롭) + 미리보기로 변경
- 수정 시 기존 썸네일 교체/삭제 지원
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- schedule_variety: broadcaster, replay_url, thumbnail_url
- 카테고리 ID 10 '예능' (#22c55e) 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
photos.js, teasers.js에서 invalidateAlbumCache 호출 추가.
앨범 생성 후 사진/티저 추가 시 캐시된 빈 데이터가 반환되던 문제 해결.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 백엔드: GET /admin/concert/schedule/:seriesId (상세 조회)
- 백엔드: PUT /admin/concert/schedule/:seriesId (수정)
- 프론트엔드: ConcertEditForm 페이지 (생성 폼 컴포넌트 재사용)
- 라우트: /admin/schedule/concert/:seriesId/edit 등록
- 일정 목록: 콘서트 카테고리 편집 버튼이 수정 페이지로 연결
- schedule.js: formatSchedule에 concertSeriesId 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 프론트엔드: 단일 setlist → 회차별 setlists (탭 UI로 전환)
- 회차 추가 시 이전 회차의 세트리스트 자동 복사
- '다른 회차에서 복사' 기능 추가
- 백엔드: 각 concert_id별로 독립적인 세트리스트 저장
- 하위호환: 기존 setlist 필드도 지원 (단일 배열 → 첫 회차에 적용)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- getProfile: bot_x에 없는 계정도 Nitter에서 직접 조회 후 Redis 캐시
- refetch-retweets 스크립트: 원본 작성자 타임라인에서 매칭 트윗 찾아 이미지/내용 복구
- 기존 21건 리트윗 데이터 재수집 완료 (이미지 포함)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- extractTextFromHtml: Nitter 프록시 t.co URL을 원본 https://t.co/ URL로 변환
- parseTweets: 리트윗 원본 작성자(originalUsername) 추출, URL을 원본 작성자 기준으로 생성
- saveTweet: 리트윗인 경우 원본 작성자를 username으로 저장
- refetch-retweets 엔드포인트 및 스크립트 추가 (기존 잘못된 데이터 재수집)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 카테고리를 하드코딩 대신 DB에서 조회하도록 변경 (GET /admin/logs/categories)
- 카테고리 칩을 체크박스 멀티셀렉트 드롭다운으로 교체
- 카테고리가 없을 때 드롭다운 비활성화
- DatePicker에 min/max/compact prop 추가 (날짜 범위 제한, 높이 통일)
- 날짜 선택 칸 너비 축소, 초기화 버튼 여백 축소
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12개 관리자 라우트와 3개 봇 서비스 파일에 활동 로그 기록 추가.
관리자 작업(일정/앨범/멤버/봇 CRUD)과 봇 동기화(완료/에러)를
logs 테이블에 fire-and-forget으로 기록.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
기존: 매 폴링마다 activities.list + videos.list (2 units)
변경: activities.list로 videoId 확인 후 DB에 없는 새 영상만 videos.list 호출
결과: 일일 API 사용량 약 50% 감소 (1분 간격 3채널도 가능)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DB 조회 시 WHERE enabled = 1 필터를 제거하여 비활성 봇도 시스템에서
인식되도록 변경. 이전에는 비활성 봇이 목록/검색에서 완전히 제외되어
재시작 불가 및 관리 UI에서 사라지는 문제가 있었음.
PUT 엔드포인트의 stopBot/startBot 조건 로직도 함께 정리.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- bot_x.sql에 누락된 컬럼 추가 (text_filters, include_retweets, extract_youtube)
- api.md에 X 봇 API 응답 스키마 및 필드 설명 추가
- architecture.md bot_x 테이블 설명 구체화
- development.md API 클라이언트 함수 목록 보완
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
X 봇 설정에서 트윗 내 YouTube 링크 자동 추출 기능을 온/오프 가능하게 함:
- bot_x 테이블에 extract_youtube 컬럼 추가 (기본값: false)
- 고급 설정에 "YouTube 영상 추출" 토글 추가
- extractYoutube가 true일 때만 YouTube 일정 자동 생성
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- syncAllSchedules()에서 Meilisearch의 모든 문서 ID 조회
- DB에 없는 문서는 Meilisearch에서 삭제
- 삭제된 일정이 검색에 계속 나타나는 문제 해결
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- x_profiles 테이블 삭제 (bot_x에 프로필 정보 포함)
- saveProfile(), getProfile() 함수가 bot_x 테이블 사용하도록 수정
- Redis 캐시는 그대로 유지 (성능)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- include_retweets 옵션으로 리트윗 포함 여부 설정 가능
- 고정된 트윗(pinned)은 기본적으로 파싱에서 제외
- XBotDialog에서 Twitter 아이콘을 X 아이콘으로 변경
- schedule_x의 username을 source_name으로 활용
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend:
- bot_x 테이블에 text_filters 컬럼 추가
- syncNewTweets/syncAllTweets에 텍스트 필터링 로직 적용
- 봇 추가 시 전체 트윗 동기화 수행 (백그라운드)
- X 봇 API에 text_filters 필드 처리
Frontend:
- XBotDialog에 고급 설정 (키워드 필터) UI 추가
- BotTableRow에서 X 봇 수정/삭제 버튼 활성화
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>