Back to Home
Gistify Snippet
Public
wink.js
wink.js
#github-gist#javascript#gist:b93823925123b214fb61e54b7c1d304d#gistsync:2026-09-18T02:41:44Z
3
Rawwink.jsjavascript
182 lines| 1 | |
| 2 | import axios from "axios"; |
| 3 | import FormData from "form-data"; |
| 4 | import crypto from "node:crypto"; |
| 5 | import fs from "node:fs"; |
| 6 | import fsp from "node:fs/promises"; |
| 7 | import path from "node:path"; |
| 8 | import { CookieJar } from "tough-cookie"; |
| 9 | import { wrapper } from "axios-cookiejar-support"; |
| 10 | |
| 11 | const BASE_URL = "https://wink.ai"; |
| 12 | const STRATEGY_URL = "https://strategy.app.meitudata.com"; |
| 13 | |
| 14 | const CLIENT_ID = "1189857605"; |
| 15 | const VERSION = "5.1.2"; |
| 16 | const COUNTRY_CODE = "ID"; |
| 17 | const CLIENT_LANGUAGE = "en_US"; |
| 18 | const CLIENT_TIMEZONE = "Asia/Jakarta"; |
| 19 | |
| 20 | const TASK_TYPE = "12"; |
| 21 | const CONTENT_TYPE = "1"; |
| 22 | |
| 23 | const UA = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Mobile Safari/537.36"; |
| 24 | |
| 25 | function sleep(ms) { |
| 26 | return new Promise(resolve => setTimeout(resolve, ms)); |
| 27 | } |
| 28 | |
| 29 | function extToMime(file) { |
| 30 | const ext = path.extname(file).toLowerCase(); |
| 31 | if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg"; |
| 32 | if (ext === ".png") return "image/png"; |
| 33 | if (ext === ".webp") return "image/webp"; |
| 34 | return "application/octet-stream"; |
| 35 | } |
| 36 | |
| 37 | function fileSuffix(file) { |
| 38 | const ext = path.extname(file).toLowerCase(); |
| 39 | if (ext === ".jpeg") return ".jpg"; |
| 40 | if (ext) return ext; |
| 41 | return ".jpg"; |
| 42 | } |
| 43 | |
| 44 | function makeTrace() { |
| 45 | return `${crypto.randomBytes(16).toString("hex")}-${crypto.randomBytes(8).toString("hex")}-1`; |
| 46 | } |
| 47 | |
| 48 | function traceHeaders(transaction = "GET%20%2F%5Blocale%5D%2Fimage-enhancer%2Fupload") { |
| 49 | const trace = makeTrace(); |
| 50 | return { |
| 51 | "sentry-trace": trace, |
| 52 | baggage: [ |
| 53 | "sentry-environment=release", |
| 54 | "sentry-release=5.1.2", |
| 55 | "sentry-public_key=e1bf914f3448d9bc8a10c7e499d17d54", |
| 56 | `sentry-trace_id=${trace.split("-")[0]}`, |
| 57 | `sentry-transaction=${transaction}`, |
| 58 | "sentry-sampled=true", |
| 59 | "sentry-sample_rate=0.75" |
| 60 | ].join(",") |
| 61 | }; |
| 62 | } |
| 63 | |
| 64 | function baseParams(GNUM, extra = {}) { |
| 65 | return new URLSearchParams({ |
| 66 | client_id: CLIENT_ID, |
| 67 | version: VERSION, |
| 68 | country_code: COUNTRY_CODE, |
| 69 | gnum: GNUM, |
| 70 | client_language: CLIENT_LANGUAGE, |
| 71 | client_channel_id: "", |
| 72 | client_timezone: CLIENT_TIMEZONE, |
| 73 | ...extra |
| 74 | }); |
| 75 | } |
| 76 | |
| 77 | export async function wink(IMAGE_PATH) { |
| 78 | try { |
| 79 | const GNUM = crypto.randomUUID(); |
| 80 | const jar = new CookieJar(); |
| 81 | await jar.setCookie(`_sm=${GNUM}; Path=/; Domain=wink.ai`, BASE_URL); |
| 82 | await jar.setCookie(`meitustat=${encodeURIComponent(JSON.stringify({ wgid: GNUM }))}; Path=/; Domain=wink.ai`, BASE_URL); |
| 83 | |
| 84 | const api = wrapper(axios.create({ |
| 85 | baseURL: BASE_URL, |
| 86 | jar, |
| 87 | withCredentials: true, |
| 88 | validateStatus: () => true, |
| 89 | headers: { |
| 90 | accept: "*/*", |
| 91 | origin: BASE_URL, |
| 92 | referer: `${BASE_URL}/image-enhancer/upload`, |
| 93 | "user-agent": UA, |
| 94 | "sec-ch-ua": "\"Google Chrome\";v=\"147\", \"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"147\"", |
| 95 | "sec-ch-ua-mobile": "?1", |
| 96 | "sec-ch-ua-platform": "\"Android\"", |
| 97 | ab_info: JSON.stringify({ ab_codes: [], version: "1.4.4" }) |
| 98 | } |
| 99 | })); |
| 100 | |
| 101 | if (!fs.existsSync(IMAGE_PATH)) throw new Error(`File tidak ditemukan: ${IMAGE_PATH}`); |
| 102 | |
| 103 | const getMaatSign = async () => { |
| 104 | const params = baseParams(GNUM, { suffix: fileSuffix(IMAGE_PATH), type: "temp", count: "1" }); |
| 105 | const res = await api.get(`/api/file/get_maat_sign.json?${params.toString()}`, { headers: traceHeaders() }); |
| 106 | if (res.status >= 400 || res.data?.code !== 0) throw new Error(`get_maat_sign gagal: ${JSON.stringify(res.data)}`); |
| 107 | return res.data.data; |
| 108 | }; |
| 109 | |
| 110 | const getUploadPolicy = async (sign) => { |
| 111 | const params = new URLSearchParams({ app: sign.app, count: String(sign.count), sig: sign.sig, sigTime: sign.sig_time, sigVersion: sign.sig_version, suffix: sign.suffix, type: sign.type }); |
| 112 | const res = await axios.get(`${STRATEGY_URL}/upload/policy?${params.toString()}`, { |
| 113 | headers: { accept: "*/*", origin: BASE_URL, referer: `${BASE_URL}/`, "user-agent": UA }, |
| 114 | validateStatus: () => true |
| 115 | }); |
| 116 | if (res.status >= 400 || !Array.isArray(res.data) || !res.data[0]?.qiniu) throw new Error(`upload policy gagal: ${JSON.stringify(res.data)}`); |
| 117 | return res.data[0].qiniu; |
| 118 | }; |
| 119 | |
| 120 | const uploadToQiniu = async (policy, filePath) => { |
| 121 | const form = new FormData(); |
| 122 | form.append("file", fs.createReadStream(filePath), { filename: path.basename(filePath), contentType: extToMime(filePath) }); |
| 123 | form.append("token", policy.token); |
| 124 | form.append("key", policy.key); |
| 125 | form.append("fname", path.basename(filePath)); |
| 126 | const res = await axios.post(policy.url, form, { |
| 127 | headers: form.getHeaders({ origin: BASE_URL, referer: `${BASE_URL}/`, "user-agent": UA, accept: "*/*" }), |
| 128 | maxBodyLength: Infinity, maxContentLength: Infinity, validateStatus: () => true |
| 129 | }); |
| 130 | if (res.status >= 400) throw new Error(`upload qiniu gagal HTTP ${res.status}`); |
| 131 | return { file_key: policy.key, source_url: res.data.url || res.data.data || policy.data }; |
| 132 | }; |
| 133 | |
| 134 | const sign = await getMaatSign(); |
| 135 | const policy = await getUploadPolicy(sign); |
| 136 | const uploaded = await uploadToQiniu(policy, IMAGE_PATH); |
| 137 | |
| 138 | const TASK_NAME = `Enhancer-Ultra HD-${path.parse(IMAGE_PATH).name}`; |
| 139 | const delivery = async (sourceUrl) => { |
| 140 | const body = baseParams(GNUM, { |
| 141 | type: TASK_TYPE, content_type: CONTENT_TYPE, source_url: sourceUrl, |
| 142 | type_params: JSON.stringify({ is_mirror: 0, orientation_tag: 1, j_420_trans: "1", return_ext: "2" }), |
| 143 | right_detail: JSON.stringify({ source: "1", touch_type: "4", function_id: "630", material_id: "63011", url: "https://wink.ai/image-enhancer/upload" }), |
| 144 | ext_params: JSON.stringify({ task_name: TASK_NAME, records: TASK_TYPE }), |
| 145 | with_prepare: "1" |
| 146 | }); |
| 147 | const res = await api.post("/api/meitu_ai/delivery.json", body.toString(), { |
| 148 | headers: { ...traceHeaders(), "content-type": "application/x-www-form-urlencoded;charset=UTF-8" } |
| 149 | }); |
| 150 | if (res.status >= 400 || res.data?.code !== 0) throw new Error(`delivery gagal: ${JSON.stringify(res.data)}`); |
| 151 | return res.data.data; |
| 152 | }; |
| 153 | |
| 154 | const task = await delivery(uploaded.source_url); |
| 155 | const firstMsgId = task.msg_id || task.prepare_msg_id; |
| 156 | if (!firstMsgId) throw new Error(`delivery tidak mengembalikan msg_id`); |
| 157 | |
| 158 | const waitResult = async (msgId) => { |
| 159 | for (let i = 1; i <= 80; i++) { |
| 160 | const params = baseParams(GNUM, { msg_ids: msgId }); |
| 161 | const res = await api.get(`/api/meitu_ai/query_batch.json?${params.toString()}`, { |
| 162 | headers: { ...traceHeaders("%2F%3Alocale%2Feditor%2Frecent-task"), referer: `${BASE_URL}/image-enhancer/upload` } |
| 163 | }); |
| 164 | if (res.status >= 400 || res.data?.code !== 0) throw new Error(`query batch gagal`); |
| 165 | const item = res.data.data?.item_list?.[0]; |
| 166 | const resultValue = item?.result?.result || ""; |
| 167 | const realMsgId = item?.result?.msg_id || item?.msg_id || ""; |
| 168 | if (resultValue && resultValue !== msgId && !resultValue.startsWith("http")) { msgId = resultValue; continue; } |
| 169 | if (realMsgId && realMsgId !== msgId && !realMsgId.startsWith("wpr_")) { msgId = realMsgId; continue; } |
| 170 | const url = item?.result?.media_info_list?.[0]?.media_data; |
| 171 | if (url && url.startsWith("http") && item?.result?.error_code === 0) return url; |
| 172 | await sleep(3000); |
| 173 | } |
| 174 | throw new Error("Timeout"); |
| 175 | }; |
| 176 | |
| 177 | const resultUrl = await waitResult(firstMsgId); |
| 178 | return { status: true, resultUrl }; |
| 179 | } catch (e) { |
| 180 | return { status: false, error: e.message }; |
| 181 | } |
| 182 | } |
Discussion & Comments0
No comments yet — be the first to leave feedback or start a code discussion.
Sign in with your GitHub account to write comments and join the discussion.