add Player model
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "01eb28d6d5d4bfc205f7c8b2f8a3dc3a3d278b26c75f239cbd1cf26e4482e33d",
|
||||
"originHash" : "48b60a6f8caccb2179aca6ee731ee84b313e8f73da96743e41514dc5bf29a68e",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "async-http-client",
|
||||
@@ -217,6 +217,15 @@
|
||||
"version" : "2.7.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-msgpack",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/nnabeyang/swift-msgpack.git",
|
||||
"state" : {
|
||||
"revision" : "1c8dbd506c68888df3fd5df4ff56cef9cb39c388",
|
||||
"version" : "0.7.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
@@ -17,6 +17,8 @@ let package = Package(
|
||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
||||
// JWTs
|
||||
.package(url: "https://github.com/vapor/jwt.git", from: "5.0.0"),
|
||||
// MsgPack
|
||||
.package(url: "https://github.com/nnabeyang/swift-msgpack.git", from: "0.7.0")
|
||||
],
|
||||
targets: [
|
||||
.executableTarget(
|
||||
@@ -28,6 +30,7 @@ let package = Package(
|
||||
.product(name: "NIOCore", package: "swift-nio"),
|
||||
.product(name: "NIOPosix", package: "swift-nio"),
|
||||
.product(name: "JWT", package: "jwt"),
|
||||
.product(name: "SwiftMsgpack", package: "swift-msgpack"),
|
||||
],
|
||||
swiftSettings: swiftSettings
|
||||
),
|
||||
|
8
Sources/stella/Controllers/Api/ApiController.swift
Normal file
8
Sources/stella/Controllers/Api/ApiController.swift
Normal file
@@ -0,0 +1,8 @@
|
||||
import Vapor
|
||||
|
||||
struct ApiController: RouteCollection {
|
||||
func boot(routes: any RoutesBuilder) throws {
|
||||
let group = routes.grouped("latest", "api", "index.php")
|
||||
try group.register(collection: ToolController())
|
||||
}
|
||||
}
|
34
Sources/stella/Controllers/Api/ToolController.swift
Normal file
34
Sources/stella/Controllers/Api/ToolController.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
import Vapor
|
||||
import SwiftMsgpack
|
||||
import JWT
|
||||
|
||||
struct ToolController: RouteCollection {
|
||||
func boot(routes: any RoutesBuilder) throws {
|
||||
let group = routes.grouped("tool")
|
||||
group.post("signup", use: self.signup)
|
||||
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func signup(req: Request) async throws -> Response {
|
||||
let body = try req.content.decode(SignupReq.self, using: MsgPackDecoder())
|
||||
|
||||
let session = try await req.jwt.verify(body.access_token, as: SessionPayload.self)
|
||||
guard session.type == SessionType.ZAT.rawValue else {
|
||||
throw Abort(.forbidden, reason: "Invalid access token")
|
||||
}
|
||||
|
||||
|
||||
throw Abort(.notImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
struct SignupReq: Content {
|
||||
let app_secret: String
|
||||
let access_token: String
|
||||
let storage_directory_path: String
|
||||
let app_admin: String
|
||||
let kakao_pid: String
|
||||
let device_id: Double
|
||||
let idp_code: String
|
||||
}
|
@@ -6,6 +6,7 @@ struct AuthController: RouteCollection {
|
||||
routes.post("v4", "device", "accessToken", "create", use: self.createAccessToken)
|
||||
routes.post("v3", "agreement", "getForLogin", use: self.loginAgreement)
|
||||
routes.post("v4", "auth", "loginDevice", use: self.loginDevice)
|
||||
routes.post("v3", "zat", "login", use: self.zatLogin)
|
||||
}
|
||||
|
||||
@Sendable
|
||||
@@ -67,14 +68,19 @@ struct AuthController: RouteCollection {
|
||||
} else if let existingAccount = try await Account.query(on: req.db).filter(\.$idpId == idpId).first() {
|
||||
account = existingAccount
|
||||
} else {
|
||||
let account = Account(appId: body.appId, idpAlias: idpAlias, idpCode: "zd3", idpId: idpId, status: "normal")
|
||||
try await account.create(on: req.db)
|
||||
account = Account(appId: body.appId, idpAlias: idpAlias, idpCode: "zd3", idpId: idpId, status: "normal")
|
||||
try await account!.create(on: req.db)
|
||||
}
|
||||
|
||||
guard let account = account else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
|
||||
if account.idpAlias != idpAlias {
|
||||
account.idpAlias = idpAlias
|
||||
try await account.save(on: req.db)
|
||||
}
|
||||
|
||||
let zatExpiry = Date.now.advanced(by: 43200)
|
||||
let zrtExpiry = Date.now.advanced(by: 2592000)
|
||||
|
||||
@@ -91,13 +97,17 @@ struct AuthController: RouteCollection {
|
||||
externalToken: "",
|
||||
zat: zatToken,
|
||||
zrt: zrtToken,
|
||||
player: Player(
|
||||
player: LoginPlayer(
|
||||
idpId: account.idpId,
|
||||
appId: account.appId,
|
||||
playerId: String(try account.requireID()),
|
||||
agreemenet: nil,
|
||||
pushOption: PushOptionResponse(night: "n", player: "n"),
|
||||
regTime: Int(account.regDate.timeIntervalSince1970),
|
||||
idpAlias: idpAlias,
|
||||
idpCode: nil,
|
||||
lang: nil,
|
||||
lastLoginTime: nil,
|
||||
firstLoginTime: Int(account.firstLogin.timeIntervalSince1970),
|
||||
status: account.status
|
||||
)
|
||||
@@ -105,6 +115,110 @@ struct AuthController: RouteCollection {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@Sendable
|
||||
func zatLogin(req: Request) async throws -> LoginDeviceRes {
|
||||
let body = try req.content.decode(ZatLoginReq.self, as: .json)
|
||||
if let session = try? await req.jwt.verify(body.zat, as: SessionPayload.self) {
|
||||
guard session.type == SessionType.ZAT.rawValue && session.accountId.value == body.playerId else {
|
||||
throw Abort(.badRequest, reason: "Invalid zat provided.")
|
||||
}
|
||||
}
|
||||
guard
|
||||
let accountId = Int(body.playerId),
|
||||
let account = try await Account.query(on: req.db)
|
||||
.filter(\.$id == accountId)
|
||||
.first()
|
||||
else {
|
||||
throw Abort(.badRequest, reason: "Invalid playerId")
|
||||
}
|
||||
|
||||
account.lastLogin = Date.now
|
||||
try await account.save(on: req.db)
|
||||
|
||||
let zatExpiry = Date.now.advanced(by: 43200)
|
||||
|
||||
let session = generateToken(accountId: try account.requireID(), expires: zatExpiry, type: SessionType.ZAT)
|
||||
let zatToken = try await req.jwt.sign(session)
|
||||
return LoginDeviceRes(
|
||||
zatExpiryTime: Int(zatExpiry.timeIntervalSince1970) * 1000,
|
||||
zrtExpiryTime: nil,
|
||||
firstLogin: false,
|
||||
externalToken: "",
|
||||
zat: zatToken,
|
||||
zrt: nil,
|
||||
player: LoginPlayer(
|
||||
idpId: account.idpId,
|
||||
appId: account.appId,
|
||||
playerId: String(accountId),
|
||||
agreemenet: AgreementResponse(E001: "y", E002: "y", E006: "y", N002: "n", N003: "n", timestamp: "1717623430484"),
|
||||
pushOption: PushOptionResponse(night: "n", player: "n"),
|
||||
regTime: Int(account.regDate.timeIntervalSince1970) * 1000,
|
||||
idpAlias: account.idpAlias,
|
||||
idpCode: account.idpCode,
|
||||
lang: body.lang,
|
||||
lastLoginTime: Int(account.lastLogin.timeIntervalSince1970) * 1000,
|
||||
firstLoginTime: Int(account.firstLogin.timeIntervalSince1970) * 1000,
|
||||
status: account.status
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ZatLoginReq: Content {
|
||||
let adid: String
|
||||
let appId: String
|
||||
let appSecret: String
|
||||
let appVer: String
|
||||
let clientTime: Int
|
||||
let country: String
|
||||
let deviceId: String
|
||||
let deviceModel: String
|
||||
let fields: [String]
|
||||
let gsiToken: Bool?
|
||||
let lang: String
|
||||
let loginType: String
|
||||
let market: String
|
||||
let network: String
|
||||
let os: String
|
||||
let playerId: String
|
||||
let resume: Bool
|
||||
let retryNo: Int?
|
||||
let sdkVer: String
|
||||
let telecom: String
|
||||
let timezoneOffset: Int
|
||||
let usimCountry: String?
|
||||
let whiteKey: String
|
||||
let zat: String
|
||||
|
||||
init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
adid = try container.decode(String.self, forKey: .adid)
|
||||
appId = try container.decode(String.self, forKey: .appId)
|
||||
appSecret = try container.decode(String.self, forKey: .appSecret)
|
||||
appVer = try container.decode(String.self, forKey: .appVer)
|
||||
clientTime = try container.decode(Int.self, forKey: .clientTime)
|
||||
country = try container.decode(String.self, forKey: .country)
|
||||
deviceId = try container.decode(String.self, forKey: .deviceId)
|
||||
deviceModel = try container.decode(String.self, forKey: .deviceModel)
|
||||
fields = try container.decode([String].self, forKey: .fields)
|
||||
gsiToken = try container.decodeIfPresent(Bool.self, forKey: .gsiToken)
|
||||
lang = try container.decode(String.self, forKey: .lang)
|
||||
loginType = try container.decode(String.self, forKey: .loginType)
|
||||
market = try container.decode(String.self, forKey: .market)
|
||||
network = try container.decode(String.self, forKey: .network)
|
||||
os = try container.decode(String.self, forKey: .os)
|
||||
playerId = try container.decode(String.self, forKey: .playerId)
|
||||
resume = try container.decode(Bool.self, forKey: .resume)
|
||||
retryNo = try container.decodeIfPresent(Int.self, forKey: .retryNo)
|
||||
sdkVer = try container.decode(String.self, forKey: .sdkVer)
|
||||
telecom = try container.decode(String.self, forKey: .telecom)
|
||||
timezoneOffset = try container.decode(Int.self, forKey: .timezoneOffset)
|
||||
usimCountry = try container.decodeIfPresent(String.self, forKey: .usimCountry)
|
||||
whiteKey = try container.decode(String.self, forKey: .whiteKey)
|
||||
zat = try container.decode(String.self, forKey: .zat)
|
||||
}
|
||||
}
|
||||
|
||||
struct AccessTokenRes: Content {
|
||||
@@ -152,21 +266,25 @@ struct LoginDeviceReq: Content {
|
||||
|
||||
struct LoginDeviceRes: Content {
|
||||
let zatExpiryTime: Int
|
||||
let zrtExpiryTime: Int
|
||||
let zrtExpiryTime: Int?
|
||||
let firstLogin: Bool
|
||||
let externalToken: String
|
||||
let zat: String
|
||||
let zrt: String
|
||||
let player: Player
|
||||
let zrt: String?
|
||||
let player: LoginPlayer
|
||||
}
|
||||
|
||||
struct Player: Content {
|
||||
struct LoginPlayer: Content {
|
||||
let idpId: String
|
||||
let appId: String
|
||||
let playerId: String
|
||||
let agreemenet: AgreementResponse?
|
||||
let pushOption: PushOptionResponse
|
||||
let regTime: Int
|
||||
let idpAlias: String
|
||||
let idpCode: String?
|
||||
let lang: String?
|
||||
let lastLoginTime: Int?
|
||||
let firstLoginTime: Int
|
||||
let status: String
|
||||
}
|
||||
|
18
Sources/stella/Decoders/MsgPack.swift
Normal file
18
Sources/stella/Decoders/MsgPack.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
import SwiftMsgpack
|
||||
import Vapor
|
||||
|
||||
extension MsgPackEncoder: @retroactive ContentEncoder, @retroactive @unchecked Sendable {
|
||||
public func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws where E : Encodable {
|
||||
let data = try self.encode(encodable)
|
||||
body.writeString(data.base64EncodedString())
|
||||
}
|
||||
}
|
||||
|
||||
extension MsgPackDecoder: @retroactive ContentDecoder, @retroactive @unchecked Sendable {
|
||||
public func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D where D : Decodable {
|
||||
guard let base64String = body.peekString(length: body.capacity), let data = Data(base64Encoded: base64String) else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
return try self.decode(decodable, from: data)
|
||||
}
|
||||
}
|
39
Sources/stella/Migrations/CreatePlayers.swift
Normal file
39
Sources/stella/Migrations/CreatePlayers.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
import Fluent
|
||||
|
||||
struct CreatePlayers: AsyncMigration {
|
||||
func prepare(on database: any Database) async throws {
|
||||
try await database.schema("players")
|
||||
.field("id", .int, .identifier(auto: true))
|
||||
.field("stamina", .int, .required)
|
||||
.field("stamina_heal_time", .date, .required)
|
||||
.field("boost_point", .int, .required)
|
||||
.field("boss_boost_point", .int, .required)
|
||||
.field("transition_state", .int, .required)
|
||||
.field("role", .int, .required)
|
||||
.field("name", .string, .required)
|
||||
.field("last_login_time", .date, .required)
|
||||
.field("comment", .string, .required)
|
||||
.field("vmoney", .int, .required)
|
||||
.field("free_vmoney", .int, .required)
|
||||
.field("rank_point", .int, .required)
|
||||
.field("star_crumb", .int, .required)
|
||||
.field("bond_token", .int, .required)
|
||||
.field("exp_pool", .int, .required)
|
||||
.field("exp_pooled_time", .date, .required)
|
||||
.field("leader_character_id", .int, .required)
|
||||
.field("party_slot", .int, .required)
|
||||
.field("degree_id", .int, .required)
|
||||
.field("birth", .int, .required)
|
||||
.field("free_mana", .int, .required)
|
||||
.field("paid_mana", .int, .required)
|
||||
.field("enable_auto_3x", .bool, .required)
|
||||
.field("account_id", .int, .required)
|
||||
.field("tutorial_step", .int, .required)
|
||||
.field("tutorial_skip_flag", .int, .required)
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: any Database) async throws {
|
||||
|
||||
}
|
||||
}
|
@@ -1,8 +1,5 @@
|
||||
import Fluent
|
||||
|
||||
/// Property wrappers interact poorly with `Sendable` checking, causing a warning for the `@ID` property
|
||||
/// It is recommended you write your model with sendability checking on and then suppress the warning
|
||||
/// afterwards with `@unchecked Sendable`.
|
||||
final class Account: Model, @unchecked Sendable {
|
||||
static let schema = "accounts"
|
||||
|
||||
|
151
Sources/stella/Models/Player.swift
Normal file
151
Sources/stella/Models/Player.swift
Normal file
@@ -0,0 +1,151 @@
|
||||
import Fluent
|
||||
|
||||
final class Player: Model, @unchecked Sendable {
|
||||
static let schema = "players"
|
||||
|
||||
@ID(custom: "id", generatedBy: .database)
|
||||
var id: Int?
|
||||
|
||||
@Field(key: "stamina")
|
||||
var stamina: Int
|
||||
@Field(key: "stamina_heal_time")
|
||||
var staminaHealTime: Date
|
||||
@Field(key: "boost_point")
|
||||
var boostPoint: Int
|
||||
@Field(key: "boss_boost_point")
|
||||
var bossBoostPoint: Int
|
||||
@Field(key: "transition_state")
|
||||
var transitionState: Int
|
||||
@Field(key: "role")
|
||||
var role: Int
|
||||
@Field(key: "name")
|
||||
var name: String
|
||||
@Field(key: "last_login_time")
|
||||
var lastLoginTime: Date
|
||||
@Field(key: "comment")
|
||||
var comment: String
|
||||
@Field(key: "vmoney")
|
||||
var vmoney: Int
|
||||
@Field(key: "free_vmoney")
|
||||
var freeVmoney: Int
|
||||
@Field(key: "rank_point")
|
||||
var rankPoint: Int
|
||||
@Field(key: "star_crumb")
|
||||
var starCrumb: Int
|
||||
@Field(key: "bond_token")
|
||||
var bondToken: Int
|
||||
@Field(key: "exp_pool")
|
||||
var expPool: Int
|
||||
@Field(key: "exp_pooled_time")
|
||||
var expPooledTime: Date
|
||||
@Field(key: "leader_character_id")
|
||||
var leaderCharacterId: Int
|
||||
@Field(key: "party_slot")
|
||||
var partySlot: Int
|
||||
@Field(key: "degree_id")
|
||||
var degreeId: Int
|
||||
@Field(key: "birth")
|
||||
var birth: Int
|
||||
@Field(key: "free_mana")
|
||||
var freeMana: Int
|
||||
@Field(key: "paid_mana")
|
||||
var paidMana: Int
|
||||
@Field(key: "enable_auto_3x")
|
||||
var enableAuto3x: Bool
|
||||
@Parent(key: "account_id")
|
||||
var account: Account
|
||||
@Field(key: "tutorial_step")
|
||||
var tutorialStep: Int?
|
||||
@Field(key: "tutorial_skip_flag")
|
||||
var tutorialSkipFlag: Int?
|
||||
|
||||
init() { }
|
||||
|
||||
init(
|
||||
stamina: Int,
|
||||
staminaHealTime: Date,
|
||||
boostPoint: Int,
|
||||
bossBoostPoint: Int,
|
||||
transitionState: Int,
|
||||
role: Int,
|
||||
name: String,
|
||||
lastLoginTime: Date,
|
||||
comment: String,
|
||||
vmoney: Int,
|
||||
freeVmoney: Int,
|
||||
rankPoint: Int,
|
||||
starCrumb: Int,
|
||||
bondToken: Int,
|
||||
expPool: Int,
|
||||
expPooledTime: Date,
|
||||
leaderCharacterId: Int,
|
||||
partySlot: Int,
|
||||
degreeId: Int,
|
||||
birth: Int,
|
||||
freeMana: Int,
|
||||
paidMana: Int,
|
||||
enableAuto3x: Bool,
|
||||
account: Account,
|
||||
tutorialStep: Int?,
|
||||
tutorialSkipFlag: Int?
|
||||
) {
|
||||
self.stamina = stamina
|
||||
self.staminaHealTime = staminaHealTime
|
||||
self.boostPoint = boostPoint
|
||||
self.bossBoostPoint = bossBoostPoint
|
||||
self.transitionState = transitionState
|
||||
self.role = role
|
||||
self.name = name
|
||||
self.lastLoginTime = lastLoginTime
|
||||
self.comment = comment
|
||||
self.vmoney = vmoney
|
||||
self.freeVmoney = freeVmoney
|
||||
self.rankPoint = rankPoint
|
||||
self.starCrumb = starCrumb
|
||||
self.bondToken = bondToken
|
||||
self.expPool = expPool
|
||||
self.expPooledTime = expPooledTime
|
||||
self.leaderCharacterId = leaderCharacterId
|
||||
self.partySlot = partySlot
|
||||
self.degreeId = degreeId
|
||||
self.birth = birth
|
||||
self.freeMana = freeMana
|
||||
self.paidMana = paidMana
|
||||
self.enableAuto3x = enableAuto3x
|
||||
self.tutorialStep = tutorialStep
|
||||
self.tutorialSkipFlag = tutorialSkipFlag
|
||||
}
|
||||
|
||||
static func createDefault(account: Account) -> Player {
|
||||
return Player(
|
||||
stamina: 20,
|
||||
staminaHealTime: Date.now,
|
||||
boostPoint: 3,
|
||||
bossBoostPoint: 3,
|
||||
transitionState: 0,
|
||||
role: 1,
|
||||
name: "플레이어",
|
||||
lastLoginTime: Date.now,
|
||||
comment: "Nice to meet you.",
|
||||
vmoney: 0,
|
||||
freeVmoney: 150,
|
||||
rankPoint: 10,
|
||||
starCrumb: 0,
|
||||
bondToken: 0,
|
||||
expPool: 0,
|
||||
expPooledTime: Date.now,
|
||||
leaderCharacterId: 1,
|
||||
partySlot: 1,
|
||||
degreeId: 1,
|
||||
birth: 19900101,
|
||||
freeMana: 1000,
|
||||
paidMana: 0,
|
||||
enableAuto3x: false,
|
||||
account: account,
|
||||
tutorialStep: 0,
|
||||
tutorialSkipFlag: nil
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -12,4 +12,5 @@ func routes(_ app: Application) throws {
|
||||
|
||||
try app.register(collection: InfodeskController())
|
||||
try app.register(collection: OpenApiController())
|
||||
try app.register(collection: ApiController())
|
||||
}
|
||||
|
1
start-mitm.sh
Executable file
1
start-mitm.sh
Executable file
@@ -0,0 +1 @@
|
||||
mitmweb --set connection_strategy=lazy --allow-hosts gc-openapi-zinny3.kakaogames.com --allow-hosts gc-infodesk-zinny3.kakaogames.com --allow-hosts na.wdfp.kakaogames.com --allow-hosts patch.wdfp.kakaogames.com -s ./mitm-redirect-traffic.py
|
Reference in New Issue
Block a user