From 07dfaaf71bf4c1d4b0847ee11d0541e477e9628c Mon Sep 17 00:00:00 2001 From: hyoseung930 <35983843+hyoseung930@users.noreply.github.com> Date: Fri, 8 May 2026 10:27:33 +0900 Subject: [PATCH] New task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 서버 정상 동작 확인! 이제 Reddit 앱 발급이 필요해요. --- ## Reddit 앱 발급 (2분) **1단계**: https://www.reddit.com/prefs/apps 접속 → **"are you a developer? create an app..."** 클릭 **2단계** 양식 입력: - **name**: `WosCouponBot` - **type**: `script` 선택 - **redirect uri**: `http://localhost` (아무거나 OK) **3단계** 생성 후 표시되는 값 복사: ``` client_id: 앱 이름 아래 짧은 문자열 (14자리) client_secret: "secret" 옆 긴 문자열 ``` **4단계** EC2 서버 `.env`에 추가: ```bash ssh -i ~/Desktop/hyoseung.pem ubuntu@3.34.1.212 echo "REDDIT_CLIENT_ID=여기" >> ~/autoCoupon/back/.env echo "REDDIT_CLIENT_SECRET=여기" >> ~/autoCoupon/back/.env ``` **5단계** 서버 재시작 후 테스트: ```bash kill $(ps aux | grep 'autoCoupon' | grep node | awk '{print $2}') cd ~/autoCoupon/back && nohup node dist/main.js >> ~/autoCoupon/back.log 2>&1 & curl -X POST http://localhost:3002/api/crawler/trigger ``` Reddit 앱 만들면 바로 테스트해볼 수 있어요! --- back/.env.example | 2 ++ back/src/crawler/crawler.service.ts | 45 ++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/back/.env.example b/back/.env.example index 9eeda6c..1d90ced 100644 --- a/back/.env.example +++ b/back/.env.example @@ -5,3 +5,5 @@ DB_PASSWORD=486251daKWON@ DB_DATABASE=kakao APP_PORT=3000 DISCORD_WEBHOOK_URL= +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= diff --git a/back/src/crawler/crawler.service.ts b/back/src/crawler/crawler.service.ts index 00b6611..c5c9148 100644 --- a/back/src/crawler/crawler.service.ts +++ b/back/src/crawler/crawler.service.ts @@ -6,8 +6,8 @@ import axios from 'axios'; import { WosCoupon } from '../entities/wos-coupon.entity'; import { WosService } from '../wos/wos.service'; -const REDDIT_SEARCH_URL = - 'https://www.reddit.com/r/WhiteoutSurvival/search.json?q=gift+code+OR+redeem+code+OR+coupon&sort=new&restrict_sr=1&limit=25&t=week'; +const REDDIT_OAUTH_URL = 'https://oauth.reddit.com/r/WhiteoutSurvival/search.json?q=gift+code+OR+redeem+code+OR+coupon&sort=new&restrict_sr=1&limit=25&t=week'; +const REDDIT_TOKEN_URL = 'https://www.reddit.com/api/v1/access_token'; const COMMON_WORDS = new Set([ 'THE', 'AND', 'FOR', 'ARE', 'BUT', 'NOT', 'YOU', 'ALL', 'ANY', 'CAN', @@ -33,6 +33,8 @@ const COMMON_WORDS = new Set([ export class CrawlerService { private readonly logger = new Logger(CrawlerService.name); private seenPostIds = new Set(); + private redditAccessToken: string | null = null; + private redditTokenExpiry = 0; constructor( @InjectRepository(WosCoupon) @@ -40,6 +42,34 @@ export class CrawlerService { private readonly wosService: WosService, ) {} + private async getRedditToken(): Promise { + const clientId = process.env.REDDIT_CLIENT_ID; + const clientSecret = process.env.REDDIT_CLIENT_SECRET; + if (!clientId || !clientSecret) return null; + + if (this.redditAccessToken && Date.now() < this.redditTokenExpiry) { + return this.redditAccessToken; + } + + try { + const resp = await axios.post( + REDDIT_TOKEN_URL, + new URLSearchParams({ grant_type: 'client_credentials' }), + { + auth: { username: clientId, password: clientSecret }, + headers: { 'User-Agent': 'WosCouponBot/1.0 by wageulwageul' }, + timeout: 10000, + }, + ); + this.redditAccessToken = resp.data.access_token; + this.redditTokenExpiry = Date.now() + (resp.data.expires_in - 60) * 1000; + return this.redditAccessToken; + } catch (err: any) { + this.logger.error(`Reddit 토큰 발급 실패: ${err.message}`); + return null; + } + } + private extractCodes(text: string): string[] { const codePattern = /\b([A-Z][A-Z0-9]{4,19})\b/g; const found = new Set(); @@ -97,9 +127,16 @@ export class CrawlerService { let posts: any[] = []; try { - const resp = await axios.get(REDDIT_SEARCH_URL, { + const token = await this.getRedditToken(); + if (!token) { + this.logger.warn('Reddit OAuth 토큰 없음 - REDDIT_CLIENT_ID/SECRET 환경변수를 설정하세요'); + return { found: 0, added: [] }; + } + + const resp = await axios.get(REDDIT_OAUTH_URL, { headers: { - 'User-Agent': 'WosCouponBot/1.0', + 'Authorization': `Bearer ${token}`, + 'User-Agent': 'WosCouponBot/1.0 by wageulwageul', 'Accept': 'application/json', }, timeout: 10000,