194 lines
7.6 KiB
Kotlin
194 lines
7.6 KiB
Kotlin
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
|
|
import io.ktor.client.request.forms.FormDataContent
|
|
import io.ktor.client.request.get
|
|
import io.ktor.client.request.header
|
|
import io.ktor.client.request.parameter
|
|
import io.ktor.client.request.post
|
|
import io.ktor.client.request.setBody
|
|
import io.ktor.client.statement.HttpResponse
|
|
import io.ktor.client.statement.bodyAsText
|
|
import io.ktor.http.ContentType
|
|
import io.ktor.http.ContentType.Application.Json
|
|
import io.ktor.http.HttpHeaders
|
|
import io.ktor.http.HttpStatusCode
|
|
import io.ktor.http.Parameters
|
|
import io.ktor.http.Url
|
|
import io.ktor.http.contentType
|
|
import io.ktor.network.tls.TLSConfigBuilder
|
|
import io.ktor.serialization.kotlinx.json.json
|
|
import kotlinx.serialization.Serializable
|
|
import kotlinx.serialization.json.Json
|
|
import model.DartFinancialResponse
|
|
import model.KisSession
|
|
import model.NaverNewsResponse
|
|
import service.SafeScraper
|
|
import service.UrlCacheManager
|
|
import java.io.BufferedReader
|
|
import java.io.InputStreamReader
|
|
import java.net.URL
|
|
import java.net.URLEncoder
|
|
import java.time.ZonedDateTime
|
|
import java.time.format.DateTimeFormatter
|
|
import java.time.temporal.ChronoUnit
|
|
import java.util.Locale
|
|
import javax.net.ssl.HttpsURLConnection
|
|
import javax.net.ssl.SSLContext
|
|
import kotlin.Double
|
|
|
|
object NewsService {
|
|
private val client = HttpClient<CIOEngineConfig>(CIO) {
|
|
|
|
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true })
|
|
|
|
}
|
|
install(Logging) {
|
|
logger = Logger.DEFAULT
|
|
level = LogLevel.NONE
|
|
}
|
|
}
|
|
|
|
suspend fun fetchAndIngestNews(corpInfo: CorpInfo) {
|
|
val clientId = KisSession.config.nAppKey // 설정에서 가져오도록 수정 필요
|
|
val clientSecret = KisSession.config.nSecretKey
|
|
val qlistNews = listOf(
|
|
"${corpInfo.stockName} 주가",
|
|
"${corpInfo.stockName} 실적",
|
|
"${corpInfo.stockName} 공시",
|
|
// "${corpInfo.stockName} 이벤트"
|
|
)
|
|
|
|
val qlistCorpTrend = listOf(
|
|
"${corpInfo.cName} 최근 동향",
|
|
"${corpInfo.cName} 이슈",
|
|
// "${corpInfo.cName} 투자",
|
|
// "${corpInfo.cName} 실적"
|
|
)
|
|
(qlistNews + qlistCorpTrend).forEach { query ->
|
|
try {
|
|
val formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH)
|
|
val today = ZonedDateTime.now().toLocalDate() // 오늘 날짜 정보
|
|
|
|
val response: NaverNewsResponse = client.get("https://openapi.naver.com/v1/search/news.json") {
|
|
parameter("query", query)
|
|
parameter("display", 4) // 최근 10개 뉴스
|
|
parameter("sort", "date") // 유사도 순 (또는 date 발간순)
|
|
header("X-Naver-Client-Id", clientId)
|
|
header("X-Naver-Client-Secret", clientSecret)
|
|
}.body()
|
|
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)
|
|
}
|
|
} catch (e: Exception) {
|
|
println("❌ 뉴스 가져오기 실패: ${e.message}")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
suspend fun fetchFinancialGrowth(corpCode: String?): String {
|
|
if (corpCode != null) {
|
|
val apiKey = KisSession.config.dAppKey
|
|
// 단일회사 주요계정 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 "재무 데이터 없음"
|
|
var buffer : StringBuffer = StringBuffer()
|
|
buffer.append("[재무 분석 데이터]").append("\n")
|
|
response.list.forEach { it
|
|
buffer.append("${it.account_nm} (당기)${it.thstrm_amount} (전기)${it.frmtrm_amount}").append("\n")
|
|
}
|
|
return buffer.toString()
|
|
} catch (e: Exception) {
|
|
"재무 API 연동 실패: ${e.message}"
|
|
}
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
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\":\"$encodedMessage\"}"
|
|
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()
|
|
}
|
|
}
|
|
|
|
}
|