atrade/src/main/kotlin/network/NewsService.kt

194 lines
7.6 KiB
Kotlin
Raw Normal View History

2026-01-21 18:59:55 +09:00
package network
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.engine.cio.CIOEngineConfig
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
2026-04-02 15:22:38 +09:00
import io.ktor.client.request.forms.FormDataContent
2026-01-21 18:59:55 +09:00
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter
2026-04-02 15:22:38 +09:00
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText
2026-05-06 15:53:55 +09:00
import io.ktor.http.ContentType
2026-01-21 18:59:55 +09:00
import io.ktor.http.ContentType.Application.Json
2026-04-02 15:22:38 +09:00
import io.ktor.http.HttpHeaders
2026-05-06 15:53:55 +09:00
import io.ktor.http.HttpStatusCode
2026-04-02 15:22:38 +09:00
import io.ktor.http.Parameters
2026-02-09 15:32:31 +09:00
import io.ktor.http.Url
2026-05-06 15:53:55 +09:00
import io.ktor.http.contentType
import io.ktor.network.tls.TLSConfigBuilder
2026-01-21 18:59:55 +09:00
import io.ktor.serialization.kotlinx.json.json
2026-02-10 15:08:52 +09:00
import kotlinx.serialization.Serializable
2026-01-21 18:59:55 +09:00
import kotlinx.serialization.json.Json
2026-01-22 16:21:18 +09:00
import model.DartFinancialResponse
2026-03-13 11:06:20 +09:00
import model.KisSession
2026-01-21 18:59:55 +09:00
import model.NaverNewsResponse
2026-01-23 17:05:09 +09:00
import service.SafeScraper
import service.UrlCacheManager
2026-05-06 15:53:55 +09:00
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
import java.net.URLEncoder
2026-04-07 17:32:21 +09:00
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.Locale
2026-05-06 15:53:55 +09:00
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
2026-02-10 15:08:52 +09:00
import kotlin.Double
2026-01-21 18:59:55 +09:00
object NewsService {
private val client = HttpClient<CIOEngineConfig>(CIO) {
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true })
2026-01-23 17:05:09 +09:00
}
install(Logging) {
logger = Logger.DEFAULT
2026-04-29 15:32:09 +09:00
level = LogLevel.NONE
2026-01-21 18:59:55 +09:00
}
}
2026-01-23 17:05:09 +09:00
suspend fun fetchAndIngestNews(corpInfo: CorpInfo) {
2026-03-13 11:06:20 +09:00
val clientId = KisSession.config.nAppKey // 설정에서 가져오도록 수정 필요
val clientSecret = KisSession.config.nSecretKey
2026-02-09 15:32:31 +09:00
val qlistNews = listOf(
"${corpInfo.stockName} 주가",
"${corpInfo.stockName} 실적",
"${corpInfo.stockName} 공시",
2026-02-10 15:08:52 +09:00
// "${corpInfo.stockName} 이벤트"
2026-02-09 15:32:31 +09:00
)
val qlistCorpTrend = listOf(
"${corpInfo.cName} 최근 동향",
"${corpInfo.cName} 이슈",
2026-02-10 15:08:52 +09:00
// "${corpInfo.cName} 투자",
// "${corpInfo.cName} 실적"
2026-02-09 15:32:31 +09:00
)
(qlistNews + qlistCorpTrend).forEach { query ->
2026-01-23 17:05:09 +09:00
try {
2026-04-07 17:32:21 +09:00
val formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH)
val today = ZonedDateTime.now().toLocalDate() // 오늘 날짜 정보
2026-01-23 17:05:09 +09:00
val response: NaverNewsResponse = client.get("https://openapi.naver.com/v1/search/news.json") {
parameter("query", query)
2026-02-10 15:08:52 +09:00
parameter("display", 4) // 최근 10개 뉴스
2026-02-09 15:32:31 +09:00
parameter("sort", "date") // 유사도 순 (또는 date 발간순)
2026-01-23 17:05:09 +09:00
header("X-Naver-Client-Id", clientId)
header("X-Naver-Client-Secret", clientSecret)
}.body()
2026-04-07 17:32:21 +09:00
val todayItems = response.items.filter { item ->
try {
val pubDate = ZonedDateTime.parse(item.pubDate, formatter)
pubDate.toLocalDate() == today // 날짜가 오늘과 일치하는지 확인
} catch (e: Exception) {
false
}
}
// 중복 호스트 제거 및 최종 2건 선택
val finalItems = todayItems
.distinctBy { Url(it.originallink).host }
.take(2)
if (finalItems.isNotEmpty()) {
SafeScraper.scrapeParallel(corpInfo, finalItems)
}
2026-01-23 17:05:09 +09:00
} catch (e: Exception) {
println("❌ 뉴스 가져오기 실패: ${e.message}")
2026-01-21 18:59:55 +09:00
}
}
}
2026-01-22 16:21:18 +09:00
2026-04-02 15:22:38 +09:00
2026-01-22 16:21:18 +09:00
suspend fun fetchFinancialGrowth(corpCode: String?): String {
if (corpCode != null) {
2026-03-13 11:06:20 +09:00
val apiKey = KisSession.config.dAppKey
2026-01-22 16:21:18 +09:00
// 단일회사 주요계정 API (재무상태표, 손익계산서 주요 항목)
val url = "https://opendart.fss.or.kr/api/fnlttSinglAcnt.json?crtfc_key=$apiKey&corp_code=$corpCode&bsns_year=2024&reprt_code=11011"
return try {
val response = client.get(url).body<DartFinancialResponse>()
val accounts = response.list ?: return "재무 데이터 없음"
2026-01-23 17:05:09 +09:00
var buffer : StringBuffer = StringBuffer()
2026-02-10 15:08:52 +09:00
buffer.append("[재무 분석 데이터]").append("\n")
2026-01-23 17:05:09 +09:00
response.list.forEach { it
2026-03-26 13:48:26 +09:00
buffer.append("${it.account_nm} (당기)${it.thstrm_amount} (전기)${it.frmtrm_amount}").append("\n")
2026-01-23 17:05:09 +09:00
}
return buffer.toString()
2026-01-22 16:21:18 +09:00
} catch (e: Exception) {
"재무 API 연동 실패: ${e.message}"
}
} else {
return ""
}
}
2026-05-06 15:53:55 +09:00
suspend fun sendTelegramMessage(data: String) {
Thread {
try {
var chatId = KisSession.tradeConfig.tlg_id
println("sendTelegramMessage $chatId")
sendViaSystemCurl("https://lunaticbum.kr/tlg/sendToMe.bjx",chatId,data)
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
fun sendViaSystemCurl(url : String, chatId: String, message: String) {
try {
// 메시지 내 공백이나 한글이 깨지지 않도록 인코딩 (필수)
val encodedMessage = URLEncoder.encode(message, "UTF-8")
// OS 확인
val isWindows = System.getProperty("os.name").lowercase().contains("win")
val command = if (isWindows) {
// 윈도우용: 큰따옴표 이스케이프에 주의해야 합니다.
val jsonBody = "{\"id\":\"$chatId\",\"message\":\"$encodedMessage\"}"
listOf("cmd", "/c", "curl -s -X POST $url -H \"Content-Type: application/json\" -d \"$jsonBody\"")
} else {
// 맥/리눅스용: 홑따옴표를 사용하여 JSON 구조를 보호합니다.
val jsonBody = "{\"id\":\"$chatId\",\"message\":\"$message\"}"
listOf("curl", "-s", "-X", "POST", url, "-H", "Content-Type: application/json", "-d", jsonBody)
}
val process = ProcessBuilder(command)
.redirectErrorStream(true) // 에러 출력(stderr)을 표준 출력(stdout)으로 합침
.start()
// 프로세스의 출력을 읽어오는 블록
BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
val output = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
output.append(line).append("\n")
}
val exitCode = process.waitFor() // 프로세스가 종료될 때까지 대기
println("--- Telegram Curl Log Start ---")
println("Exit Code: $exitCode") // 0이면 성공, 그 외는 curl 에러 코드
println("Response:\n$output")
println("--- Telegram Curl Log End ---")
}
} catch (e: Exception) {
println("시스템 명령어 실행 중 예외 발생: ${e.message}")
e.printStackTrace()
}
}
2026-02-10 15:08:52 +09:00
}