Fosi Fossil Service
Small teams. Open source. Built on Fossil.

IaC / SwiftFlow

High-performance WMS with wave picking, smart routing, and real-time inventory visibility.

Public | Owner: IaC | Default branch: trunk | Last update: 2026-10-23 14:37

Artifact 3789890a6feb02900fb9f3470da8c297ca706fee01569bb2374061f82fd453ce:


import gleam/dynamic/decode
import gleam/http
import gleam/http/request
import gleam/httpc
import gleam/json
import gleam/result
import gleam/string
import gleam/uri
import ql361api/client.{type Client}
import ql361api/models.{
  type ApiError, type LoginResult, ApiFailure, ApiSession, AuthExpired,
  HttpError, JsonError, LoginResult, NetworkError, UrlParseError,
}

pub const auth_endpoint = "https://www.ql361.com/account/Auth"
pub const captcha_id = "77a29448570d458fb31f106383850cba"
pub const get_login_status_method = "CC.ERP.BLL.API.MessageServer.GetLoginStatusForDesktop"

pub fn login(
  username username: String,
  password password: String,
) -> Result(#(Client, LoginResult), ApiError) {
  let form_data =
    "username="
    <> uri.percent_encode(username)
    <> "&password="
    <> uri.percent_encode(password)
    <> "&type=2&captchaid="
    <> captcha_id
    <> "&piccode=&vcode="

  use req <- result.try(
    request.to(auth_endpoint)
    |> result.replace_error(UrlParseError),
  )

  let req =
    req
    |> request.set_method(http.Post)
    |> request.set_header("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
    |> request.set_header("x-requested-with", "XMLHttpRequest")
    |> request.set_header("user-agent", client.default_user_agent)
    |> request.set_header("referer", "https://www.ql361.com/account/logon")
    |> request.set_body(form_data)

  case httpc.send(req) {
    Ok(resp) -> {
      case resp.status {
        200 -> parse_login_response(resp.body)
        status -> Error(HttpError(status: status, body: resp.body))
      }
    }
    Error(err) -> Error(NetworkError(string.inspect(err)))
  }
}

fn parse_login_response(body: String) -> Result(#(Client, LoginResult), ApiError) {
  let company_decoder = {
    use company_name <- decode.optional_field("companyname", "", decode.string)
    use agent_real_name <- decode.optional_field("agentrealname", "", decode.string)
    decode.success(#(company_name, agent_real_name))
  }

  let login_decoder = {
    use success <- decode.field("success", decode.bool)
    use token <- decode.optional_field("token", "", decode.string)
    use sign <- decode.optional_field("sign", "", decode.string)
    use msg <- decode.optional_field("msg", "", decode.string)
    use company <- decode.optional_field("companydata", #("", ""), company_decoder)
    decode.success(LoginResult(
      success: success,
      company_name: company.0,
      agent_real_name: company.1,
      token: token,
      sign: sign,
      msg: msg,
    ))
  }

  case json.parse(body, login_decoder) {
    Ok(info) -> {
      case info.success {
        True -> {
          let session = ApiSession(
            s: info.sign,
            tok: info.token,
            cookie: "s=" <> info.sign <> "; tok=" <> info.token,
          )
          let authed_client = client.default_client() |> client.with_session(session)
          Ok(#(authed_client, info))
        }
        False -> Error(ApiFailure(case info.msg {
          "" -> "登录失败,请检查账号密码"
          msg -> msg
        }))
      }
    }
    Error(err) -> Error(JsonError(string.inspect(err)))
  }
}

pub fn check_login_status(client: Client) -> Result(Bool, ApiError) {
  case client.post_rpc(client, get_login_status_method, "crmoa", "{}") {
    Ok(_) -> Ok(True)
    Error(AuthExpired(_)) -> Ok(False)
    Error(err) -> Error(err)
  }
}

Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/IaC/SwiftFlow

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.