diff --git a/rc_rewrite.js b/rc_rewrite.js new file mode 100644 index 0000000..47f0812 --- /dev/null +++ b/rc_rewrite.js @@ -0,0 +1,288 @@ +const storageKeyName = "rc_rewrite_ddata"; +const jobTitle = "update_rc"; +const notificationTitle = "Update RevenueCat JSON" + +let URL = ""; +// URL = 'https://jsonplaceholder.typicode.com/todos/1'; +// URL = "https://stackoverflow.com/questions/38708550/difference-between-return-await-promise-and-return-promise" +// URL = "http://localhost:8000/rc_rewrite_data.json"; +// URL = "https://api.jsonbin.io/v3/qs/651be8b30574da7622b3b246"; +URL = "https://git.ykz.app/PorridgePi/json/raw/branch/main/rc_rewrite_data.json"; +// URL = "https://porridgepi.github.io/json/rc_rewrite_data.json" + +requestOpts = { + // mode:'no-cors', + cache: "no-store", + headers: { + // "Origin": "https://git.ykz.app/", + // "Accept": "application/json" + } +} + +const isSurge = typeof $httpClient != "undefined"; +const isQuanX = typeof $task != "undefined"; +const isLoon = typeof $loon != "undefined"; +const isJSBox = typeof $app != "undefined" && typeof $http != "undefined"; +const isNode = typeof require == "function" && !isJSBox; + +function timeout(ms, promise) { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error("Timed out after " + String(ms) + "ms")) + }, ms) + + promise + .then(value => { + clearTimeout(timer) + resolve(value) + }) + .catch(reason => { + clearTimeout(timer) + reject(reason) + }) + }) +} + +async function fetchJSON(url, opts = {}) { + if (isQuanX) { + return fetch(url, { + ...opts + }) + .then(response => { + if (false && !response.ok) { + console.log("ERROR(fetchJSON): Response not okay, HTTP " + String(response.status) + ' ' + response.statusText); + throw new Error("Response not ok"); + } + return response.text() + .then(text => { + try { + return JSON.parse(text); + } catch (err) { + console.log("ERROR(fetchJSON): Response body not JSON" + String(err)); + console.log(text); + throw new Error("Response body not JSON"); + } + }) + }); + } + if (isNode) { + var resp = await $http.get(url, opts); + try { + return JSON.parse(resp.body); + } catch (err) { + console.log("ERROR(fetchJSON): Response body not JSON" + String(err)); + console.log(text); + throw new Error("Response body not JSON"); + } + } +} + +async function getRewriteItems() { + try { + let toReturn = JSON.parse($prefs.valueForKey(storageKeyName)); + console.log("INFO: Retrieved local copy.") + return toReturn; + } catch (err) { + console.log("ERROR(getRewriteItems): Unable to access local copy - " + err); + console.log("INFO: Fetching new copy..."); + try { + if (isQuanX) return await timeout(1000, fetchJSON(URL, requestOpts)); // add await if using nested try-catch - https://stackoverflow.com/a/42750371 + if (isNode) return fetchJSON(URL, requestOpts); + } catch (err) { + console.log("ERROR(getRewriteItems): " + err); + throw new Error("Unable to retrieve rewrite items."); + } + } +} + + +async function onResponse(context, url, request, response) { + console.log("INFO: Running..."); + + let rewriteItems = {} + try { + rewriteItems = await getRewriteItems(); + // console.log(JSON.stringify(rewriteItems)); + } catch (err) { + console.log("ERROR(onResponse): " + err); + } + + let bundle_id = request.headers["X-Client-Bundle-ID"]; + let user_agent = request.headers["User-Agent"]; + + for (let i = 0; i < rewriteItems.length; i++) { + rewriteItem = rewriteItems[i] + // console.log(JSON.stringify(rewriteItem)); + console.log(rewriteItem.name); + + let bundleIdMatched = bundle_id != null && bundle_id == rewriteItem.bundle_id + let userAgentMatched = user_agent != null && user_agent.includes(rewriteItem.user_agent) + if (!bundleIdMatched && !userAgentMatched) { + if (i == rewriteItems.length - 1) { // last + console.log(String(bundle_id) + " " + String(user_agent)); + break; + } + continue; + } + console.log("MATCHED!"); + + body = { + ...response.body + } + + if (body.subscriber == undefined) body.subscriber = {} + + console.log(JSON.stringify(body)); + + let now = new Date(); + let MS_PER_MINUTE = 60000; + let timeToUse = new Date(now - 0 * MS_PER_MINUTE); // CAN CHANGE MANUAL DELAY + if (body.request_date == undefined) body.request_date = timeToUse.toISOString().split('.')[0] + "Z"; + if (body.request_date_ms == undefined) body.request_date_ms = Date.parse(timeToUse); + if (body.subscriber.first_seen == undefined) body.subscriber.first_seen = timeToUse.toISOString().split('.')[0] + "Z"; + if (body.subscriber.last_seen == undefined) body.subscriber.last_seen = timeToUse.toISOString().split('.')[0] + "Z"; + if (body.subscriber.management_url == undefined) body.subscriber.management_url = null; + + const uuidString = url.slice(-32); + console.log(uuidString) + const uuid = uuidString.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/g, '$1-$2-$3-$4-$5') + console.log(uuid) + + if (body.subscriber.original_app_user_id == undefined) body.subscriber.original_app_user_id = uuid; + if (body.subscriber.original_application_version == undefined) body.subscriber.original_application_version = "0.0"; + if (body.subscriber.original_purchase_date == undefined) body.subscriber.original_purchase_date = now.toISOString().split('.')[0] + "Z"; + body.subscriber.entitlements = { + ...body.subscriber.entitlements + }; + body.subscriber.subscriptions = { + ...body.subscriber.subscriptions + }; + body.subscriber.non_subscriptions = { + ...body.subscriber.non_subscriptions + }; + body.subscriber.other_purchases = { + ...body.subscriber.other_purchases + }; + + let isSubscription = rewriteItem.new_subscriptions.length > 0; + let isNonSubscription = rewriteItem.new_non_subscriptions.length > 0; + let product_id = "" + + console.log(isNonSubscription); + console.log(isSubscription); + + console.log(body.subscriber.entitlements); + + if (isSubscription) { + product_id = rewriteItem.new_subscriptions[0]; + for (const i of rewriteItem.new_subscriptions) { + body.subscriber.subscriptions[i] = { + "auto_resume_date": null, + "billing_issues_detected_at": null, + "expires_date": "2099-12-31T23:59:59Z", + "grace_period_expires_date": null, + "is_sandbox": false, + "original_purchase_date": "2020-01-01T00:00:00Z", + "period_type": "normal", + "purchase_date": "2020-01-01T00:00:00Z", + "refunded_at": null, + "store": "app-store", + "store_transaction_id": "", + "unsubscribe_detected_at": null + } + } + } + + if (isNonSubscription) { + product_id = rewriteItem.new_non_subscriptions[0]; + for (const i of rewriteItem.new_non_subscriptions) { + body.subscriber.non_subscriptions[i] = { + "is_sandbox": false, + "store_transaction_id": "", + "id": "", + "original_purchase_date": "2020-01-01T00:00:00Z", + "store": "app_store", + "purchase_date": "2020-01-01T00:00:00Z" + } + } + for (const i of rewriteItem.new_other_purchases) { + body.subscriber.other_purchases[i] = { + purchase_date: "2020-01-01T00:00:00Z" + } + } + } + + for (const i of rewriteItem.new_entitlements) { + body.subscriber.entitlements[i] = { + "expires_date": null, + "grace_period_expires_date": null, + "product_identifier": product_id, + "purchase_date": "2020-01-01T00:00:00Z" + } + } + + response.body = body; + response.statusCode = 200; + response.statusPhrase = "OK"; + response.headers["Content-Type"] = "application/json"; + console.log(response.body); + break; + } + + console.log("INFO: Exiting..."); + return response; +} + + +var $request, $response; + +async function main() { + console.log("Starting..."); + if (isSurge || isQuanX) { + try { + console.log(JSON.stringify($request)); + if (typeof $request == 'undefined') $request = { + url: "", + headers: { + // "X-Client-Bundle-ID": "com.benricemccarthy.obscura-2" + }, + body: "{}" + } + console.log(JSON.stringify($request)); + + if (typeof $response == 'undefined') $response = { + url: "", + headers: {}, + body: "{}" + } + url = $request.url; + $request.body = typeof $request.body != "undefined" ? JSON.parse($request.body) : {}; + $response.body = typeof $response.body != "undefined" ? JSON.parse($response.body) : {}; + } catch (err) { + console.log(err); + } + + + console.log("starting onResponse"); + try { + response = await timeout(3000, onResponse(null, url, $request, $response)); + } catch (err) { + console.log(err); + } + + if (isQuanX) { + $request.status = "HTTP/1.1 " + $request.statusCode + " " + $request.statusPhrase; + $response.status = "HTTP/1.1 " + $response.statusCode + " " + $response.statusPhrase; + $request.body = JSON.stringify($request.body); + $response.body = JSON.stringify($response.body); + } + + $done({ + status: $response.status, + headers: $response.headers, + body: $response.body + }); + } +} + +main(); diff --git a/rc_rewrite_data.json b/rc_rewrite_data.json index a862f94..064af58 100644 --- a/rc_rewrite_data.json +++ b/rc_rewrite_data.json @@ -100,6 +100,30 @@ "new_non_subscriptions": [], "new_other_purchases": [] }, + { + "name": "APTV", + "isEnabled": true, + "bundle_id": "com.kimen.aptvpro", + "user_agent": null, + "new_entitlements": [ + "pro" + ], + "new_subscriptions": [], + "new_non_subscriptions": [ + "com.kimen.aptvpro.lifetime" + ], + "new_other_purchases": [] + }, + { + "name": "", + "isEnabled": true, + "bundle_id": "", + "user_agent": null, + "new_entitlements": [], + "new_subscriptions": [], + "new_non_subscriptions": [], + "new_other_purchases": [] + }, { "name": "", "isEnabled": true,