From 3740b38fc84de0178fdfed537da1036d2b0e4aaa Mon Sep 17 00:00:00 2001 From: ened Date: Sat, 27 May 2023 08:20:56 +0900 Subject: [PATCH] Implement love command --- KissMe/Common/KissExtensions.swift | 7 -- KissMe/Domestic/DomesticStockPrice.swift | 4 +- KissMe/Foreign/ForeignFutures.swift | 2 +- KissMe/Foreign/ForeignStock.swift | 2 +- KissMe/Foreign/ForeignStockPrice.swift | 2 +- KissMe/KissProfile.swift | 44 +++++++-- KissMeConsole/KissMeConsole/KissConsole.swift | 89 ++++++++++++++++++- README.md | 12 +-- 8 files changed, 134 insertions(+), 28 deletions(-) diff --git a/KissMe/Common/KissExtensions.swift b/KissMe/Common/KissExtensions.swift index 2690d90..9dbec87 100644 --- a/KissMe/Common/KissExtensions.swift +++ b/KissMe/Common/KissExtensions.swift @@ -31,10 +31,3 @@ extension Date { ProcessInfo.processInfo.systemUptime } } - - -extension Array { - func write(toCSV: URL) where Element == Encodable { - // TODO: later - } -} diff --git a/KissMe/Domestic/DomesticStockPrice.swift b/KissMe/Domestic/DomesticStockPrice.swift index 05f00cc..a1972db 100644 --- a/KissMe/Domestic/DomesticStockPrice.swift +++ b/KissMe/Domestic/DomesticStockPrice.swift @@ -113,7 +113,7 @@ extension KissAccount { return } - // TODO: work + // TODO: 현재 시세 정보를 가져오기 } @@ -123,6 +123,6 @@ extension KissAccount { return } - // TODO: work + // TODO: 분봉 정보를 가져오기 } } diff --git a/KissMe/Foreign/ForeignFutures.swift b/KissMe/Foreign/ForeignFutures.swift index d188de3..f473ebd 100644 --- a/KissMe/Foreign/ForeignFutures.swift +++ b/KissMe/Foreign/ForeignFutures.swift @@ -7,4 +7,4 @@ import Foundation -// TODO: WORK +// TODO: 선물은 나중에... diff --git a/KissMe/Foreign/ForeignStock.swift b/KissMe/Foreign/ForeignStock.swift index 7c7993c..0e11344 100644 --- a/KissMe/Foreign/ForeignStock.swift +++ b/KissMe/Foreign/ForeignStock.swift @@ -7,4 +7,4 @@ import Foundation -// TODO: WORK +// TODO: 해외주식은 나중에... diff --git a/KissMe/Foreign/ForeignStockPrice.swift b/KissMe/Foreign/ForeignStockPrice.swift index ac38f6e..4315c87 100644 --- a/KissMe/Foreign/ForeignStockPrice.swift +++ b/KissMe/Foreign/ForeignStockPrice.swift @@ -7,4 +7,4 @@ import Foundation -// TODO: WORK +// TODO: 해외주식은 나중에... diff --git a/KissMe/KissProfile.swift b/KissMe/KissProfile.swift index 5cdd823..d806bfd 100644 --- a/KissMe/KissProfile.swift +++ b/KissMe/KissProfile.swift @@ -78,9 +78,13 @@ extension KissProfile { } public struct Love: Codable { - let key: String - let iscd: String - let name: String + public let isin: String + public let name: String + + public init(isin: String, name: String) { + self.isin = isin + self.name = name + } } private var profileJsonUrl: URL { @@ -126,16 +130,44 @@ extension KissProfile { } } - public func removeLove(_ love: Love, for key: String) -> Bool { + public func removeLove(isin: String, for key: String) -> KissProfile.Love? { profileLock.lock() defer { profileLock.unlock() } - if let index = profile.loves[key]?.firstIndex(where: { $0.iscd == love.iscd }) { - profile.loves[key]?.remove(at: index) + if let index = profile.loves[key]?.firstIndex(where: { $0.isin == isin }) { + return profile.loves[key]?.remove(at: index) + } + return nil + } + + public func setLove(_ love: Love, index: Int, for key: String) -> Bool { + profileLock.lock() + defer { + profileLock.unlock() + } + + if let array = profile.loves[key], index < array.count { + profile.loves[key]?[index] = love return true } return false } + + public func getLoves() -> [String: [Love]] { + profileLock.lock() + defer { + profileLock.unlock() + } + return profile.loves + } + + public func getLoves(for key: String) -> [Love]? { + profileLock.lock() + defer { + profileLock.unlock() + } + return profile.loves[key] + } } diff --git a/KissMeConsole/KissMeConsole/KissConsole.swift b/KissMeConsole/KissMeConsole/KissConsole.swift index db113a2..fc757da 100644 --- a/KissMeConsole/KissMeConsole/KissConsole.swift +++ b/KissMeConsole/KissMeConsole/KissConsole.swift @@ -172,6 +172,15 @@ extension KissConsole { //return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) } } + private func getProductName(isin: String) -> String? { + productsLock.lock() + defer { + productsLock.unlock() + } + + return products.compactMap { $0.value.first(where: { $0.isinCode == isin })?.itemName }.first + } + private func loadShop(_ profile: Bool = false) { let appTime1 = Date.appTime guard let stringCsv = try? String(contentsOfFile: shopProducts.path) else { @@ -290,9 +299,13 @@ extension KissConsole { private func onBuy(_ args: [String]) async { - guard args.count == 3, - let price = Int(args[1]), - let quantity = Int(args[2]) else { + guard args.count == 3 else { + return + } + guard let price = Int(args[1]) else { + return + } + guard let quantity = Int(args[2]) else { return } @@ -308,6 +321,8 @@ extension KissConsole { orderQuantity: quantity, orderPrice: price) do { let result = try await account?.orderStock(contract: contract) + + // TODO: 매수처리 이후 print(result) } catch { print("\(error)") @@ -446,17 +461,83 @@ extension KissConsole { private func onLoves() async { - + guard let account = account else { return } + let loves = account.getLoves() + for tab in loves.sorted(by: { $0.key < $1.key }) { + printLoveTab(tab.key, loves: tab.value) + } } + private func printLoveTab(_ key: String, loves: [KissProfile.Love]) { + print("Page: \(key)") + for item in loves { + print(" \(item.isin) \(item.name)") + } + } private func onLove(_ args: [String]) async { + guard let account = account else { return } + guard args.count > 0 else { + print("Invalid love\nlove ") + return + } + if args.count == 1 { + let key = args[0] + guard let loves = account.getLoves(for: key) else { return } + printLoveTab(key, loves: loves) + } + else if args.count == 2 { + let keys = args[0].split(separator: ".").map { String($0) } + let isin = args[1] + if keys.count == 2 { + /// key 탭의 index 위치에 Love 를 추가 + /// + let key = keys[0] + let index = keys[1] + if let loves = account.getLoves(for: key), let index = Int(index) { + if index < loves.count { + guard let name = getProductName(isin: isin) else { + print("No product about isin: \(isin)") + return + } + if account.setLove(KissProfile.Love(isin: isin, name: name), index: index, for: key) { + print("Success \(name)") + account.saveProfile() + } + else { + print("Invalid index: \(index) for \(key)") + } + } + } + } + else { + /// key 탭의 맨뒤에 Love 를 추가 + /// + guard let name = getProductName(isin: isin) else { + print("No product about isin: \(isin)") + return + } + account.addLove(KissProfile.Love(isin: isin, name: name), for: keys[0]) + print("Success \(name)") + account.saveProfile() + } + } } private func onHate(_ args: [String]) async { + guard let account = account else { return } + guard args.count > 1 else { + print("Invalid hate") + return + } + let key = args[0] + let isin = args[1] + if let love = account.removeLove(isin: isin, for: key) { + print("Success \(love.name)") + } } } diff --git a/README.md b/README.md index ca5a03b..e510525 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ KissMeConsole 에서 유효한 command line 명령어는 다음과 같습니다. command | 설명 ------- | --- `quit` | 종료 -`login mock` | Mock 서버로 로그인, mock-server.json 을 credential 로 사용. -`login real` | Real 서버로 로그인, real-server.json 을 credential 로 사용. +`login mock` | Mock 서버로 로그인. mock-server.json 을 credential 로 사용. +`login real` | Real 서버로 로그인. real-server.json 을 credential 로 사용. `logout` | 접속한 서버에서 로그아웃 `top` | 상위 거래량 30종목 (평균거래량) WIP `buy (ISCD) (수량)` | 구매 @@ -22,12 +22,12 @@ WIP `sell (ISCD) (수량)` | 판매 WIP `cancel (ISCD)` | 주문 취소 `open bag` | 보유 종목 열람 `load shop` | data/shop-products.csv 로부터 전체 상품을 로딩 -`update shop` | 금융위원회_KRX상장종목정보 로부터 전체 상품을 얻어서 data/shop-products.csv 로 저장 +`update shop` | **금융위원회_KRX상장종목정보** 로부터 전체 상품을 얻어서 data/shop-products.csv 로 저장 `look (상품명)` | (상품명) 에 해당되는 ISCD 를 표시함 WIP `showcase` | 추천 상품을 제안함 -WIP `loves` | 관심 종목 전체를 열람. profile.json 에 저장된 관심 종목을 표시함. -WIP `love (탭).(번호) (ISCD)` | 관심 종목에 추가함. (번호) 를 지정하지 않으면 (탭) 마지막에 추가함. -WIP `hate (탭).(번호) (ISCD)` | 관심 종목에서 삭제함. +`loves` | 관심 종목 전체를 열람. profile.json 에 저장된 관심 종목을 표시함. +`love (탭).(번호) (ISCD)` | 관심 종목에 추가함. (번호) 를 지정하지 않으면 (탭) 마지막에 추가함. +`hate (탭) (ISCD)` | 관심 종목에서 삭제함. # KissCredential