iphone-xudale 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.eslintrc.json +3 -0
  2. package/app/(pages)/options/page.tsx +720 -0
  3. package/app/(pages)/popup/page.tsx +166 -0
  4. package/app/(pages)/tips/page.tsx +40 -0
  5. package/app/components/DropListBox.tsx +157 -0
  6. package/app/components/SVGPlay.tsx +14 -0
  7. package/app/favicon.ico +0 -0
  8. package/app/globals.css +27 -0
  9. package/app/layout.tsx +15 -0
  10. package/app/page.tsx +3 -0
  11. package/app/scripts/content/checkoutSteps.ts +341 -0
  12. package/app/scripts/content/doFroApplePages.ts +206 -0
  13. package/app/scripts/content/getPageInitInfo.ts +49 -0
  14. package/app/scripts/content/getStoreCanPickInfo.ts +251 -0
  15. package/app/scripts/content/goOrderSteps.ts +179 -0
  16. package/app/scripts/content/index.ts +56 -0
  17. package/app/scripts/content/playSystemNotifacation.ts +39 -0
  18. package/app/scripts/content/sendSelfNotificatioin.ts +27 -0
  19. package/app/scripts/inject/index.ts +18 -0
  20. package/app/shared/constants.ts +236 -0
  21. package/app/shared/interface.ts +25 -0
  22. package/app/shared/location/city.json +1774 -0
  23. package/app/shared/location/county.json +17115 -0
  24. package/app/shared/location/province.json +94 -0
  25. package/app/shared/util.ts +93 -0
  26. package/buildAfter.js +86 -0
  27. package/bunBuild.ts +54 -0
  28. package/extension/content-script.js +7 -0
  29. package/extension/favicon.ico +0 -0
  30. package/extension/icons/icon128.png +0 -0
  31. package/extension/icons/icon16.png +0 -0
  32. package/extension/icons/icon19-disable.png +0 -0
  33. package/extension/icons/icon19.png +0 -0
  34. package/extension/icons/icon32.png +0 -0
  35. package/extension/icons/icon38.png +0 -0
  36. package/extension/icons/icon48.png +0 -0
  37. package/extension/inject-script.js +1 -0
  38. package/extension/manifest.json +38 -0
  39. package/extension/service-worker.js +62 -0
  40. package/extension.next.config.js +25 -0
  41. package/icon_generator.py +30 -0
  42. package/middleware.ts +21 -0
  43. package/next.config.js +15 -0
  44. package/package.json +67 -0
  45. package/postcss.config.js +6 -0
  46. package/public/assets/images/SCR-20230916-nbkz.png +0 -0
  47. package/public/assets/images/SCR-20230916-nbyv.png +0 -0
  48. package/public/assets/images/SCR-20230916-ncte.png +0 -0
  49. package/public/assets/images/SCR-20230916-ndgw.png +0 -0
  50. package/public/assets/images/SCR-20230916-ndks.png +0 -0
  51. package/public/assets/images/SCR-20230916-neaa.png +0 -0
  52. package/public/assets/images/SCR-20230916-neeq.jpeg +0 -0
  53. package/public/assets/images/SCR-20230916-nfkt.png +0 -0
  54. package/public/assets/images/SCR-20230919-ulfn.png +0 -0
  55. package/public/assets/images/SCR-20230919-ulzd.png +0 -0
  56. package/public/assets/images/SCR-20230919-uocr.png +0 -0
  57. package/public/icon_original.png +0 -0
  58. package/public/next.svg +1 -0
  59. package/public/vercel.svg +1 -0
  60. package/tailwind.config.ts +20 -0
  61. package/tsconfig.json +27 -0
  62. package/types/global.d.ts +19 -0
