index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import axios, { all } from "axios"
  2. import pRetry, { AbortError } from "p-retry"
  3. import { parse } from "node-html-parser"
  4. import tmp from "tmp"
  5. import fs, { readFileSync } from "fs"
  6. import path from "path"
  7. import { execFileSync, spawn } from "child_process"
  8. import printf from "printf"
  9. import chalk from "chalk"
  10. import PQueue from "p-queue"
  11. import { setTimeout } from "timers/promises"
  12. async function fetchModels() {
  13. console.log("fetching models")
  14. const html = await pRetry(
  15. async () => {
  16. const response = await fetch("https://desktop.firmware.mobi")
  17. // Abort retrying if the resource doesn't exist
  18. if (response.status === 404) {
  19. throw new AbortError(response.statusText)
  20. }
  21. return response.text()
  22. },
  23. { retries: 5 }
  24. )
  25. const root = parse(html)
  26. const scripts = root.querySelectorAll("script")
  27. let allModels = []
  28. scripts.map(s => {
  29. const text = s.text.trim()
  30. if (!text.includes("models =")) {
  31. return
  32. }
  33. let models = []
  34. let firmwares
  35. eval(s.text)
  36. allModels = allModels.concat(models)
  37. })
  38. allModels.sort((a, b) => a.id - b.id)
  39. console.log(`total models: ${allModels.length}`)
  40. return allModels
  41. }
  42. async function fetchFirmwares(model) {
  43. console.log(`fetching firmwares: ${model.de}(modelId=${model.id})`)
  44. const html = await pRetry(
  45. async () => {
  46. const response = await fetch(
  47. `https://desktop.firmware.mobi/device:${model.id}`
  48. )
  49. // Abort retrying if the resource doesn't exist
  50. if (response.status === 404) {
  51. throw new AbortError(response.statusText)
  52. }
  53. return response.text()
  54. },
  55. { retries: 5 }
  56. )
  57. const root = parse(html)
  58. const scripts = root.querySelectorAll("script")
  59. let allFirmwares = []
  60. scripts.map(s => {
  61. const text = s.text.trim()
  62. if (!text.includes("firmwares =")) {
  63. return
  64. }
  65. let firmwares = []
  66. eval(s.text)
  67. allFirmwares = allFirmwares.concat(firmwares)
  68. })
  69. console.log(`${allFirmwares.length} firmwares found`)
  70. model.firmwares = allFirmwares
  71. return allFirmwares
  72. }
  73. async function fetchProps(model, firmware) {
  74. console.log(
  75. `fetching props: ${model.de}(modelId=${model.id}, firmwareId=${firmware.id})`
  76. )
  77. const html = await pRetry(
  78. async () => {
  79. const response = await fetch(
  80. `https://desktop.firmware.mobi/device:${model.id}/firmware:${firmware.id}`
  81. )
  82. // Abort retrying if the resource doesn't exist
  83. if (response.status === 404) {
  84. throw new AbortError(response.statusText)
  85. }
  86. return response.text()
  87. },
  88. { retries: 5 }
  89. )
  90. const root = parse(html)
  91. const rows = root.querySelectorAll(".rTableRow")
  92. const props = {}
  93. rows.map(row => {
  94. const cells = row.querySelectorAll(".rTableCell")
  95. if (cells.length === 2) {
  96. return {
  97. key: cells[0].text.replace(/^- /, "").trim(),
  98. value: cells[1].text.trim()
  99. }
  100. }
  101. return null
  102. })
  103. .filter(i => {
  104. return i && i.key && i.value
  105. })
  106. .forEach(i => {
  107. props[i.key] = i.value
  108. })
  109. console.log(props)
  110. const collaps = root.querySelectorAll(".collapsible-body")
  111. const rawProps = collaps
  112. .map(c =>
  113. c
  114. .querySelectorAll("pre")
  115. .map(pre => pre.text)
  116. .join("")
  117. )
  118. .join()
  119. const tmpDir = tmp.dirSync()
  120. console.log(tmpDir.name)
  121. fs.copyFileSync(
  122. path.join("scripts", "gen_pif_custom.sh"),
  123. path.join(tmpDir.name, "gen_pif_custom.sh")
  124. )
  125. fs.chmodSync(path.join(tmpDir.name, "gen_pif_custom.sh"), "755")
  126. let buildProp = ""
  127. let vendorBuildProp = ""
  128. let productBuildProp = ""
  129. rawProps.split("\n").forEach(line => {
  130. if (line.includes(".vendor.")) {
  131. vendorBuildProp += line + "\n"
  132. } else if (
  133. line.includes(".product.product.") ||
  134. line.includes(".product.build.") ||
  135. line.includes(".product.vndk.")
  136. ) {
  137. productBuildProp += line + "\n"
  138. } else {
  139. buildProp += line + "\n"
  140. }
  141. })
  142. if (!buildProp.includes("ro.product.system.device=generic")) {
  143. fs.writeFileSync(path.join(tmpDir.name, "build.prop"), buildProp)
  144. }
  145. fs.writeFileSync(
  146. path.join(tmpDir.name, "vendor-build.prop"),
  147. vendorBuildProp
  148. )
  149. fs.writeFileSync(
  150. path.join(tmpDir.name, "product-build.prop"),
  151. productBuildProp
  152. )
  153. try {
  154. // execFileSync(path.join(tmpDir.name, "gen_pif_custom.sh"), [], {
  155. // cwd: tmpDir.name
  156. // })
  157. await new Promise((resolve, reject) => {
  158. const p = spawn(path.join(tmpDir.name, "gen_pif_custom.sh"), {
  159. cwd: tmpDir.name
  160. })
  161. p.stdout.on("data", data => {
  162. console.log(chalk.cyan(data.toString().replace("\n", "")))
  163. })
  164. p.stderr.on("data", data => {
  165. console.log(chalk.red(data.toString().replace("\n", "")))
  166. })
  167. p.on("close", code => {
  168. resolve()
  169. })
  170. })
  171. } catch (e) {}
  172. const pifPath = path.join(tmpDir.name, "custom.pif.json")
  173. if (fs.existsSync(pifPath)) {
  174. fs.copyFileSync(
  175. pifPath,
  176. path.join(
  177. "dist",
  178. `${printf("%05d", model.id)}_${printf(
  179. "%05d",
  180. firmware.id
  181. )}.json`
  182. )
  183. )
  184. }
  185. }
  186. if (!fs.existsSync("dist")) {
  187. fs.mkdirSync("dist")
  188. }
  189. const queue = new PQueue({ concurrency: 2 })
  190. // const models = await fetchModels()
  191. // models.forEach(model => {
  192. // queue.add(() => fetchFirmwares(model))
  193. // })
  194. // await queue.onIdle()
  195. // fs.writeFileSync("models.json", JSON.stringify(models, null, 2))
  196. const models = JSON.parse(fs.readFileSync("models.json").toString())
  197. // models.forEach(model => {
  198. // model.firmwares.forEach(firmware => {
  199. // queue.add(() => fetchProps(model, firmware))
  200. // })
  201. // })
  202. // await queue.onIdle()
  203. console.log(
  204. models.map(model => model.firmwares.length).reduce((a, b) => a + b, 0)
  205. )
  206. const missingModels = []
  207. const missingFirmwares = []
  208. models.forEach(model => {
  209. model.firmwares.forEach(firmware => {
  210. const pifPath = path.join(
  211. "dist",
  212. `${printf("%05d", model.id)}_${printf("%05d", firmware.id)}.json`
  213. )
  214. if (!fs.existsSync(pifPath)) {
  215. if (missingModels.findIndex(m => m.id === model.id) === -1) {
  216. missingModels.push(model)
  217. missingFirmwares.push([model, firmware])
  218. }
  219. }
  220. })
  221. })
  222. console.log(`missing: ${missingModels.length}`)
  223. missingFirmwares.sort((a, b) => a[0].id - b[0].id)
  224. missingFirmwares.forEach(([model, firmware]) => {
  225. console.log(
  226. `https://desktop.firmware.mobi/device:${model.id}/firmware:${firmware.id}`
  227. )
  228. })
  229. missingFirmwares
  230. .reverse()
  231. .forEach(([model, firmware]) => {
  232. // queue.add(() => fetchProps(model, firmware))
  233. })
  234. await queue.onIdle()
  235. // console.log(
  236. // `https://desktop.firmware.mobi/device:${missingFirmwares[0][0].id}/firmware:${missingFirmwares[0][1].id}`
  237. // )
  238. // await fetchProps(missingFirmwares[0][0], missingFirmwares[0][1])