Add profile & enhance command line

This commit is contained in:
2023-05-24 07:45:00 +09:00
parent a31896557d
commit 819fa792aa
6 changed files with 210 additions and 64 deletions

View File

@@ -265,66 +265,89 @@ public struct Contract {
public let orderDivision: OrderDivision
public let orderQuantity: Int
public let orderPrice: Int
public init(productNo: String, orderType: OrderType, orderDivision: OrderDivision, orderQuantity: Int, orderPrice: Int) {
self.productNo = productNo
self.orderType = orderType
self.orderDivision = orderDivision
self.orderQuantity = orderQuantity
self.orderPrice = orderPrice
}
}
// MARK: Stock Order
extension KissAccount {
public func orderStock(contract: Contract, completion: @escaping (Result<Bool, Error>) -> Void) {
guard let accessToken = accessToken else {
completion(.failure(GeneralError.invalidAccessToken))
return
}
let request = Domestic.StockOrderRequest(credential: credential, accessToken: accessToken, contract: contract)
request.query { result in
switch result {
case .success(let result):
completion(.success(true))
case .failure(let error):
completion(.failure(error))
public func orderStock(contract: Contract) async throws -> OrderResult {
return try await withUnsafeThrowingContinuation { continuation in
guard let accessToken = accessToken else {
continuation.resume(throwing: GeneralError.invalidAccessToken)
return
}
let request = Domestic.StockOrderRequest(credential: credential, accessToken: accessToken, contract: contract)
request.query { result in
switch result {
case .success(let result):
continuation.resume(returning: result)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
public func cancelOrder(completion: @escaping (Result<Bool, Error>) -> Void) {
guard let accessToken = accessToken else {
completion(.failure(GeneralError.invalidAccessToken))
return
public func cancelOrder() async throws -> Bool {
return try await withUnsafeThrowingContinuation { continuation in
guard let accessToken = accessToken else {
continuation.resume(throwing: GeneralError.invalidAccessToken)
return
}
// TODO: work
}
// TODO: work
}
public func changeOrder(completion: @escaping (Result<Bool, Error>) -> Void) {
guard let accessToken = accessToken else {
completion(.failure(GeneralError.invalidAccessToken))
return
public func changeOrder() async throws -> Bool {
return try await withUnsafeThrowingContinuation { continuation in
guard let accessToken = accessToken else {
continuation.resume(throwing: GeneralError.invalidAccessToken)
return
}
// TODO: work
}
// TODO: work
}
public func getStockBalance(completion: @escaping (Result<Bool, Error>) -> Void) {
guard let accessToken = accessToken else {
completion(.failure(GeneralError.invalidAccessToken))
return
public func getStockBalance() async throws -> Bool {
return try await withUnsafeThrowingContinuation { continuation in
guard let accessToken = accessToken else {
continuation.resume(throwing: GeneralError.invalidAccessToken)
return
}
// TODO: work
}
// TODO: work
}
public func canOrderStock(completion: @escaping (Result<Bool, Error>) -> Void) {
guard let accessToken = accessToken else {
completion(.failure(GeneralError.invalidAccessToken))
return
public func canOrderStock() async throws -> Bool {
return try await withUnsafeThrowingContinuation { continuation in
guard let accessToken = accessToken else {
continuation.resume(throwing: GeneralError.invalidAccessToken)
return
}
// TODO: work
}
// TODO: work
}
}

View File

@@ -12,13 +12,13 @@ public struct VolumeRankResult: Codable {
public let resultCode: String
public let messageCode: String
public let message: String
public let output1: [OutputDetail]?
public let output: [OutputDetail]?
private enum CodingKeys: String, CodingKey {
case resultCode = "rt_cd"
case messageCode = "msg_cd"
case message = "msg1"
case output1 = "Output1"
case output = "output"
}
public struct OutputDetail: Codable {

View File

@@ -11,18 +11,62 @@ import Foundation
public class KissAccount {
let credential: Credential
var profileLock = NSLock()
var profile = Profile()
var accessTokenLock = NSLock()
var accessToken: String?
var accessToken: String? {
profileLock.lock()
defer {
profileLock.unlock()
}
return profile.recent?.accessToken
}
public init(credential: Credential) {
self.credential = credential
self.accessToken = nil
// TODO: Profile
// accessToken
}
func setAccessToken(_ accessToken: String?) {
accessTokenLock.lock()
self.accessToken = accessToken
accessTokenLock.unlock()
func setAccessToken(_ accessToken: String, expired: Date) {
profileLock.lock()
defer {
profileLock.unlock()
}
profile.recent = Recent(accessToken: accessToken, accessTokenExpired: expired)
}
func resetAccessToken() {
profileLock.lock()
defer {
profileLock.unlock()
}
profile.recent = nil
}
}
extension KissAccount {
public class Profile: Codable {
public var recent: Recent?
public var loves: [Love]
init() {
recent = nil
loves = []
}
}
public struct Recent: Codable {
public let accessToken: String
public let accessTokenExpired: Date
}
public struct Love: Codable {
// TODO: write
}
}

View File

@@ -123,7 +123,7 @@ extension KissAccount {
request.query { result in
switch result {
case .success(let result):
self.setAccessToken(result.accessToken)
self.setAccessToken(result.accessToken, expired: result.accessTokenExpiredDate)
continuation.resume(returning: true)
case .failure(let error):
continuation.resume(throwing: error)
@@ -145,7 +145,7 @@ extension KissAccount {
switch result {
case .success(let result):
if result.code == 200 {
self.setAccessToken(nil)
self.resetAccessToken()
continuation.resume(returning: true)
}
else {

View File

@@ -10,14 +10,24 @@ import Foundation
public struct TokenResult: Codable {
public let accessToken: String
public let accessTokenExpired: String
public let tokenType: String
public let expiresIn: Int
private enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case accessTokenExpired = "access_token_token_expired"
case tokenType = "token_type"
case expiresIn = "expires_in"
}
public var accessTokenExpiredDate: Date {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: "Asia/Seoul")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = dateFormatter.date(from: accessTokenExpired)!
return date
}
}

View File

@@ -19,24 +19,47 @@ class KissConsole {
enum KissCommand: String {
case quit = "quit"
//
case loginMock = "login mock"
case loginReal = "login real"
case logout = "logout"
case search = "search"
case top = "top"
//
case buy = "buy"
case sell = "sell"
case cancel = "cancel"
//
case openBag = "open bag"
//
case loadShop = "load shop"
case updateShop = "update shop"
case look = "look"
// ( )
case showcase = "showcase"
//
case love = "love" // love nuts.1
case hate = "hate" // hate nuts.1
var needLogin: Bool {
switch self {
case .quit, .loginMock, .loginReal:
return false
case .logout, .search, .buy, .sell:
case .logout, .top, .buy, .sell, .cancel:
return true
case .loadShop, .updateShop, .look:
return false
case .showcase:
return false
case .love, .hate:
return false
case .openBag:
return false
}
}
}
@@ -51,10 +74,13 @@ class KissConsole {
createSubpath("log")
createSubpath("data")
onLoadShop()
Task {
await onLoadShop()
}
}
func getCommand(_ line: String) -> (KissCommand?, [String]) {
private func getCommand(_ line: String) -> (KissCommand?, [String]) {
let args = line.split(separator: " ")
let double = args.prefix(upTo: min(2, args.count)).joined(separator: " ")
if let cmd = KissCommand(rawValue: double) {
@@ -90,10 +116,11 @@ class KissConsole {
case .loginMock: await onLogin(isMock: true)
case .loginReal: await onLogin(isMock: false)
case .logout: await onLogout()
case .search: await onSearch(args)
case .top: await onTop(args)
case .buy: await onBuy(args)
case .sell: await onSell(args)
case .loadShop: onLoadShop()
case .cancel: await onCancel(args)
case .loadShop: await onLoadShop()
case .updateShop: await onUpdateShop()
case .look: await onLook(args)
default:
@@ -131,10 +158,10 @@ extension KissConsole {
productsLock.unlock()
}
return products.filter { $0.key.contains(similarName) }
// return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) }
//return products.filter { $0.key.decomposedStringWithCanonicalMapping.contains(similarName) }
}
private func loadShop(_ profile: Bool = true) {
private func loadShop(_ profile: Bool = false) {
let appTime1 = Date.appTime
guard let stringCsv = try? String(contentsOfFile: shopProducts.path) else {
return
@@ -206,11 +233,14 @@ extension KissConsole {
}
private func onSearch(_ arg: [String]) async {
private func onTop(_ arg: [String]) async {
let option = RankingOption(divisionClass: .all, belongClass: .averageVolume)
do {
_ = try await account?.getVolumeRanking(option: option)
let rank = try await account?.getVolumeRanking(option: option)
print("\(rank)")
// TODO: json csv table
} catch {
print("\(error)")
}
@@ -218,21 +248,58 @@ extension KissConsole {
private func onBuy(_ args: [String]) async {
// TODO: work
guard args.count == 3,
let price = Int(args[1]),
let quantity = Int(args[2]) else {
return
}
let productNo = args[0]
if price < 100 || quantity <= 0 {
print("Invalid price or quantity")
return
}
let contract = Contract(productNo: productNo,
orderType: .buy,
orderDivision: .limits,
orderQuantity: quantity, orderPrice: price)
do {
let result = try await account?.orderStock(contract: contract)
print(result)
} catch {
print("\(error)")
}
}
private func onSell(_ args: [String]) async {
// TODO: work
}
private func onLoadShop() {
DispatchQueue.global(qos: .default).async {
self.loadShop()
private func onCancel(_ args: [String]) async {
// TODO: work
do {
try await account?.cancelOrder() { result in
}
} catch {
print("\(error)")
}
}
private func onLoadShop() async {
return await withUnsafeContinuation { continuation in
self.loadShop()
continuation.resume()
}
}
private func onUpdateShop() async {
guard let _ = shop else {
print("Invalid shop instance")
@@ -294,20 +361,21 @@ extension KissConsole {
return shopItems
}
private func onLook(_ args: [String]) async {
guard args.count >= 1 else {
print("No target name")
return
}
let productName = String(args[0])
print(args, productName, "\(productName.count)")
//print(args, productName, "\(productName.count)")
guard let items = getProducts(similarName: productName), items.isEmpty == false else {
print("No products like \(productName)")
return
}
for item in items {
if let first = item.value.first {
print("\(first.shortCode) \(item.key.maxSpace(20)) \(first.marketCategory) \(first.baseDate)")
print("\(first.isinCode) \(item.key.maxSpace(20)) \(first.marketCategory) \(first.baseDate)")
}
}
}
@@ -323,6 +391,7 @@ private extension Array {
}
}
private extension String {
func maxSpace(_ length: Int) -> String {
let count = unicodeScalars.reduce(0) {