Files
KissMe/KissMeConsole/Sources/KissConsole.swift

818 lines
27 KiB
Swift
Raw Normal View History

//
// KissConsole.swift
// KissMeConsole
//
// Created by ened-book-m1 on 2023/05/17.
//
import Foundation
import KissMe
class KissConsole {
2023-05-29 09:13:44 +09:00
private var credential: Credential? = nil
private var account: KissAccount? = nil
private var shop: KissShop? = nil
2023-05-29 09:13:44 +09:00
private var productsLock = NSLock()
private var products = [String: [DomesticShop.Product]]()
private var currentShortCode: String?
2023-05-29 19:01:03 +09:00
private var currentCandleShortCode: String?
2023-05-21 13:51:31 +09:00
2023-05-29 09:13:44 +09:00
private enum KissCommand: String {
case quit = "quit"
2023-05-24 07:45:00 +09:00
//
case loginMock = "login mock"
case loginReal = "login real"
case logout = "logout"
2023-05-24 07:45:00 +09:00
case top = "top"
//
case buy = "buy"
2023-05-30 19:30:22 +09:00
case buyCheck = "buy check"
case sell = "sell"
2023-05-24 07:45:00 +09:00
case cancel = "cancel"
2023-05-30 19:30:22 +09:00
case modify = "modify"
2023-05-24 07:45:00 +09:00
//
case openBag = "open bag"
2023-05-27 22:53:34 +09:00
//
case now = "now"
case candle = "candle"
case candleAll = "candle all"
2023-05-27 22:53:34 +09:00
2023-05-24 07:45:00 +09:00
//
2023-05-19 01:17:09 +09:00
case loadShop = "load shop"
2023-05-21 13:51:31 +09:00
case updateShop = "update shop"
2023-05-21 15:11:10 +09:00
case look = "look"
2023-05-24 07:45:00 +09:00
// ( )
case showcase = "showcase"
//
2023-05-25 05:43:22 +09:00
case loves = "loves" //
case love = "love" // love nuts.1 ISCD
case hate = "hate" // hate nuts.1 ISCD
2023-05-24 07:45:00 +09:00
var needLogin: Bool {
switch self {
2023-05-19 01:17:09 +09:00
case .quit, .loginMock, .loginReal:
return false
2023-05-30 19:30:22 +09:00
case .logout, .top, .buy, .buyCheck, .sell, .cancel, .modify:
return true
2023-05-27 00:15:45 +09:00
case .openBag:
return true
case .now, .candle, .candleAll:
2023-05-27 22:53:34 +09:00
return true
2023-05-21 15:11:10 +09:00
case .loadShop, .updateShop, .look:
2023-05-19 01:17:09 +09:00
return false
2023-05-24 07:45:00 +09:00
case .showcase:
return false
2023-05-25 05:43:22 +09:00
case .loves, .love, .hate:
2023-05-24 07:45:00 +09:00
return false
}
}
}
2023-05-29 09:13:44 +09:00
private var isLogined: Bool {
account != nil
}
2023-05-19 01:17:09 +09:00
init() {
let jsonUrl = URL.currentDirectory().appending(path: "shop-server.json")
shop = try? KissShop(jsonUrl: jsonUrl)
2023-05-21 13:51:31 +09:00
createSubpath("log")
createSubpath("data")
2023-05-25 05:43:22 +09:00
lastLogin()
2023-05-30 19:30:22 +09:00
let semaphore = DispatchSemaphore(value: 0)
2023-05-24 07:45:00 +09:00
Task {
await onLoadShop()
semaphore.signal()
2023-05-24 07:45:00 +09:00
}
semaphore.wait()
2023-05-30 19:30:22 +09:00
// 005930:
setCurrent(productNo: "005930")
2023-05-21 13:51:31 +09:00
}
2023-05-24 07:45:00 +09:00
private func getCommand(_ line: String) -> (KissCommand?, [String]) {
2023-05-23 02:02:30 +09:00
let args = line.split(separator: " ")
let double = args.prefix(upTo: min(2, args.count)).joined(separator: " ")
if let cmd = KissCommand(rawValue: double) {
return (cmd, args.suffixStrings(from: 2))
}
let single = args.prefix(upTo: min(1, args.count)).joined(separator: " ")
if let cmd = KissCommand(rawValue: single) {
return (cmd, args.suffixStrings(from: 1))
}
return (nil, [])
}
2023-05-21 13:51:31 +09:00
func run() {
guard CommandLine.argc == 1 else {
let line = CommandLine.arguments.suffix(Int(CommandLine.argc)-1).joined(separator: " ")
let (cmd, args) = getCommand(line)
if cmd?.needLogin == true {
guard isLogined else {
print("Need to login")
exit(1)
}
}
let semaphore = DispatchSemaphore(value: 0)
Task {
let success = await run(command: cmd, args: args, line: line)
if !success {
exit(2)
}
semaphore.signal()
}
semaphore.wait()
return
}
2023-05-21 13:51:31 +09:00
print("Enter command:")
let semaphore = DispatchSemaphore(value: 0)
var loop = true
while loop {
guard let line = readLine(strippingNewline: true) else {
continue
}
2023-05-23 02:02:30 +09:00
let (cmd, args) = getCommand(line)
2023-05-21 13:51:31 +09:00
if cmd?.needLogin == true {
guard isLogined else {
print("Need to login")
continue
}
}
Task {
_ = await run(command: cmd, args: args, line: line)
2023-05-21 13:51:31 +09:00
semaphore.signal()
}
semaphore.wait()
loop = (cmd != .quit)
}
}
private func run(command: KissCommand?, args: [String], line: String) async -> Bool {
switch command {
case .quit: break
case .loginMock: await onLogin(isMock: true)
case .loginReal: await onLogin(isMock: false)
case .logout: await onLogout()
case .top: await onTop(args)
case .buy: await onBuy(args)
2023-05-30 19:30:22 +09:00
case .buyCheck: await onBuyCheck(args)
case .sell: await onSell(args)
case .cancel: await onCancel(args)
2023-05-30 19:30:22 +09:00
case .modify: await onModify(args)
case .openBag: await onOpenBag()
case .now: await onNow(args)
case .candle: await onCandle(args)
case .candleAll: onCancleAll()
case .loadShop: await onLoadShop()
case .updateShop: await onUpdateShop()
case .look: await onLook(args)
case .showcase: await onShowcase()
case .loves: await onLoves()
case .love: await onLove(args)
case .hate: await onHate(args)
default:
print("Unknown command: \(line)")
return false
}
return true
}
2023-05-21 13:51:31 +09:00
}
2023-05-21 12:16:26 +09:00
2023-05-21 13:51:31 +09:00
extension KissConsole {
private func createSubpath(_ name: String) {
let subPath = URL.currentDirectory().appending(path: name)
try? FileManager.default.createDirectory(at: subPath, withIntermediateDirectories: true)
}
2023-05-31 10:05:45 +09:00
func setProducts(_ products: [String: [DomesticShop.Product]]) {
2023-05-21 15:11:10 +09:00
productsLock.lock()
self.products = products
productsLock.unlock()
}
private func getProducts(similarName: String) -> [String: [DomesticShop.Product]]? {
productsLock.lock()
defer {
productsLock.unlock()
}
2023-05-23 09:35:54 +09:00
return products.filter { $0.key.contains(similarName) }
2023-05-24 07:45:00 +09:00
//return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) }
2023-05-21 15:11:10 +09:00
}
2023-05-27 22:53:34 +09:00
private func getProduct(isin: String) -> DomesticShop.Product? {
2023-05-27 08:20:56 +09:00
productsLock.lock()
defer {
productsLock.unlock()
}
2023-05-27 22:53:34 +09:00
return products.compactMap { $0.value.first(where: { $0.isinCode == isin }) }.first
}
private func getProduct(shortCode: String) -> DomesticShop.Product? {
productsLock.lock()
defer {
productsLock.unlock()
}
return products.compactMap { $0.value.first(where: { $0.shortCode == shortCode }) }.first
2023-05-27 08:20:56 +09:00
}
private func getAllProducts() -> [DomesticShop.Product] {
productsLock.lock()
defer {
productsLock.unlock()
}
var all = [DomesticShop.Product]()
for items in products.values {
all.append(contentsOf: items)
}
return all
}
2023-05-25 05:43:22 +09:00
private func lastLogin() {
let profile = KissProfile()
guard let isMock = profile.isMock else {
return
}
do {
2023-05-26 21:59:13 +09:00
let lastCredential = try KissCredential(isMock: isMock)
let lastAccount = KissAccount(credential: lastCredential)
guard let expiredAt = lastAccount.accessTokenExpiredDate else {
return
}
if expiredAt > Date() {
credential = lastCredential
account = lastAccount
let expiredAt = account?.accessTokenExpiredDate?.yyyyMMdd_HHmmss_forTime ?? ""
print("resume \(isMock ? "mock login": "real login") expired at \(expiredAt)")
}
2023-05-25 05:43:22 +09:00
} catch {
print("\(error)")
}
}
2023-05-30 19:30:22 +09:00
private func setCurrent(productNo: String) {
productsLock.lock()
currentShortCode = productNo
productsLock.unlock()
let productName = getProduct(shortCode: productNo)?.itemName ?? ""
print("current product \(productNo) \(productName)")
}
2023-05-21 13:51:31 +09:00
}
2023-05-29 15:49:39 +09:00
2023-05-21 13:51:31 +09:00
extension KissConsole {
2023-05-21 13:51:31 +09:00
private func onLogin(isMock: Bool) async {
guard !isLogined else {
print("Already loginged")
return
}
do {
credential = try KissCredential(isMock: isMock)
account = KissAccount(credential: credential!)
if try await account!.login() {
print("Success")
}
} catch {
print("\(error)")
}
}
2023-05-21 13:51:31 +09:00
private func onLogout() async {
do {
_ = try await account?.logout()
credential = nil
account = nil
2023-05-29 15:49:39 +09:00
print("Success")
} catch {
print("\(error)")
}
}
2023-05-24 07:45:00 +09:00
private func onTop(_ arg: [String]) async {
let option = RankingOption(divisionClass: .all, belongClass: .averageVolume)
do {
2023-05-26 21:59:13 +09:00
let rank = try await account!.getVolumeRanking(option: option)
guard let output = rank.output else {
print("Error \(rank.messageCode) \(rank.message)")
return
}
print("랭킹 단축상품코드 상품명 가격 평균거래량 누적거래대금")
for item in output {
print("\(item.dataRank) \(item.shortProductNo) \(item.htsProductName.maxSpace(20)) \(item.currentStockPrice.maxSpace(10, digitBy: 3)) \(item.averageVolume.maxSpace(15, digitBy: 3)) \(item.accumulatedTradingAmount.maxSpace(25, digitBy: 3))")
}
} catch {
print("\(error)")
}
}
2023-05-21 13:51:31 +09:00
private func onBuy(_ args: [String]) async {
2023-05-27 08:20:56 +09:00
guard args.count == 3 else {
2023-05-30 19:30:22 +09:00
print("Missing buy paramters: buy (PNO) (PRICE) (QUANTITY)")
2023-05-27 08:20:56 +09:00
return
}
2023-05-30 19:30:22 +09:00
let productNo = args[0]
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
2023-05-27 08:20:56 +09:00
return
}
2023-05-30 19:30:22 +09:00
guard let price = Int(args[1]), (price == -8282 || price >= 100) else {
print("Invalid price: \(args[1])")
2023-05-24 07:45:00 +09:00
return
}
2023-05-30 19:30:22 +09:00
guard let quantity = Int(args[2]), (quantity == -82 || quantity > 0) else {
print("Invalid quantity: \(args[2])")
2023-05-24 07:45:00 +09:00
return
}
2023-05-30 19:30:22 +09:00
let division: OrderDivision = (price == -8282 ? .marketPrice: .limits)
2023-05-24 07:45:00 +09:00
let contract = Contract(productNo: productNo,
orderType: .buy,
2023-05-30 19:30:22 +09:00
orderDivision: division,
2023-05-24 07:45:00 +09:00
orderQuantity: quantity, orderPrice: price)
do {
2023-05-30 19:30:22 +09:00
let result = try await account!.orderStock(contract: contract)
if let output = result.output {
print("Success \(product.itemName) orderNo: \(output.orderNo) at \(output.orderTime)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
} catch {
print("\(error)")
}
}
private func onBuyCheck(_ args: [String]) async {
guard args.count == 2 else {
print("Missing buy check paramters: buy check (PNO) (PRICE)")
return
}
let productNo = args[0]
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
return
}
guard let price = Int(args[1]), price >= 100 else {
print("Invalid price: \(args[1])")
return
}
do {
let result = try await account!.canOrderStock(productNo: productNo, division: .limits, price: price)
if let output = result.output {
print("Success \(product.itemName) \(output)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
2023-05-24 07:45:00 +09:00
} catch {
print("\(error)")
}
}
2023-05-21 13:51:31 +09:00
private func onSell(_ args: [String]) async {
2023-05-30 19:30:22 +09:00
guard args.count == 3 else {
print("Missing sell paramters: sell (PNO) (PRICE) (QUANTITY)")
return
}
let productNo = args[0]
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
return
}
guard let price = Int(args[1]), (price == -8282 || price >= 100) else {
print("Invalid price: \(args[1])")
return
}
guard let quantity = Int(args[2]), quantity > 0 else {
print("Invalid quantity: \(args[2])")
return
}
2023-05-24 07:45:00 +09:00
2023-05-30 19:30:22 +09:00
let division: OrderDivision = (price == -8282 ? .marketPrice: .limits)
let contract = Contract(productNo: productNo,
orderType: .sell,
orderDivision: division,
orderQuantity: quantity, orderPrice: price)
do {
let result = try await account!.orderStock(contract: contract)
if let output = result.output {
print("Success \(product.itemName) orderNo: \(output.orderNo) at \(output.orderTime)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
} catch {
print("\(error)")
}
}
2023-05-24 07:45:00 +09:00
private func onCancel(_ args: [String]) async {
2023-05-30 19:30:22 +09:00
guard args.count == 3 else {
print("Missing cancel paramters: cancel (PNO) (QUANTITY)")
return
}
let productNo = args[0]
guard let product = getProduct(shortCode: productNo) else {
print("No product \(productNo)")
return
}
let orderNo = args[1]
guard orderNo.count >= 1 else {
print("Invalid orderNo: \(args[1])")
return
}
guard let quantity = Int(args[2]), (quantity == -82 || quantity > 0) else {
print("Invalid quantity: \(args[2])")
return
}
2023-05-24 07:45:00 +09:00
do {
2023-05-30 19:30:22 +09:00
let cancel = ContractCancel(productNo: productNo,
orderNo: orderNo,
orderQuantity: (quantity == -82 ? 0: quantity))
let result = try await account!.cancelOrder(cancel: cancel)
if let output = result.output {
print("Success \(product.itemName) orderNo: \(output.orderNo) at \(output.orderTime)")
}
else {
print("Failed \(result.resultCode) \(result.messageCode) \(result.message)")
}
2023-05-24 07:45:00 +09:00
} catch {
print("\(error)")
}
}
2023-05-30 19:30:22 +09:00
private func onModify(_ args: [String]) async {
// TODO: work
}
2023-05-27 00:15:45 +09:00
private func onOpenBag() async {
do {
let result = try await account!.getStockBalance()
if let output = result.output1 {
for item in output {
print("\(item)")
}
}
if let output = result.output2 {
for item in output {
print("\(item)")
}
}
} catch {
print("\(error)")
}
}
2023-05-27 22:53:34 +09:00
private func onNow(_ args: [String]) async {
let productNo: String? = (args.isEmpty ? currentShortCode: args[0])
guard let productNo = productNo else {
print("Invalid productNo")
return
}
do {
let result = try await account!.getCurrentPrice(productNo: productNo)
if let output = result.output {
let productName = getProduct(shortCode: output.shortProductCode)?.itemName ?? ""
print("\t종목명: ", productName)
print("\t업종명: ", output.koreanMarketName, output.koreanBusinessTypeName)
print("\t주식 현재가: ", output.currentStockPrice)
print("\t전일 대비: ", output.previousDayVariableRatio)
print("\t누적 거래 대금: ", output.accumulatedTradingAmount)
print("\t누적 거래량: ", output.accumulatedVolume)
print("\t전일 대비 거래량 비율: ", output.previousDayDiffVolumeRatio)
print("\t주식 시가: ", output.stockPrice)
print("\t주식 최고가: ", output.highestStockPrice)
print("\t주식 최저가: ", output.lowestStockPrice)
print("\t외국인 순매수 수량: ", output.foreignNetBuyingQuantity)
print("\t외국인 보유 수량: ", output.foreignHoldQuantity)
print("\t최종 공매도 체결 수량: ", output.lastShortSellingConclusionQuantity)
print("\t프로그램매매 순매수 수량: ", output.programTradeNetBuyingQuantity)
print("\t자본금: ", output.capital)
print("\t상장 주수: ", output.listedStockCount)
print("\tHTS 시가총액: ", output.htsTotalMarketValue)
print("\tPER: ", output.per)
print("\tPBR: ", output.pbr)
print("\t주식 단축 종목코드", output.shortProductCode)
2023-05-30 19:30:22 +09:00
setCurrent(productNo: output.shortProductCode)
2023-05-27 22:53:34 +09:00
}
} catch {
print("\(error)")
}
}
private func onCancleAll() {
let all = getAllProducts()
for item in all {
let semaphore = DispatchSemaphore(value: 0)
Task {
let success = await getCandle(productNo: item.shortCode)
print("DONE \(success) \(item.shortCode)")
semaphore.signal()
}
semaphore.wait()
}
}
2023-05-27 22:53:34 +09:00
private func onCandle(_ args: [String]) async {
2023-05-28 16:08:49 +09:00
let productNo: String? = (args.isEmpty ? currentShortCode: args[0])
guard let productNo = productNo else {
print("Invalid productNo")
return
}
_ = await getCandle(productNo: productNo)
}
private func getCandle(productNo: String) async -> Bool {
do {
guard currentCandleShortCode == nil else {
print("Already candle collecting")
return false
}
currentCandleShortCode = productNo
defer {
currentCandleShortCode = nil
}
var nextTime = Date()
//nextTime.change(hour: 17, min: 0, sec: 0)
//nextTime.change(year: 2023, month: 5, day: 26)
//nextTime.change(hour: 9, min: 1, sec: 0)
2023-05-31 10:05:45 +09:00
var candles = [Domestic.Candle]()
var count = 0
while true {
let more = (count > 0)
count += 1
print("minute price \(productNo) from \(nextTime.yyyyMMdd_HHmmss_forTime) \(more)")
let result = try await account!.getMinutePrice(productNo: productNo, startTodayTime: nextTime, more: more)
2023-05-29 15:49:39 +09:00
if let prices = result.output2, prices.isEmpty == false {
candles.append(contentsOf: prices)
if let last = prices.last {
if nextTime.yyyyMMdd != last.stockBusinessDate {
if let (yyyy, mm, dd) = last.stockBusinessDate.yyyyMMdd {
print("next: \(last.stockBusinessDate)")
nextTime.change(year: yyyy, month: mm, day: dd)
2023-05-29 19:01:03 +09:00
}
}
if let (hh, mm, ss) = last.stockConclusionTime.HHmmss {
print("next: \(last.stockConclusionTime) / \(hh) \(mm) \(ss)")
nextTime.change(hour: hh, min: mm-1, sec: ss)
if hh == 9, mm == 0, ss == 0 {
print("minute price finished")
break
2023-05-29 19:01:03 +09:00
}
2023-05-29 15:49:39 +09:00
}
}
/// Limit to request queries with 5 tps
try await Task.sleep(nanoseconds: 200_000_000)
2023-05-29 15:49:39 +09:00
}
else {
print("minute price finished")
break
2023-05-29 19:01:03 +09:00
}
2023-05-28 16:08:49 +09:00
}
candles.sort(by: { $0.stockBusinessDate < $1.stockBusinessDate })
guard let minTime = candles.first?.stockBusinessDate else {
print("No price items")
return false
}
let subPath = "data/\(productNo)"
let subFile = "\(subPath)/candle-\(minTime).csv"
let fileUrl = URL.currentDirectory().appending(path: subFile)
createSubpath(subPath)
2023-05-31 10:05:45 +09:00
KissConsole.writeCandle(candles, fileUrl: fileUrl)
return true
} catch {
print("\(error)")
return false
2023-05-28 16:08:49 +09:00
}
2023-05-27 22:53:34 +09:00
}
2023-05-24 07:45:00 +09:00
private func onLoadShop() async {
return await withUnsafeContinuation { continuation in
2023-05-21 13:51:31 +09:00
self.loadShop()
2023-05-24 07:45:00 +09:00
continuation.resume()
2023-05-21 13:51:31 +09:00
}
}
2023-05-24 07:45:00 +09:00
2023-05-21 13:51:31 +09:00
private func onUpdateShop() async {
2023-05-23 02:02:30 +09:00
guard let _ = shop else {
2023-05-21 13:51:31 +09:00
print("Invalid shop instance")
return
}
2023-05-23 02:02:30 +09:00
var baseDate = Date()
var shopItems = [DomesticShop.Product]()
2023-05-21 12:16:26 +09:00
2023-05-23 02:02:30 +09:00
for _ in 0 ..< 7 {
print("try to get shop at date \(baseDate.yyyyMMdd)")
shopItems = await getAllProduct(baseDate: baseDate)
if shopItems.isEmpty {
let oneDaySeconds: TimeInterval = 60 * 60 * 24
baseDate = baseDate.addingTimeInterval(-oneDaySeconds)
}
else {
break
2023-05-21 12:16:26 +09:00
}
2023-05-19 01:17:09 +09:00
}
2023-05-31 10:05:45 +09:00
KissConsole.writeShop(shopItems, fileUrl: shopProductsUrl)
}
2023-05-21 15:11:10 +09:00
2023-05-23 02:02:30 +09:00
private func getAllProduct(baseDate: Date) async -> [DomesticShop.Product] {
var pageNo = 0
var shopItems = [DomesticShop.Product]()
do {
while true {
pageNo += 1
print("get pageNo: \(pageNo)")
let (totalCount, items) = try await shop!.getProduct(baseDate: baseDate, pageNo: pageNo)
shopItems.append(contentsOf: items)
print("got pageNo: \(pageNo) - \(shopItems.count)/\(totalCount)")
if totalCount == 0 || shopItems.count >= totalCount {
break
}
}
} catch {
print("\(error)")
}
return shopItems
}
2023-05-24 07:45:00 +09:00
2023-05-21 15:11:10 +09:00
private func onLook(_ args: [String]) async {
guard args.count >= 1 else {
2023-05-23 02:02:30 +09:00
print("No target name")
2023-05-21 15:11:10 +09:00
return
}
2023-05-23 02:02:30 +09:00
let productName = String(args[0])
2023-05-24 07:45:00 +09:00
//print(args, productName, "\(productName.count)")
2023-05-23 02:02:30 +09:00
guard let items = getProducts(similarName: productName), items.isEmpty == false else {
print("No products like \(productName)")
2023-05-21 15:11:10 +09:00
return
}
for item in items {
2023-05-23 09:35:54 +09:00
if let first = item.value.first {
2023-05-27 22:53:34 +09:00
print("\tISIN: ", first.isinCode)
print("\t상품명: ", item.key.maxSpace(20))
print("\t단축코드: ", first.shortCode)
print("\t시장구분: ", first.marketCategory, "\t기준일자: ", first.baseDate)
2023-05-30 19:30:22 +09:00
setCurrent(productNo: first.shortCode)
2023-05-21 15:11:10 +09:00
}
}
}
2023-05-27 00:15:45 +09:00
private func onShowcase() async {
// TODO: write
}
private func onLoves() async {
2023-05-27 08:20:56 +09:00
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)
}
2023-05-27 00:15:45 +09:00
}
2023-05-27 08:20:56 +09:00
private func printLoveTab(_ key: String, loves: [KissProfile.Love]) {
print("Page: \(key)")
for item in loves {
print(" \(item.isin) \(item.name)")
}
}
2023-05-27 00:15:45 +09:00
2023-05-29 15:49:39 +09:00
2023-05-27 00:15:45 +09:00
private func onLove(_ args: [String]) async {
2023-05-27 08:20:56 +09:00
guard let account = account else { return }
guard args.count > 0 else {
print("Invalid love\nlove ")
return
}
2023-05-27 00:15:45 +09:00
2023-05-27 08:20:56 +09:00
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 {
2023-05-27 22:53:34 +09:00
guard let product = getProduct(isin: isin) else {
2023-05-27 08:20:56 +09:00
print("No product about isin: \(isin)")
return
}
2023-05-27 22:53:34 +09:00
if account.setLove(KissProfile.Love(isin: isin, name: product.itemName), index: index, for: key) {
print("Success \(product.itemName)")
2023-05-27 08:20:56 +09:00
account.saveProfile()
2023-05-30 19:30:22 +09:00
setCurrent(productNo: product.shortCode)
2023-05-27 08:20:56 +09:00
}
else {
print("Invalid index: \(index) for \(key)")
}
}
}
}
else {
/// key Love
///
2023-05-27 22:53:34 +09:00
guard let product = getProduct(isin: isin) else {
2023-05-27 08:20:56 +09:00
print("No product about isin: \(isin)")
return
}
2023-05-27 22:53:34 +09:00
account.addLove(KissProfile.Love(isin: isin, name: product.itemName), for: keys[0])
print("Success \(product.itemName)")
2023-05-27 08:20:56 +09:00
account.saveProfile()
2023-05-30 19:30:22 +09:00
setCurrent(productNo: product.shortCode)
2023-05-27 08:20:56 +09:00
}
}
2023-05-27 00:15:45 +09:00
}
private func onHate(_ args: [String]) async {
2023-05-27 08:20:56 +09:00
guard let account = account else { return }
guard args.count > 1 else {
print("Invalid hate")
return
}
2023-05-27 00:15:45 +09:00
2023-05-27 08:20:56 +09:00
let key = args[0]
let isin = args[1]
if let love = account.removeLove(isin: isin, for: key) {
print("Success \(love.name)")
}
2023-05-27 00:15:45 +09:00
}
}
2023-05-21 12:16:26 +09:00
private extension Array {
func suffixStrings(from: Int) -> [String] where Element == String.SubSequence {
2023-05-23 02:02:30 +09:00
guard from < count else {
return []
}
return suffix(from: from).map { String($0) }
}
}