应用克隆

clone快应用,还不完善

适配设备:Pro

作者:zidspo

下载量:36 · 点赞:5

可信源码风险提示:保存或下载前请阅读源码和权限声明。
SHA-2567fe2c85d87212e03598b6e52b8c0c622dcd536fe5d7f562f3a9b13032cc64295
// author:紫初辰 QQ:2027439658
const baseDir = 'internal://files/../../app/'

// 响应式状态
const status = ui.signal('准备就绪')
const srcPkg = ui.signal('')
const dstPkg = ui.signal('')

function joinPath(dir, name) {
  return dir + (dir.endsWith('/') ? '' : '/') + name
}

function getDirName(p) {
  var i = p.lastIndexOf('/')
  return i >= 0 ? p.substring(0, i) : ''
}

// system.files → Promise 包装
function fs(method, params) {
  return new Promise(function(resolve) {
    system.files[method](Object.assign({}, params, {
      success: function(d) { resolve({ ok: true, data: d }) },
      fail: function(_, code) { resolve({ ok: false, code: code }) }
    }))
  })
}

// ===== 递归复制(get + subFiles) =====
async function copyDir(src, dst) {
  var r = await fs('get', { uri: src })
  if (!r.ok) { console.log('get失败: ' + src + ' code=' + r.code); return }
  var subs = (r.data.subFiles || [])
  console.log('get ' + src + ' -> ' + subs.length + ' 项')

  for (var i = 0; i < subs.length; i++) {
    var f = subs[i]
    var name = f.name
    if (!name) {
      var u = f.uri || ''
      name = u.substring(u.lastIndexOf('/') + 1)
    }
    if (!name) continue

    var sc = joinPath(src, name)
    var dc = joinPath(dst, name)

    if (f.type === 'dir') {
      await fs('mkdir', { uri: dc, recursive: true })
      await copyDir(sc, dc)
    } else {
      var cr = await fs('copy', { srcUri: sc, dstUri: dc })
      if (cr.ok) console.log('[FILE] ' + dc)
      else console.log('[FILE] 失败: ' + dc + ' code=' + cr.code)
    }
  }
}

// ===== 选择源包 =====
async function pickSource() {
  status.set('读取应用列表...')
  var ar = await fs('readText', { uri: 'internal://files/../../apps.json' })
  if (!ar.ok) { script.toast('读取apps.json失败'); status.set('就绪'); return }

  var apps = []
  try { apps = JSON.parse(ar.data.text).InstalledApps || [] } catch(e) {}

  if (apps.length === 0) { script.toast('没有已安装的应用'); status.set('就绪'); return }

  var items = apps.map(function(a) {
    return { label: (a.names && a.names[0] && a.names[0].value) || a.package, value: a.package }
  })

  var choice = await dialog.select({
    title: '选择要克隆的应用',
    items: items,
    multiple: false
  })

  if (choice.action === 'confirm' && choice.value) {
    srcPkg.set(choice.value)
    dstPkg.set(choice.value + '.clone')
    status.set('已选择: ' + choice.value)
  }
}

// ===== 修改目标包名 =====
async function renameTarget() {
  var r = await dialog.text({
    title: '目标包名',
    message: '输入新应用的包名',
    value: dstPkg.get() || 'com.test.app',
    required: true,
    maxLength: 64
  })
  if (r.action === 'confirm' && r.value && r.value.trim()) {
    dstPkg.set(r.value.trim())
    status.set('目标包名已更新')
  }
}

// ===== 执行克隆 =====
async function doClone() {
  if (!srcPkg.get()) { script.toast('请先选择源包'); return }
  if (!dstPkg.get()) { script.toast('请设置目标包名'); return }

  status.set('克隆中...')
  var srcDir = joinPath(baseDir, srcPkg.get())
  var dstDir = joinPath(baseDir, dstPkg.get())

  // 1. 创建目标目录
  await fs('mkdir', { uri: dstDir, recursive: true })

  // 2. 递归复制
  await copyDir(srcDir, dstDir)

  // 3. 读源 manifest 拿图标路径
  var srcIcon = ''
  var srcMr = await fs('readText', { uri: joinPath(srcDir, 'manifest.json') })
  if (srcMr.ok) {
    try { srcIcon = JSON.parse(srcMr.data.text).icon || '' } catch(e) {}
  }

  // 4. 改目标 manifest 包名 + 图标
  var dstMr = await fs('readText', { uri: joinPath(dstDir, 'manifest.json') })
  if (dstMr.ok) {
    try {
      var m = JSON.parse(dstMr.data.text)
      m.package = dstPkg.get()
      if (srcIcon) m.icon = srcIcon
      await fs('writeText', { uri: joinPath(dstDir, 'manifest.json'), text: JSON.stringify(m, null, 2) })
    } catch(e) { console.log('manifest解析失败') }
  }

  // 5. 复制图标文件
  if (srcIcon) {
    var cleanPath = srcIcon.replace(/^\/+/, '')
    var srcIconFull = joinPath(srcDir, cleanPath)
    var dstIconFull = joinPath(dstDir, cleanPath)
    await fs('mkdir', { uri: getDirName(dstIconFull), recursive: true })
    await fs('copy', { srcUri: srcIconFull, dstUri: dstIconFull })
  }

  // 6. 注册到 apps.json
  var ar = await fs('readText', { uri: 'internal://files/../../apps.json' })
  if (ar.ok) {
    try {
      var j = JSON.parse(ar.data.text)
      if (!j.InstalledApps) j.InstalledApps = []
      var exist = false
      for (var i = 0; i < j.InstalledApps.length; i++) {
        if (j.InstalledApps[i].package === dstPkg.get()) { exist = true; break }
      }
      if (!exist) {
        j.InstalledApps.push({
          package: dstPkg.get(),
          versionName: '1.0.0',
          versionCode: 1,
          icon: srcIcon || 'common/icon.png',
          minAPILevel: 1,
          minPlatformVersion: 1000,
          fingerprint: '',
          names: [{ value: 'Cloned App', lang: 'defaults' }]
        })
        await fs('writeText', { uri: 'internal://files/../../apps.json', text: JSON.stringify(j, null, 4) })
      }
    } catch(e) { console.log('apps.json处理失败') }
  }

  status.set('克隆完成')
  script.toast('克隆完成,重启后生效')
}

// ===== UI 布局 =====
ui.showHeader(false)
ui.render(function() {
  return ui.column([
    ui.heading('应用克隆工具', { size: 26 }),
    ui.text('状态: ' + status.get(), { size: 17, color: '#b9c8cf' }),
    ui.text('源包: ' + (srcPkg.get() || '未选择'), { size: 16, color: '#c1cbd1' }),
    ui.text('目标: ' + (dstPkg.get() || '未设置'), { size: 16, color: '#c1cbd1' }),
    ui.row([
      ui.button('选择源包', pickSource, { background: '#1976a3', height: 48, lines: 1 }),
      ui.button('改包名', renameTarget, { background: '#1976a3', height: 48, lines: 1 }),
    ], { gap: 8 }),
    ui.button('开始克隆', doClone, { background: '#2e7d32', height: 54, lines: 1 }),
    ui.button('退出', function() { script.exit() }, { background: '#34373d', height: 44, lines: 1 })
  ], { gap: 12, padding: 16 })
})