@@ -0,0 +1,720 @@
1
+ 'use client'
2
+ import { restoreFromStorage, saveToStorage, sleep } from '@/app/shared/util'
3
+ import {
4
+ storeKeys,
5
+ billItemList,
6
+ defaultiPhoneOrderConfig,
7
+ billTypeKeys,
8
+ defaultPayinstallmentTotal,
9
+ } from '@/app/shared/constants'
10
+ import DropListBox from '@/app/components/DropListBox'
11
+ import { IPHONEORDER_CONFIG } from '@/app/shared/interface'
12
+ import { useCallback, useEffect, useState, useRef, useMemo } from 'react'
13
+ import { filter as _filter, map as _map, find as _find, findIndex as _findIndex, sortBy as _sortBy } from 'lodash'
14
+ import city from '@/app/shared/location/city.json'
15
+ import province from '@/app/shared/location/province.json'
16
+ import county from '@/app/shared/location/county.json'
17
+
18
+ const defaultItem = { id: '', name: '' }
19
+
20
+ type VoiceType = { lang?: string; voiceName?: string; id: string; name: string }
21
+
22
+ export default function Options() {
23
+ const [config, setConfig] = useState<IPHONEORDER_CONFIG>(defaultiPhoneOrderConfig)
24
+ const [payinstallmentList, setpayinstallmentList] = useState([defaultItem])
25
+ const [provinceList, setProvinceList] = useState(province)
26
+ const [selectedProvinceIndex, setSelectedProvinceIndex] = useState(0)
27
+ const [cityList, setCityList] = useState([defaultItem])
28
+ const [selectedCityIndex, setSelectedCityIndex] = useState(0)
29
+ const [districtList, setDistrictList] = useState([defaultItem])
30
+ const [selectedDistrictIndex, setSelectedDistrictIndex] = useState(0)
31
+ const [voiceList, setVoiceList] = useState<VoiceType[]>([defaultItem])
32
+ const firstNameRef = useRef<HTMLInputElement>(null)
33
+ const lastNameRef = useRef<HTMLInputElement>(null)
34
+ const last4codeRef = useRef<HTMLInputElement>(null)
35
+ const mobileRef = useRef<HTMLInputElement>(null)
36
+ const appleidRef = useRef<HTMLInputElement>(null)
37
+ const passwordRef = useRef<HTMLInputElement>(null)
38
+ const stepWaitRef = useRef<HTMLInputElement>(null)
39
+ const beforeReloadCountRef = useRef<HTMLInputElement>(null)
40
+ const voiceTimesRef = useRef<HTMLInputElement>(null)
41
+ const voiceTextRef = useRef<HTMLInputElement>(null)
42
+ const notiAPIRef = useRef<HTMLInputElement>(null)
43
+
44
+ useEffect(() => {
45
+ restoreFromStorage(storeKeys.orderConfig).then(data => {
46
+ if (data) {
47
+ console.log(`restoreFromStorage`, data)
48
+ setConfig(config => {
49
+ return {
50
+ ...config,
51
+ ...(data as IPHONEORDER_CONFIG),
52
+ }
53
+ })
54
+ }
55
+ })
56
+
57
+ const syncFun = async () => {
58
+ const voiceList = await getVoices()
59
+ setVoiceList(voiceList)
60
+ }
61
+ syncFun()
62
+ }, [])
63
+
64
+ const voiceSelected = useMemo(() => {
65
+ let _index = -1
66
+ const { lang, voiceName } = config?.voiceInfo || {}
67
+ if (lang && voiceName && voiceList.length) {
68
+ _index = _findIndex(voiceList, v => {
69
+ return v.lang == lang && v.voiceName == voiceName
70
+ })
71
+ }
72
+ if (_index < 0) _index = 0
73
+ return _index
74
+ }, [voiceList, config.voiceInfo])
75
+ // ************ 更新选中支付方式 ************
76
+ const billSelected = useMemo(() => {
77
+ return (
78
+ (config?.payBill &&
79
+ _findIndex(billItemList, _b => {
80
+ return _b.id == config.payBill
81
+ })) ||
82
+ 0
83
+ )
84
+ }, [config.payBill])
85
+
86
+ // ************ 更新选中分期笔数 ************
87
+ const payinstallmentSelected = useMemo(() => {
88
+ return (
89
+ (config?.payInstallment &&
90
+ _findIndex(payinstallmentList, _b => {
91
+ return _b.id == String(config.payInstallment)
92
+ })) ||
93
+ 0
94
+ )
95
+ }, [config.payInstallment, payinstallmentList])
96
+
97
+ // ************ 👇下拉菜单联动👇 ************
98
+ useEffect(() => {
99
+ const newPayinstallmentList = _map(
100
+ _filter(defaultPayinstallmentTotal, item => {
101
+ if (item.id == 0) return true
102
+ if (item?.includes && Number(item?.includes?.length) > 0) {
103
+ return item.includes.includes(config.payBill)
104
+ }
105
+ return false
106
+ }),
107
+ (_item: any) => {
108
+ return {
109
+ id: _item.id,
110
+ name: _item.name,
111
+ }
112
+ }
113
+ )
114
+
115
+ setpayinstallmentList(newPayinstallmentList)
116
+ // 当原来的分期笔数不存在时
117
+ if (
118
+ !_find(newPayinstallmentList, _t => {
119
+ return _t.id == config.payInstallment
120
+ })
121
+ ) {
122
+ setConfig({
123
+ ...config,
124
+ payInstallment: Number(newPayinstallmentList[0].id) || 0,
125
+ })
126
+ }
127
+ }, [config.payBill])
128
+
129
+ useEffect(() => {
130
+ if (config.provinceName) {
131
+ let provinceIndex: number = _findIndex(province, item => {
132
+ return item.name == config.provinceName
133
+ })
134
+ provinceIndex = provinceIndex > -1 ? provinceIndex : 0
135
+
136
+ setSelectedProvinceIndex(provinceIndex)
137
+ const provinceId = province[provinceIndex].id
138
+ // @ts-ignore
139
+ const newCityList = city[provinceId]
140
+ setCityList(newCityList)
141
+ if (
142
+ !_find(newCityList, _t => {
143
+ return _t.name == config.cityName
144
+ })
145
+ ) {
146
+ setConfig({
147
+ ...config,
148
+ cityName: newCityList[0].name,
149
+ })
150
+ }
151
+ // if (provinceList.length < 1) {
152
+ // setProvinceList(province)
153
+ // }
154
+ }
155
+ }, [config.provinceName])
156
+
157
+ useEffect(() => {
158
+ if (config.cityName) {
159
+ let cityIndex: number = _findIndex(cityList, item => {
160
+ return item.name == config.cityName
161
+ })
162
+ cityIndex = cityIndex > -1 ? cityIndex : 0
163
+ setSelectedCityIndex(cityIndex)
164
+ const cityId: string = cityList[cityIndex].id
165
+ // @ts-ignore
166
+ const newDistrictList = county[cityId]
167
+ // @ts-ignore
168
+ console.log(`cityIndex`, cityIndex, cityList[cityIndex], county[cityId])
169
+ if (newDistrictList) {
170
+ setDistrictList(newDistrictList)
171
+ if (
172
+ !_find(newDistrictList, _t => {
173
+ return _t.name == config.districtName
174
+ })
175
+ ) {
176
+ setConfig({
177
+ ...config,
178
+ districtName: newDistrictList[0].name,
179
+ })
180
+ }
181
+ }
182
+ }
183
+ }, [config.cityName, cityList])
184
+
185
+ useEffect(() => {
186
+ // setSelectedDistrictIndex
187
+ let districtIndex: number = _findIndex(districtList, item => {
188
+ return item.name == config.districtName
189
+ })
190
+ districtIndex = districtIndex > -1 ? districtIndex : 0
191
+ setSelectedDistrictIndex(districtIndex)
192
+ }, [config.districtName, districtList])
193
+ // ************ 👆下拉菜单联动👆 ************
194
+
195
+ // ************ 支付方式 ************
196
+ const handleSelectPayType = (payItem: Record<string, any>) => {
197
+ setConfig(prev => {
198
+ return {
199
+ ...prev,
200
+ payBill: payItem.id,
201
+ }
202
+ })
203
+ }
204
+
205
+ // ************ 分期笔数 ************
206
+ const handleSelectPayinstallment = (payinstallmentItem: Record<string, any>) => {
207
+ setConfig(prev => {
208
+ return {
209
+ ...prev,
210
+ payInstallment: payinstallmentItem.id,
211
+ }
212
+ })
213
+ }
214
+
215
+ // ************ 选中省份 ************
216
+ const handleSelectProvince = (provinceItem: Record<string, any>) => {
217
+ setConfig(prev => {
218
+ return {
219
+ ...prev,
220
+ provinceName: provinceItem.name,
221
+ }
222
+ })
223
+ }
224
+ // ************ 选中城市 ************
225
+ const handleSelectCity = (cityItem: Record<string, any>) => {
226
+ setConfig(prev => {
227
+ return {
228
+ ...prev,
229
+ cityName: cityItem.name,
230
+ }
231
+ })
232
+ }
233
+
234
+ // ************ 选中区域 ************
235
+ const handleSelectDistrict = (districtItem: Record<string, any>) => {
236
+ setConfig(prev => {
237
+ return {
238
+ ...prev,
239
+ districtName: districtItem.name,
240
+ }
241
+ })
242
+ }
243
+
244
+ const handleSelectVoice = (voiceItem: Record<string, any>) => {
245
+ setConfig(prev => {
246
+ return {
247
+ ...prev,
248
+ voiceInfo: {
249
+ ...prev.voiceInfo,
250
+ lang: voiceItem.lang,
251
+ voiceName: voiceItem.voiceName,
252
+ },
253
+ }
254
+ })
255
+ }
256
+ const handleSave = useCallback(() => {
257
+ const saveConfig: IPHONEORDER_CONFIG = {
258
+ ...config,
259
+ firstName: firstNameRef.current?.value,
260
+ lastName: lastNameRef.current?.value,
261
+ last4code: last4codeRef.current?.value,
262
+ mobile: mobileRef.current?.value,
263
+ appleId: appleidRef.current?.value,
264
+ password: passwordRef.current?.value,
265
+ stepWait: Number(stepWaitRef.current?.value) || config.stepWait || 10,
266
+ afterCountThenReload: Number(beforeReloadCountRef.current?.value) || config.afterCountThenReload || 50,
267
+ selfNotiAPI: notiAPIRef.current?.value || '',
268
+ voiceInfo: {
269
+ ...config.voiceInfo,
270
+ text: voiceTextRef.current?.value || defaultiPhoneOrderConfig.voiceInfo.text,
271
+ voiceName: config.voiceInfo.voiceName || '',
272
+ lang: config.voiceInfo.lang || '',
273
+ times: Number(voiceTimesRef.current?.value) || defaultiPhoneOrderConfig.voiceInfo.times,
274
+ },
275
+ }
276
+ console.log(saveConfig)
277
+ saveAsync(saveConfig)
278
+ }, [config])
279
+
280
+ const handleCancel = useCallback(() => {
281
+ window.close()
282
+ }, [])
283
+
284
+ const handlePlaySound = () => {
285
+ const _text = voiceTextRef.current?.value || defaultiPhoneOrderConfig.voiceInfo.text,
286
+ _voiceName = config.voiceInfo.voiceName || '',
287
+ _lang = config.voiceInfo.lang || ''
288
+ let voiceOptions = {}
289
+ if (config.voiceInfo.voiceName && config.voiceInfo.lang) {
290
+ voiceOptions = {
291
+ lang: config.voiceInfo.lang,
292
+ voiceName: config.voiceInfo.voiceName,
293
+ }
294
+ }
295
+ if (typeof chrome !== 'undefined' && chrome?.tts) {
296
+ chrome.tts.speak(_text, voiceOptions)
297
+ }
298
+ }
299
+
300
+ const inputClass = `px-3 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6`
301
+ const labelClass = `block text-sm font-medium leading-6 text-gray-900`
302
+ return (
303
+ <main className="flex min-h-screen flex-col items-center justify-between p-24">
304
+ <div className="space-y-12">
305
+ <div className="border-b border-gray-900/10 pb-12">
306
+ <h2 className="text-base font-semibold leading-7 text-gray-900">配置信息</h2>
307
+ <p className="mt-1 text-sm leading-6 text-gray-600">以下信息用于抢购iPhone时自动填入</p>
308
+ <div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
309
+ <div className="sm:col-span-3">
310
+ <label htmlFor="last-name" className={labelClass}>
311
+ 姓氏
312
+ </label>
313
+ <div className="mt-2">
314
+ <input
315
+ ref={lastNameRef}
316
+ type="text"
317
+ name="last-name"
318
+ id="last-name"
319
+ autoComplete="family-name"
320
+ className={inputClass}
321
+ defaultValue={config.lastName}
322
+ />
323
+ </div>
324
+ </div>
325
+
326
+ <div className="sm:col-span-3">
327
+ <label htmlFor="first-name" className={labelClass}>
328
+ 名字
329
+ </label>
330
+ <div className="mt-2">
331
+ <input
332
+ ref={firstNameRef}
333
+ type="text"
334
+ name="first-name"
335
+ id="first-name"
336
+ autoComplete="given-name"
337
+ className={inputClass}
338
+ defaultValue={config.firstName}
339
+ />
340
+ </div>
341
+ </div>
342
+
343
+ <div className="sm:col-span-3">
344
+ <label htmlFor="mobile-number" className={labelClass}>
345
+ 手机号
346
+ </label>
347
+ <div className="mt-2">
348
+ <input
349
+ type="tel"
350
+ ref={mobileRef}
351
+ name="mobile-number"
352
+ id="mobile-number"
353
+ className={inputClass}
354
+ defaultValue={config.mobile}
355
+ />
356
+ </div>
357
+ </div>
358
+
359
+ <div className="sm:col-span-3">
360
+ <label htmlFor="last-code" className={labelClass}>
361
+ 身份证后四位
362
+ </label>
363
+ <div className="mt-2">
364
+ <input
365
+ type="text"
366
+ ref={last4codeRef}
367
+ name="last-code"
368
+ id="last-code"
369
+ className={inputClass}
370
+ defaultValue={config.last4code}
371
+ />
372
+ </div>
373
+ </div>
374
+
375
+ <div className="sm:col-span-3">
376
+ <label htmlFor="email" className={labelClass}>
377
+ 邮箱/Apple ID
378
+ </label>
379
+ <div className="mt-2">
380
+ <input
381
+ ref={appleidRef}
382
+ id="email"
383
+ name="email"
384
+ type="email"
385
+ autoComplete="email"
386
+ className={inputClass}
387
+ defaultValue={config.appleId}
388
+ />
389
+ </div>
390
+ </div>
391
+
392
+ <div className="sm:col-span-3">
393
+ <label htmlFor="password" className={labelClass}>
394
+ 登录密码(可选,不填则以访客模式下单)
395
+ </label>
396
+ <div className="mt-2">
397
+ <input
398
+ ref={passwordRef}
399
+ id="password"
400
+ name="password"
401
+ type="password"
402
+ autoComplete="password"
403
+ className={inputClass}
404
+ defaultValue={config.password}
405
+ />
406
+ </div>
407
+ </div>
408
+
409
+ <div className="sm:col-span-3">
410
+ <label htmlFor="pay-type" className={labelClass}>
411
+ 支付方式
412
+ </label>
413
+ <div className="mt-2">
414
+ <DropListBox
415
+ itemList={billItemList}
416
+ domID={'pay-type'}
417
+ selectedIndex={billSelected}
418
+ callback={handleSelectPayType}
419
+ />
420
+ </div>
421
+ </div>
422
+
423
+ <div className="sm:col-span-3">
424
+ <label htmlFor="pay-installment" className={labelClass}>
425
+ 分期笔数
426
+ </label>
427
+ <div className="mt-2">
428
+ <DropListBox
429
+ itemList={payinstallmentList}
430
+ domID={'pay-installment'}
431
+ selectedIndex={payinstallmentSelected}
432
+ callback={handleSelectPayinstallment}
433
+ />
434
+ </div>
435
+ </div>
436
+
437
+ <div className="sm:col-span-2 sm:col-start-1">
438
+ <label htmlFor="province-list" className={labelClass}>
439
+ 省份
440
+ </label>
441
+ <div className="mt-2">
442
+ <DropListBox
443
+ itemList={provinceList}
444
+ selectedIndex={selectedProvinceIndex}
445
+ domID={'province-list'}
446
+ callback={handleSelectProvince}
447
+ />
448
+ </div>
449
+ </div>
450
+
451
+ <div className="sm:col-span-2">
452
+ <label htmlFor="city-list" className={labelClass}>
453
+ 城市
454
+ </label>
455
+ <div className="mt-2">
456
+ <DropListBox
457
+ itemList={cityList}
458
+ domID={'city-list'}
459
+ callback={handleSelectCity}
460
+ selectedIndex={selectedCityIndex}
461
+ />
462
+ </div>
463
+ </div>
464
+
465
+ <div className="sm:col-span-2">
466
+ <label htmlFor="district-list" className={labelClass}>
467
+ 区名
468
+ </label>
469
+ <div className="mt-2">
470
+ <DropListBox
471
+ itemList={districtList}
472
+ selectedIndex={selectedDistrictIndex}
473
+ domID={'district-list'}
474
+ callback={handleSelectDistrict}
475
+ />
476
+ </div>
477
+ </div>
478
+ </div>
479
+ </div>
480
+
481
+ <div className="border-b border-gray-900/10 pb-12">
482
+ <h2 className="text-base font-semibold leading-7 text-gray-900">系统设置</h2>
483
+ <h4 className="text-sm font-medium leading-7 text-gray-900">
484
+ 如果你不了解以下配置应该如何设置,保持默认值即可。
485
+ </h4>
486
+ <p className="mt-1 text-sm leading-6 text-gray-600"></p>
487
+ <div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
488
+ <div className="sm:col-span-3">
489
+ <label htmlFor="step-wait" className={labelClass}>
490
+ StepWait (步频等待秒数。默认10秒,不建议设置过短,会封IP)
491
+ </label>
492
+ <div className="mt-2">
493
+ <input
494
+ ref={stepWaitRef}
495
+ id={'step-wait'}
496
+ min={1}
497
+ max={20}
498
+ step={0.5}
499
+ type="number"
500
+ defaultValue={config.stepWait}
501
+ className={inputClass}
502
+ />
503
+ </div>
504
+ </div>
505
+ <div className="sm:col-span-3">
506
+ <label htmlFor="beforereload-count" className={labelClass}>
507
+ 每周期重试次数 (默认50,后台请求达到该次数才会刷新页面,防止签名过期)
508
+ </label>
509
+ <div className="mt-2">
510
+ <input
511
+ ref={beforeReloadCountRef}
512
+ id={'beforereload-count'}
513
+ min={1}
514
+ max={100}
515
+ step={1}
516
+ type="number"
517
+ defaultValue={config.afterCountThenReload}
518
+ className={inputClass}
519
+ />
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
525
+ <div className="sm:col-span-3">
526
+ <label htmlFor="voice-list" className={labelClass}>
527
+ 提示音声音
528
+ </label>
529
+ <div className="mt-2">
530
+ <DropListBox
531
+ itemList={voiceList}
532
+ selectedIndex={voiceSelected}
533
+ domID={'voice-list'}
534
+ callback={handleSelectVoice}
535
+ />
536
+ </div>
537
+ </div>
538
+ <div className="sm:col-span-3">
539
+ <label htmlFor="voice-times" className={labelClass}>
540
+ 播放次数
541
+ </label>
542
+ <div className="mt-2">
543
+ <input
544
+ ref={voiceTimesRef}
545
+ id="voice-times"
546
+ name="voice-times"
547
+ type="number"
548
+ min={1}
549
+ max={15}
550
+ step={1}
551
+ className={inputClass}
552
+ defaultValue={config.voiceInfo.times}
553
+ />
554
+ </div>
555
+ </div>
556
+
557
+ <div className="sm:col-span-5">
558
+ <label htmlFor="voice-text" className={labelClass}>
559
+ 提示音文本
560
+ </label>
561
+ <div className="mt-2">
562
+ <input
563
+ type="text"
564
+ ref={voiceTextRef}
565
+ name="voice-text"
566
+ id="voice-text"
567
+ className={inputClass}
568
+ defaultValue={config.voiceInfo.text || ''}
569
+ />
570
+ </div>
571
+ </div>
572
+ <div className="sm:col-span-1 align-bottom flex items-end justify-end">
573
+ <div className="flex w-1/2 items-end justify-end">
574
+ <button
575
+ type="submit"
576
+ className="rounded-md w-full bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
577
+ onClick={handlePlaySound}
578
+ >
579
+ 播放
580
+ </button>
581
+ </div>
582
+ </div>
583
+ </div>
584
+ </div>
585
+
586
+ <div className="border-b border-gray-900/10 pb-12">
587
+ <h2 className="text-base font-semibold leading-7 text-gray-900">实验性功能</h2>
588
+ <h4 className="text-sm font-medium leading-7 text-gray-900">
589
+ 如果你不了解以下配置的功能,可以忽略它们。
590
+ </h4>
591
+ <p className="mt-1 text-sm leading-6 text-gray-600"></p>
592
+ <div className="mt-10 space-y-10">
593
+ <div className="sm:col-span-6">
594
+ <label htmlFor="noti-api" className={labelClass}>
595
+ 自定义消息API (由于跨域限制,仅支持GET请求,将以图片 src 方式引入并调用)
596
+ </label>
597
+ <div className="mt-2">
598
+ <input
599
+ type="text"
600
+ ref={notiAPIRef}
601
+ name="noti-api"
602
+ id="noti-api"
603
+ className={inputClass}
604
+ defaultValue={config.selfNotiAPI || ''}
605
+ />
606
+ </div>
607
+ </div>
608
+ </div>
609
+ </div>
610
+
611
+ {/* <div className="border-b border-gray-900/10 pb-12">
612
+ <h2 className="text-base font-semibold leading-7 text-gray-900">Notifications</h2>
613
+ <p className="mt-1 text-sm leading-6 text-gray-600"></p>
614
+
615
+ <div className="mt-10 space-y-10">
616
+ <fieldset>
617
+ <legend className="text-sm font-semibold leading-6 text-gray-900">By Email</legend>
618
+ <div className="mt-6 space-y-6">
619
+ <div className="relative flex gap-x-3">
620
+ <div className="flex h-6 items-center">
621
+ <input
622
+ id="comments"
623
+ name="comments"
624
+ type="checkbox"
625
+ className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
626
+ />
627
+ </div>
628
+ <div className="text-sm leading-6">
629
+ <label htmlFor="comments" className="font-medium text-gray-900">
630
+ Comments
631
+ </label>
632
+ <p className="text-gray-500">
633
+ Get notified when someones posts a comment on a posting.
634
+ </p>
635
+ </div>
636
+ </div>
637
+
638
+ </div>
639
+ </fieldset>
640
+ <fieldset>
641
+ <legend className="text-sm font-semibold leading-6 text-gray-900">
642
+ Push Notifications
643
+ </legend>
644
+ <p className="mt-1 text-sm leading-6 text-gray-600">
645
+ These are delivered via SMS to your mobile phone.
646
+ </p>
647
+ <div className="mt-6 space-y-6">
648
+ <div className="flex items-center gap-x-3">
649
+ <input
650
+ id="push-everything"
651
+ name="push-notifications"
652
+ type="radio"
653
+ className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
654
+ />
655
+ <label htmlFor="push-everything" className={labelClass}>
656
+ Everything
657
+ </label>
658
+ </div>
659
+
660
+ </div>
661
+ </fieldset>
662
+ </div>
663
+ </div> */}
664
+ </div>
665
+
666
+ <div className="mt-6 flex items-center justify-end gap-x-6">
667
+ <button type="button" className="text-sm font-semibold leading-6 text-gray-900" onClick={handleCancel}>
668
+ 取消
669
+ </button>
670
+ <button
671
+ type="submit"
672
+ className="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
673
+ onClick={handleSave}
674
+ >
675
+ 保存
676
+ </button>
677
+ </div>
678
+ </main>
679
+ )
680
+ }
681
+
682
+ const saveAsync = async (config: IPHONEORDER_CONFIG) => {
683
+ await saveToStorage(config, storeKeys.orderConfig)
684
+ await sleep(1)
685
+ window.close()
686
+ }
687
+
688
+ const validateVoices = ['zh-cn', 'zh-tw', 'zh-hk', 'en-us', 'en-gb']
689
+ const getVoices = async (): Promise<VoiceType[]> => {
690
+ let voiceList: VoiceType[] = [defaultItem]
691
+ if (typeof chrome !== 'undefined' && chrome?.tts) {
692
+ return new Promise((resolve, reject) => {
693
+ chrome.tts.getVoices(
694
+ // @ts-ignore
695
+ function (voices) {
696
+ voiceList = []
697
+ _map(voices, v => {
698
+ const { lang, voiceName } = v || {}
699
+ const prefixlang = lang && lang.toLowerCase()
700
+ if (validateVoices.includes(prefixlang)) {
701
+ voiceList.push({
702
+ lang: lang,
703
+ voiceName: voiceName,
704
+ name: `${lang} - ${voiceName}`,
705
+ id: `${lang} - ${voiceName}`,
706
+ })
707
+ }
708
+ })
709
+ voiceList = _sortBy(voiceList, function (o) {
710
+ const x = !o?.lang ? 9999 : validateVoices.indexOf(o.lang.toLowerCase())
711
+ return x
712
+ })
713
+ resolve(voiceList)
714
+ }
715
+ )
716
+ })
717
+ }
718
+
719
+ return voiceList
720
+ }